22 std::runtime_error(msg)
29class MultiCurlHandler {
31 MultiCurlHandler(std::vector<State*> &states, XrdSysError &log) :
32 m_handle(curl_multi_init()),
35 m_bytes_transferred(0),
39 if (m_handle == NULL) {
40 throw CurlHandlerSetupError(
"Failed to initialize a libcurl multi-handle");
42 m_avail_handles.reserve(states.size());
43 m_active_handles.reserve(states.size());
44 for (std::vector<State*>::const_iterator state_iter = states.begin();
45 state_iter != states.end();
47 m_avail_handles.push_back((*state_iter)->GetHandle());
53 if (!m_handle) {
return;}
54 for (std::vector<CURL *>::const_iterator it = m_active_handles.begin();
55 it != m_active_handles.end();
57 curl_multi_remove_handle(m_handle, *it);
59 curl_multi_cleanup(m_handle);
62 MultiCurlHandler(
const MultiCurlHandler &) =
delete;
64 CURLM *Get()
const {
return m_handle;}
66 void FinishCurlXfer(
CURL *curl) {
67 CURLMcode mres = curl_multi_remove_handle(m_handle, curl);
70 ss <<
"Failed to remove transfer from set: "
71 << curl_multi_strerror(mres);
72 throw std::runtime_error(ss.str());
74 for (std::vector<State*>::iterator state_iter = m_states.begin();
75 state_iter != m_states.end();
77 if (curl == (*state_iter)->GetHandle()) {
78 m_bytes_transferred += (*state_iter)->BytesTransferred();
79 int error_code = (*state_iter)->GetErrorCode();
80 if (error_code && !m_error_code) {
81 m_error_code = error_code;
82 m_error_message = (*state_iter)->GetErrorMessage();
84 int status_code = (*state_iter)->GetStatusCode();
85 if (status_code >= 400 && !m_status_code) {
86 m_status_code = status_code;
87 m_error_message = (*state_iter)->GetErrorMessage();
89 (*state_iter)->ResetAfterRequest();
93 for (std::vector<CURL *>::iterator iter = m_active_handles.begin();
94 iter != m_active_handles.end();
98 m_active_handles.erase(iter);
102 m_avail_handles.push_back(curl);
105 off_t StartTransfers(off_t current_offset, off_t content_length,
size_t block_size,
106 int &running_handles) {
107 bool started_new_xfer =
false;
109 size_t xfer_size = std::min(content_length - current_offset,
static_cast<off_t
>(block_size));
110 if (xfer_size == 0) {
return current_offset;}
111 if (!(started_new_xfer = StartTransfer(current_offset, xfer_size))) {
113 if (running_handles == 0) {
114 if (!CanStartTransfer(
true)) {
115 m_log.Emsg(
"StartTransfers",
"Unable to start transfers.");
120 running_handles += 1;
122 current_offset += xfer_size;
124 return current_offset;
132 int Flush(std::string &error_msg) {
133 if (m_states.empty() || (m_states[0]->Flush() != -1)) {
136 error_msg = m_states[0]->GetFinalizeErrorMessage();
137 if (error_msg.empty()) {error_msg =
"(no error message provided)";}
141 off_t BytesTransferred()
const {
142 return m_bytes_transferred;
153 off_t BytesInFlightAndTransferred()
const {
154 off_t bytes = m_bytes_transferred;
155 for (std::vector<State*>::const_iterator state_iter = m_states.begin();
156 state_iter != m_states.end();
158 bytes += (*state_iter)->BytesTransferred();
163 int GetStatusCode()
const {
164 return m_status_code;
167 int GetErrorCode()
const {
171 void SetErrorCode(
int error_code) {
172 m_error_code = error_code;
175 std::string GetErrorMessage()
const {
176 return m_error_message;
179 void SetErrorMessage(
const std::string &error_msg) {
180 m_error_message = error_msg;
185 bool StartTransfer(off_t offset,
size_t size) {
186 if (!CanStartTransfer(
false)) {
return false;}
187 for (std::vector<CURL*>::const_iterator handle_it = m_avail_handles.begin();
188 handle_it != m_avail_handles.end();
190 for (std::vector<State*>::iterator state_it = m_states.begin();
191 state_it != m_states.end();
193 if ((*state_it)->GetHandle() == *handle_it) {
194 (*state_it)->SetTransferParameters(offset, size);
195 ActivateHandle(**state_it);
203 void ActivateHandle(State &state) {
205 m_active_handles.push_back(curl);
207 mres = curl_multi_add_handle(m_handle, curl);
209 std::stringstream ss;
210 ss <<
"Failed to add transfer to libcurl multi-handle"
211 << curl_multi_strerror(mres);
212 throw std::runtime_error(ss.str());
214 for (
auto iter = m_avail_handles.begin();
215 iter != m_avail_handles.end();
219 m_avail_handles.erase(iter);
225 bool CanStartTransfer(
bool log_reason)
const {
226 size_t idle_handles = m_avail_handles.size();
227 size_t transfer_in_progress = 0;
228 for (std::vector<State*>::const_iterator state_iter = m_states.begin();
229 state_iter != m_states.end();
231 for (std::vector<CURL*>::const_iterator handle_iter = m_active_handles.begin();
232 handle_iter != m_active_handles.end();
234 if (*handle_iter == (*state_iter)->GetHandle()) {
235 transfer_in_progress += (*state_iter)->BodyTransferInProgress();
242 m_log.Emsg(
"CanStartTransfer",
"Unable to start transfers as no idle CURL handles are available.");
246 ssize_t available_buffers = m_states[0]->AvailableBuffers();
249 available_buffers -= (m_active_handles.size() - transfer_in_progress);
250 if (log_reason && (available_buffers == 0)) {
251 std::stringstream ss;
252 ss <<
"Unable to start transfers as no buffers are available. Available buffers: " <<
253 m_states[0]->AvailableBuffers() <<
", Active curl handles: " << m_active_handles.size()
254 <<
", Transfers in progress: " << transfer_in_progress;
255 m_log.Emsg(
"CanStartTransfer", ss.str().c_str());
256 if (m_states[0]->AvailableBuffers() == 0) {
257 m_states[0]->DumpBuffers();
260 return available_buffers > 0;
264 std::vector<CURL *> m_avail_handles;
265 std::vector<CURL *> m_active_handles;
266 std::vector<State*> &m_states;
268 off_t m_bytes_transferred;
271 std::string m_error_message;
277 size_t streams, std::vector<State*> &handles,
278 std::vector<ManagedCurlHandle> &curl_handles, TPCLogRecord &rec)
283 off_t current_offset = 0;
285 size_t concurrency = streams * m_pipelining_multiplier;
287 handles.reserve(concurrency);
288 handles.push_back(
new State());
289 handles[0]->Move(state);
290 for (
size_t idx = 1; idx < concurrency; idx++) {
291 handles.push_back(handles[0]->
Duplicate());
292 curl_handles.emplace_back(handles.back()->GetHandle());
296 rec.pmarkManager.startTransfer();
299 MultiCurlHandler mch(handles, m_log);
300 CURLM *multi_handle = mch.Get();
302 curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, 1);
303 curl_multi_setopt(multi_handle, CURLMOPT_MAX_HOST_CONNECTIONS, streams);
309 "Failed to send the initial response to the TPC client");
313 "Initial transfer response sent to the TPC client");
317 int running_handles = 0;
318 current_offset = mch.StartTransfers(current_offset, content_size, m_block_size, running_handles);
322 time_t last_marker = 0;
324 off_t last_advance_bytes = 0;
325 time_t last_advance_time = time(NULL);
326 time_t transfer_start = last_advance_time;
327 CURLcode res =
static_cast<CURLcode
>(-1);
328 CURLMcode mres = CURLM_OK;
330 time_t now = time(NULL);
331 time_t next_marker = last_marker + m_marker_period;
332 if (now >= next_marker) {
337 const off_t bytes_transferred = mch.BytesInFlightAndTransferred();
338 if (bytes_transferred > last_advance_bytes) {
339 last_advance_bytes = bytes_transferred;
340 last_advance_time = now;
342 if (SendPerfMarker(req, rec, handles, bytes_transferred)) {
344 "Failed to send a perf marker to the TPC client");
347 int timeout = (transfer_start == last_advance_time) ? m_first_timeout : m_timeout;
348 if (now > last_advance_time + timeout) {
349 const char *log_prefix = rec.log_prefix.c_str();
350 bool tpc_pull = strncmp(
"Pull", log_prefix, 4) == 0;
353 std::stringstream ss;
354 ss <<
"Transfer failed because no bytes have been "
355 << (tpc_pull ?
"received from the source (pull mode) in "
356 :
"transmitted to the destination (push mode) in ") << timeout <<
" seconds.";
357 mch.SetErrorMessage(ss.str());
363 mres = curl_multi_perform(multi_handle, &running_handles);
364 if (mres == CURLM_CALL_MULTI_PERFORM) {
368 }
else if (mres != CURLM_OK) {
372 rec.pmarkManager.beginPMarks();
379 msg = curl_multi_info_read(multi_handle, &msgq);
380 if (msg && (msg->msg == CURLMSG_DONE)) {
381 CURL *easy_handle = msg->easy_handle;
382 res = msg->data.result;
383 mch.FinishCurlXfer(easy_handle);
385 if (res != CURLE_OK) {
390 if (res !=
static_cast<CURLcode
>(-1) && res != CURLE_OK) {
391 std::stringstream ss;
392 ss <<
"Breaking loop due to failed curl transfer: " << curl_easy_strerror(res);
398 if (running_handles <
static_cast<int>(concurrency)) {
401 if (current_offset != content_size) {
402 current_offset = mch.StartTransfers(current_offset, content_size,
403 m_block_size, running_handles);
404 if (!running_handles) {
405 std::stringstream ss;
406 ss <<
"No handles are able to run. Streams=" << streams <<
", concurrency="
409 logTransferEvent(
LogMask::Debug, rec,
"MULTISTREAM_IDLE", ss.str());
411 }
else if (running_handles == 0) {
413 "All the ranges have been scheduled and all the handles are done; ending the transfer loop.");
418 int64_t max_sleep_time = next_marker - time(NULL);
419 if (max_sleep_time <= 0) {
423 mres = curl_multi_wait(multi_handle, NULL, 0, max_sleep_time*1000,
425 if (mres != CURLM_OK) {
428 }
while (running_handles);
430 if (mres != CURLM_OK) {
431 std::stringstream ss;
432 ss <<
"Internal libcurl multi-handle error: "
433 << curl_multi_strerror(mres);
434 logTransferEvent(
LogMask::Error, rec,
"MULTISTREAM_ERROR", ss.str());
435 throw std::runtime_error(ss.str());
442 msg = curl_multi_info_read(multi_handle, &msgq);
443 if (msg && (msg->msg == CURLMSG_DONE)) {
444 CURL *easy_handle = msg->easy_handle;
445 mch.FinishCurlXfer(easy_handle);
446 if (res == CURLE_OK || res ==
static_cast<CURLcode
>(-1))
447 res = msg->data.result;
451 if (!state.
GetErrorCode() && res ==
static_cast<CURLcode
>(-1)) {
453 "Internal state error in libcurl");
454 throw std::runtime_error(
"Internal state error in libcurl");
460 std::string flushErrorMsg;
461 const int flushErrorCode = mch.Flush(flushErrorMsg);
462 std::string flushErrorSuffix;
463 if (flushErrorCode) {
464 std::replace(flushErrorMsg.begin(), flushErrorMsg.end(),
'\n',
' ');
465 flushErrorMsg =
"Failed to flush the file to the local filesystem. " + flushErrorMsg;
466 logTransferEvent(
LogMask::Error, rec,
"FLUSH_FAIL", flushErrorMsg);
467 flushErrorSuffix =
"; " + flushErrorMsg;
470 rec.bytes_transferred = mch.BytesTransferred();
471 rec.tpc_status = mch.GetStatusCode();
474 std::stringstream ss;
476 if (mch.GetStatusCode() >= 400) {
477 std::string err = mch.GetErrorMessage();
478 std::stringstream ss2;
479 ss2 <<
"Remote side failed with status code " << mch.GetStatusCode();
481 std::replace(err.begin(), err.end(),
'\n',
' ');
482 ss2 <<
"; error message: \"" << err <<
"\"";
484 logTransferEvent(
LogMask::Error, rec,
"MULTISTREAM_FAIL", ss2.str());
485 ss2 << flushErrorSuffix;
486 ss << generateClientErr(ss2, rec);
490 std::stringstream ss2;
491 ss2 << mch.GetErrorMessage();
492 logTransferEvent(
LogMask::Error, rec,
"MULTISTREAM_FAIL", ss2.str());
493 ss2 << flushErrorSuffix;
494 ss << generateClientErr(ss2, rec);
495 }
else if (mch.GetErrorCode()) {
496 std::string err = mch.GetErrorMessage();
497 if (err.empty()) {err =
"(no error message provided)";}
498 else {std::replace(err.begin(), err.end(),
'\n',
' ');}
499 std::stringstream ss2;
500 ss2 <<
"Error when interacting with local filesystem: " << err;
501 logTransferEvent(
LogMask::Error, rec,
"MULTISTREAM_FAIL", ss2.str());
502 ss2 << flushErrorSuffix;
503 ss << generateClientErr(ss2, rec);
504 }
else if (res != CURLE_OK) {
505 std::stringstream ss2;
506 ss2 <<
"Request failed when processing";
507 std::stringstream ss3;
508 ss3 << ss2.str() <<
":" << curl_easy_strerror(res);
509 logTransferEvent(
LogMask::Error, rec,
"MULTISTREAM_FAIL", ss3.str());
510 ss2 << flushErrorSuffix;
511 ss << generateClientErr(ss2, rec, res);
512 }
else if (current_offset != content_size) {
513 std::stringstream ss2;
514 ss2 <<
"Internal logic error led to early abort; current offset is " <<
515 current_offset <<
" while full size is " << content_size;
516 logTransferEvent(
LogMask::Error, rec,
"MULTISTREAM_FAIL", ss2.str());
517 ss2 << flushErrorSuffix;
518 ss << generateClientErr(ss2, rec);
519 }
else if (flushErrorCode) {
521 std::stringstream ss2;
522 ss2 << flushErrorMsg;
523 ss << generateClientErr(ss2, rec);
525 if (!handles[0]->Finalize()) {
526 std::stringstream ss2;
527 ss2 <<
"Failed to finalize and close file handle.";
528 std::string handleErrMsg = handles[0]->GetFinalizeErrorMessage();
529 if(handleErrMsg.size()) {
530 std::replace(handleErrMsg.begin(), handleErrMsg.end(),
'\n',
' ');
531 ss2 <<
" " << handleErrMsg;
533 ss << generateClientErr(ss2, rec);
537 ss <<
"success: Created";
542 if ((retval = req.
ChunkResp(ss.str().c_str(), 0))) {
544 "Failed to send last update to remote client");
546 }
else if (success) {
554int TPCHandler::RunCurlWithStreams(XrdHttpExtReq &req,
State &state,
555 size_t streams, TPCLogRecord &rec)
557 std::vector<ManagedCurlHandle> curl_handles;
558 std::vector<State*> handles;
559 std::stringstream err_ss;
561 int retval = RunCurlWithStreamsImpl(req, state, streams, handles, curl_handles, rec);
562 for (std::vector<State*>::iterator state_iter = handles.begin();
563 state_iter != handles.end();
568 }
catch (CurlHandlerSetupError &e) {
569 for (std::vector<State*>::iterator state_iter = handles.begin();
570 state_iter != handles.end();
576 logTransferEvent(
LogMask::Error, rec,
"MULTISTREAM_ERROR", e.what());
577 std::stringstream ss;
579 err_ss << generateClientErr(ss, rec);
581 }
catch (std::runtime_error &e) {
582 for (std::vector<State*>::iterator state_iter = handles.begin();
583 state_iter != handles.end();
588 logTransferEvent(
LogMask::Error, rec,
"MULTISTREAM_ERROR", e.what());
589 std::stringstream ss;
591 err_ss << generateClientErr(ss, rec);
593 if ((retval = req.
ChunkResp(err_ss.str().c_str(), 0))) {
CurlHandlerSetupError(const std::string &msg)
virtual ~CurlHandlerSetupError() noexcept
off_t GetContentLength() const
int ChunkResp(const char *body, long long bodylen)
Send a (potentially partial) body in a chunked response; invoking with NULL body.
int StartChunkedResp(int code, const char *desc, const char *header_to_add)
Starts a chunked response; body of request is sent over multiple parts using the SendChunkResp.
int SendSimpleResp(int code, const char *desc, const char *header_to_add, const char *body, long long bodylen)
Sends a basic response. If the length is < 0 then it is calculated internally.