381 if (m_recv_all_headers) {
382 m_recv_all_headers =
false;
383 m_recv_status_line =
false;
386 if (!m_recv_status_line) {
387 m_recv_status_line =
true;
389 std::stringstream ss(header_line);
391 if (!std::getline(ss, item,
' '))
return false;
392 m_resp_protocol = item;
393 if (!std::getline(ss, item,
' '))
return false;
395 m_status_code = std::stol(item);
399 if (m_status_code < 100 || m_status_code >= 600) {
402 if (!std::getline(ss, item,
'\n'))
return false;
403 auto cr_loc = item.find(
'\r');
404 if (cr_loc != std::string::npos) {
405 m_resp_message = item.substr(0, cr_loc);
407 m_resp_message = item;
412 if (header_line.empty() || header_line ==
"\n" || header_line ==
"\r\n") {
413 m_recv_all_headers =
true;
417 auto found = header_line.find(
":");
418 if (found == std::string::npos) {
422 std::string header_name = header_line.substr(0, found);
428 while (found < header_line.size()) {
429 if (header_line[found] !=
' ') {
break;}
432 std::string header_value = header_line.substr(found);
435 header_value.erase(header_value.find_last_not_of(
" \r\n\t") + 1);
439 auto iter = m_headers.find(header_name);
440 if (iter == m_headers.end()) {
441 m_headers.insert(iter, {header_name, {header_value}});
443 iter->second.push_back(header_value);
446 if (header_name ==
"Allow") {
447 std::string_view val(header_value);
448 while (!val.empty()) {
449 auto found = val.find(
',');
450 auto method = val.substr(0, found);
451 if (method ==
"PROPFIND") {
455 if (found == std::string_view::npos)
break;
456 val = val.substr(found + 1);
461 }
else if (header_name ==
"Last-Modified") {
463 if (strptime(header_value.c_str(),
"%a, %d %b %Y %H:%M:%S", &tm) !=
nullptr) {
464 m_modtime = timegm(&tm);
466 }
else if (header_name ==
"Content-Length") {
468 m_content_length = std::stoll(header_value);
473 else if (header_name ==
"Content-Type") {
474 std::string_view val(header_value);
475 auto found = val.find(
";");
476 auto first_type = val.substr(0, found);
477 m_multipart_byteranges = first_type ==
"multipart/byteranges";
478 if (m_multipart_byteranges) {
479 auto remainder = val.substr(found + 1);
480 found = remainder.find(
"boundary=");
481 if (found != std::string_view::npos) {
486 else if (header_name ==
"Content-Range") {
487 auto found = header_value.find(
" ");
488 if (found == std::string::npos) {
491 std::string range_unit = header_value.substr(0, found);
492 if (range_unit !=
"bytes") {
495 auto range_resp = header_value.substr(found + 1);
496 found = range_resp.find(
"/");
497 if (found == std::string::npos) {
500 auto incl_range = range_resp.substr(0, found);
501 found = incl_range.find(
"-");
502 if (found == std::string::npos) {
505 auto first_pos = incl_range.substr(0, found);
507 m_response_offset = std::stoll(first_pos);
511 auto last_pos = incl_range.substr(found + 1);
514 last_byte = std::stoll(last_pos);
518 m_content_length = last_byte - m_response_offset + 1;
520 else if (header_name ==
"Location") {
521 m_location = header_value;
522 }
else if (header_name ==
"Digest") {
525 else if (header_name ==
"Etag")
529 m_etag = header_value;
530 m_etag.erase(remove(m_etag.begin(), m_etag.end(),
'\"'), m_etag.end());
532 else if (header_name ==
"Cache-Control")
534 m_cache_control = header_value;
1072 auto now = std::chrono::system_clock::now().time_since_epoch().count();
1073 auto oldest_op = now;
1074 auto oldest_cycle = now;
1076 std::unique_lock lk(m_worker_stats_mutex);
1077 for (
const auto &entry : m_workers_last_completed_cycle) {
1078 if (!entry) {
continue;}
1079 auto cycle = entry->load(std::memory_order_relaxed);
1080 if (cycle < oldest_cycle) oldest_cycle = cycle;
1082 for (
const auto &entry : m_workers_oldest_op) {
1083 if (!entry) {
continue;}
1084 auto op = entry->load(std::memory_order_relaxed);
1085 if (op < oldest_op) oldest_op = op;
1088 auto oldest_op_dbl = std::chrono::duration<double>(std::chrono::system_clock::time_point(std::chrono::system_clock::duration(oldest_op)).time_since_epoch()).count();
1089 auto oldest_cycle_dbl = std::chrono::duration<double>(std::chrono::system_clock::time_point(std::chrono::system_clock::duration(oldest_cycle)).time_since_epoch()).count();
1090 std::string retval =
"{"
1091 "\"oldest_op\":" + std::to_string(oldest_op_dbl) +
","
1092 "\"oldest_cycle\":" + std::to_string(oldest_cycle_dbl) +
","
1095 for (
size_t verb_idx = 0; verb_idx < static_cast<int>(XrdClHttp::CurlOperation::HttpVerb::Count); verb_idx++) {
1097 for (
size_t op_idx = 0; op_idx < 402; op_idx++) {
1098 if (op_idx == 401)
continue;
1100 auto &op_stats = m_ops[verb_idx][op_idx];
1101 auto duration = op_stats.m_duration.load(std::memory_order_relaxed);
1102 if (duration == 0)
continue;
1104 std::string prefix =
"http_" + verb_str +
"_" + ((op_idx == 402) ?
"invalid" : std::to_string(200 + op_idx)) +
"_";
1106 auto duration_dbl = std::chrono::duration<double>(std::chrono::steady_clock::duration(duration)).count();
1107 retval +=
"\"" + prefix +
"duration\":" + std::to_string(duration_dbl) +
",";
1109 duration = op_stats.m_pause_duration.load(std::memory_order_relaxed);
1111 duration_dbl = std::chrono::duration<double>(std::chrono::steady_clock::duration(duration)).count();
1112 retval +=
"\"" + prefix +
"pause_duration\":" + std::to_string(duration_dbl) +
",";
1115 auto count = op_stats.m_bytes.load(std::memory_order_relaxed);
1116 if (count) retval +=
"\"" + prefix +
"bytes\":" + std::to_string(count) +
",";
1117 count = op_stats.m_error.load(std::memory_order_relaxed);
1118 if (count) retval +=
"\"" + prefix +
"error\":" + std::to_string(count) +
",";
1119 count = op_stats.m_finished.load(std::memory_order_relaxed);
1120 if (count) retval +=
"\"" + prefix +
"finished\":" + std::to_string(count) +
",";
1121 count = op_stats.m_client_timeout.load(std::memory_order_relaxed);
1122 if (count) retval +=
"\"" + prefix +
"client_timeout\":" + std::to_string(count) +
",";
1123 count = op_stats.m_server_timeout.load(std::memory_order_relaxed);
1124 if (count) retval +=
"\"" + prefix +
"server_timeout\":" + std::to_string(count) +
",";
1127 auto &op_stats = m_ops[verb_idx][401];
1128 auto duration = op_stats.m_duration.load(std::memory_order_relaxed);
1129 if (duration == 0)
continue;
1131 std::string prefix =
"http_" + verb_str +
"_";
1133 auto duration_dbl = std::chrono::duration<double>(std::chrono::steady_clock::duration(duration)).count();
1134 retval +=
"\"" + prefix +
"preheader_duration\":" + std::to_string(duration_dbl) +
",";
1136 auto count = op_stats.m_started.load(std::memory_order_relaxed);
1137 if (count) retval +=
"\"" + prefix +
"started\":" + std::to_string(count) +
",";
1138 count = op_stats.m_error.load(std::memory_order_relaxed);
1139 if (count) retval +=
"\"" + prefix +
"preheader_error\":" + std::to_string(count) +
",";
1140 count = op_stats.m_finished.load(std::memory_order_relaxed);
1141 if (count) retval +=
"\"" + prefix +
"preheader_finished\":" + std::to_string(count) +
",";
1142 count = op_stats.m_server_timeout.load(std::memory_order_relaxed);
1143 if (count) retval +=
"\"" + prefix +
"preheader_timeout\":" + std::to_string(count) +
",";
1144 count = op_stats.m_conncall_timeout.load(std::memory_order_relaxed);
1145 if (count) retval +=
"\"" + prefix +
"conncall_timeout\":" + std::to_string(count) +
",";
1150 "\"conncall_error\":" + std::to_string(m_conncall_errors.load(std::memory_order_relaxed)) +
","
1151 "\"conncall_started\":" + std::to_string(m_conncall_req.load(std::memory_order_relaxed)) +
","
1152 "\"conncall_success\":" + std::to_string(m_conncall_success.load(std::memory_order_relaxed)) +
","
1153 "\"conncall_timeout\":" + std::to_string(m_conncall_timeout.load(std::memory_order_relaxed)) +
1240 int max_pending = 50;
1243 auto &queue = *m_queue.get();
1246 CURLM *multi_handle = curl_multi_init();
1247 if (multi_handle ==
nullptr) {
1248 throw std::runtime_error(
"Failed to create curl multi-handle");
1251 int running_handles = 0;
1252 time_t last_maintenance = time(NULL);
1253 CURLMcode mres = CURLM_OK;
1257 std::unordered_map<int, WaitingForBroker> broker_reqs;
1258 std::vector<struct curl_waitfd> waitfds;
1262 auto release_handle = [&](
const std::shared_ptr<CurlOperation> &op,
CURL *handle) {
1263 if (!handle)
return;
1264 curl_multi_remove_handle(multi_handle, handle);
1265 if (op->GetCurlHandle()) op->ReleaseHandle();
1266 curl_easy_cleanup(handle);
1268 auto remove_op = [&](
CURL *handle) {
1269 auto iter = m_op_map.find(handle);
1270 if (iter == m_op_map.end())
return;
1271 const auto &[op, started_at] = iter->second;
1272 release_handle(op, handle);
1273 m_op_map.erase(iter);
1274 running_handles -= 1;
1275 for (
auto req = broker_reqs.begin(); req != broker_reqs.end();) {
1276 if (req->second.curl == handle) {
1277 req = broker_reqs.erase(req);
1283 auto start_options = [&](
CURL *parent_handle,
const std::shared_ptr<CurlOperation> &parent_op,
1284 const std::string &target) {
1285 std::shared_ptr<CurlOptionsOp> options_op;
1286 CURL *options_handle =
nullptr;
1288 std::string modified_url;
1289 options_op = std::make_shared<CurlOptionsOp>(parent_handle, parent_op,
1291 m_logger, parent_op->GetConnCalloutFunc());
1292 options_handle = queue.GetHandle();
1293 if (!options_handle || !options_op->Setup(options_handle, *
this)) {
1294 throw std::runtime_error(
"Failed to setup the curl handle for OPTIONS");
1297 m_logger->Debug(
kLogXrdClHttp,
"Failed to setup the curl handle for OPTIONS");
1300 "Failed to setup the curl handle for the OPTIONS operation");
1301 release_handle(options_op, options_handle);
1304 "Failed to setup the curl handle for the OPTIONS operation");
1305 OpRecord(*parent_op, OpKind::Error);
1308 m_op_map[options_handle] = {options_op, std::chrono::system_clock::now()};
1309 OpRecord(*options_op, OpKind::Start);
1310 running_handles += 1;
1311 auto options_res = curl_multi_add_handle(multi_handle, options_handle);
1312 if (options_res == CURLM_OK)
return true;
1314 "Unable to add OPTIONS operation to the curl multi-handle");
1315 OpRecord(*options_op, OpKind::Error);
1317 "Unable to add OPTIONS operation to the curl multi-handle");
1318 OpRecord(*parent_op, OpKind::Error);
1319 remove_op(options_handle);
1322 auto reactivate_parent = [&](
const std::shared_ptr<CurlOptionsOp> &options_op) {
1323 auto parent_op = options_op->GetOperation();
1324 auto parent_handle = options_op->GetParentCurlHandle();
1325 if (parent_op->IsDone()) {
1326 remove_op(parent_handle);
1331 if (parent_op->IsRedirect()) {
1333 auto action = parent_op->Redirect(target);
1335 OpRecord(*parent_op, OpKind::Error);
1336 remove_op(parent_handle);
1340 if (start_options(parent_handle, parent_op, target))
return true;
1341 remove_op(parent_handle);
1345 OpRecord(*parent_op, OpKind::Start);
1346 auto callout_socket = parent_op->WaitSocket();
1347 if (callout_socket >= 0) {
1348 broker_reqs[callout_socket] = {parent_handle, time(
nullptr) + 20};
1349 m_conncall_req.fetch_add(1, std::memory_order_relaxed);
1352 auto parent_res = curl_multi_add_handle(multi_handle, parent_handle);
1353 if (parent_res == CURLM_OK)
return true;
1355 "Unable to re-add the parent operation to the curl multi-handle: %s",
1356 curl_multi_strerror(parent_res));
1358 "Unable to re-add the parent operation to the curl multi-handle");
1359 OpRecord(*parent_op, OpKind::Error);
1360 remove_op(parent_handle);
1364 bool want_shutdown =
false;
1365 while (!want_shutdown) {
1366 m_last_completed_cycle.store(std::chrono::system_clock::now().time_since_epoch().count());
1367 auto oldest_op = std::chrono::system_clock::now();
1368 for (
const auto &[handle, entry] : m_op_map) {
1369 const auto &[op, started_at] = entry;
1370 OpRecord(*op, OpKind::Update);
1371 if (started_at < oldest_op) {
1372 oldest_op = started_at;
1375 m_oldest_op.store(oldest_op.time_since_epoch().count());
1379 auto op = m_continue_queue->TryConsume();
1386 m_logger->Debug(
kLogXrdClHttp,
"Ignoring continuation of operation that has already completed");
1389 m_logger->Debug(
kLogXrdClHttp,
"Continuing the curl handle from op %p on thread %d", op.get(), getthreadid());
1390 auto curl = op->GetCurlHandle();
1391 if (!op->ContinueHandle()) {
1392 op->Fail(
XrdCl::errInternal, 0,
"Failed to continue the curl handle for the operation");
1393 OpRecord(*op, OpKind::Error);
1397 auto iter = m_op_map.find(curl);
1398 if (iter != m_op_map.end()) {
1399 auto &[operation, started_at] = iter->second;
1400 started_at = std::chrono::system_clock::now();
1405 while (running_handles <
static_cast<int>(m_max_ops)) {
1406 auto op = running_handles == 0 ? queue.Consume(std::chrono::seconds(1)) : queue.TryConsume();
1410 auto curl = queue.GetHandle();
1411 if (curl ==
nullptr) {
1412 m_logger->Debug(
kLogXrdClHttp,
"Unable to allocate a curl handle");
1417 auto rv = op->Setup(curl, *
this);
1419 m_logger->Debug(
kLogXrdClHttp,
"Failed to setup the curl handle");
1420 op->Fail(
XrdCl::errInternal, ENOMEM,
"Failed to setup the curl handle for the operation");
1421 release_handle(op, curl);
1424 if (!op->FinishSetup(curl)) {
1425 m_logger->Debug(
kLogXrdClHttp,
"Failed to finish setup of the curl handle");
1426 op->Fail(
XrdCl::errInternal, ENOMEM,
"Failed to finish setup of the curl handle for the operation");
1427 release_handle(op, curl);
1431 m_logger->Debug(
kLogXrdClHttp,
"Unable to setup the curl handle");
1432 op->Fail(
XrdCl::errInternal, ENOMEM,
"Failed to setup the curl handle for the operation");
1433 release_handle(op, curl);
1436 op->SetContinueQueue(m_continue_queue);
1439 op->ReleaseHandle();
1440 queue.RecycleHandle(curl);
1443 m_op_map[curl] = {op, std::chrono::system_clock::now()};
1444 running_handles += 1;
1448 if (op->RequiresOptions()) {
1449 if (!start_options(curl, op, op->GetUrl())) {
1454 OpRecord(*op, OpKind::Start);
1455 auto add_res = curl_multi_add_handle(multi_handle, curl);
1456 if (add_res != CURLM_OK) {
1457 m_logger->Debug(
kLogXrdClHttp,
"Unable to add operation to the curl multi-handle");
1458 op->Fail(
XrdCl::errInternal, add_res,
"Unable to add operation to the curl multi-handle");
1459 OpRecord(*op, OpKind::Error);
1464 m_logger->Debug(
kLogXrdClHttp,
"Added request for URL %s to worker thread for processing", op->GetUrl().c_str());
1469 time_t now = time(NULL);
1470 time_t next_maintenance = last_maintenance + m_maintenance_period.load(std::memory_order_relaxed);
1471 if (now >= next_maintenance) {
1473 m_continue_queue->Expire();
1474 m_logger->Debug(
kLogXrdClHttp,
"Curl worker thread %d is running %d operations",
1475 getthreadid(), running_handles);
1476 last_maintenance = now;
1479 std::vector<std::pair<int, CURL *>> expired_ops;
1480 for (
const auto &[fd, request] : broker_reqs) {
1481 if (request.expiry < now) {
1482 expired_ops.emplace_back(fd, request.curl);
1485 for (
const auto &[fd, handle] : expired_ops) {
1486 auto iter = m_op_map.find(handle);
1487 if (iter == m_op_map.end()) {
1488 m_logger->Warning(
kLogXrdClHttp,
"Found an expired curl handle with no corresponding operation!");
1490 auto [op, started_at] = iter->second;
1492 OpRecord(*op, OpKind::ConncallTimeout);
1493 if (
auto options_op = std::dynamic_pointer_cast<CurlOptionsOp>(op))
1494 reactivate_parent(options_op);
1497 broker_reqs.erase(fd);
1498 m_conncall_timeout.fetch_add(1, std::memory_order_relaxed);
1506 waitfds.resize(3 + broker_reqs.size());
1508 waitfds[0].fd = queue.PollFD();
1509 waitfds[0].events = CURL_WAIT_POLLIN;
1510 waitfds[0].revents = 0;
1511 waitfds[1].fd = m_continue_queue->PollFD();
1512 waitfds[1].events = CURL_WAIT_POLLIN;
1513 waitfds[1].revents = 0;
1514 waitfds[2].fd = m_shutdown_pipe_r;
1515 waitfds[2].revents = 0;
1516 waitfds[2].events = CURL_WAIT_POLLIN | CURL_WAIT_POLLPRI;
1519 for (
const auto &[fd, request] : broker_reqs) {
1520 waitfds[idx].fd = fd;
1521 waitfds[idx].events = CURL_WAIT_POLLIN|CURL_WAIT_POLLPRI;
1522 waitfds[idx].revents = 0;
1527 curl_multi_timeout(multi_handle, &timeo);
1531 if (running_handles && timeo == -1) {
1536 mres = curl_multi_wait(multi_handle, &waitfds[0], waitfds.size(), 50,
nullptr);
1542 mres = curl_multi_wait(multi_handle, &waitfds[0], waitfds.size(), 50,
nullptr);
1544 if (mres != CURLM_OK) {
1545 m_logger->Warning(
kLogXrdClHttp,
"Failed to wait on multi-handle: %d", mres);
1549 for (
const auto &entry : waitfds) {
1551 if (waitfds[0].fd == entry.fd || waitfds[1].fd == entry.fd) {
1555 if ((waitfds[2].fd == entry.fd) && entry.revents) {
1556 want_shutdown =
true;
1559 if ((entry.revents & CURL_WAIT_POLLIN) != CURL_WAIT_POLLIN) {
1562 auto request = broker_reqs.find(entry.fd);
1563 if (request == broker_reqs.end())
continue;
1564 auto handle = request->second.curl;
1565 auto iter = m_op_map.find(handle);
1566 if (iter == m_op_map.end()) {
1567 m_logger->Warning(
kLogXrdClHttp,
"Internal error: broker responded on FD %d but no corresponding curl operation", entry.fd);
1568 broker_reqs.erase(entry.fd);
1569 m_conncall_errors.fetch_add(1, std::memory_order_relaxed);
1573 auto [op, started_at] = iter->second;
1574 auto result = op->WaitSocketCallback(err);
1576 m_logger->Warning(
kLogXrdClHttp,
"Error when invoking the broker callback: %s", err.c_str());
1578 OpRecord(*op, OpKind::Error);
1579 if (
auto options_op = std::dynamic_pointer_cast<CurlOptionsOp>(op))
1580 reactivate_parent(options_op);
1582 broker_reqs.erase(entry.fd);
1583 m_conncall_errors.fetch_add(1, std::memory_order_relaxed);
1585 broker_reqs.erase(entry.fd);
1586 auto add_res = curl_multi_add_handle(multi_handle, handle);
1587 if (add_res != CURLM_OK) {
1589 "Unable to add operation to the curl multi-handle after the broker callback");
1590 OpRecord(*op, OpKind::Error);
1591 if (
auto options_op = std::dynamic_pointer_cast<CurlOptionsOp>(op))
1592 reactivate_parent(options_op);
1594 m_conncall_errors.fetch_add(1, std::memory_order_relaxed);
1597 m_conncall_success.fetch_add(1, std::memory_order_relaxed);
1603 mres = curl_multi_perform(multi_handle, &still_running);
1604 if (mres == CURLM_CALL_MULTI_PERFORM) {
1607 }
else if (mres != CURLM_OK) {
1608 m_logger->Warning(
kLogXrdClHttp,
"Failed to perform multi-handle operation: %d", mres);
1615 msg = curl_multi_info_read(multi_handle, &msgq);
1616 if (msg && (msg->msg == CURLMSG_DONE)) {
1617 if (!msg->easy_handle) {
1618 m_logger->Warning(
kLogXrdClHttp,
"Logic error: got a callback for a null handle");
1619 mres = CURLM_BAD_EASY_HANDLE;
1622 CURL *
const easy_handle = msg->easy_handle;
1623 auto iter = m_op_map.find(easy_handle);
1624 if (iter == m_op_map.end()) {
1625 m_logger->Error(
kLogXrdClHttp,
"Logic error: got a callback for an entry that doesn't exist");
1626 mres = CURLM_BAD_EASY_HANDLE;
1629 auto [op, started_at] = iter->second;
1630 auto res = msg->data.result;
1633 curl_multi_remove_handle(multi_handle, easy_handle);
1634 bool keep_handle =
false;
1635 bool waiting_on_callout =
false;
1636 bool options_pending =
false;
1637 if (res == CURLE_OK) {
1638 auto sc = op->GetStatusCode();
1639 OpRecord(*op, OpKind::Finish);
1642 op->Fail(code, number, op->GetStatusMessage());
1643 op->ReleaseHandle();
1647 if (
auto options_op = std::dynamic_pointer_cast<CurlOptionsOp>(op))
1648 reactivate_parent(options_op);
1650 queue.RecycleHandle(easy_handle);
1655 if (
auto options_op = std::dynamic_pointer_cast<CurlOptionsOp>(op)) {
1656 options_op->Success();
1657 options_op->ReleaseHandle();
1658 if (!options_op->GetOperation()->IsDone()) {
1659 options_op->GetOperation()->OptionsDone();
1661 reactivate_parent(options_op);
1662 queue.RecycleHandle(easy_handle);
1664 if (op->IsRedirect()) {
1666 switch (op->Redirect(target)) {
1668 keep_handle =
false;
1672 OpRecord(*op, OpKind::Start);
1681 if (!start_options(easy_handle, op, target)) {
1682 keep_handle =
false;
1685 m_logger->Debug(
kLogXrdClHttp,
"Invoking the OPTIONS operation before redirect to %s", target.c_str());
1688 options_pending =
true;
1692 int callout_socket = op->WaitSocket();
1693 if ((waiting_on_callout = keep_handle && !options_pending && callout_socket >= 0)) {
1694 auto expiry = time(
nullptr) + 20;
1695 m_logger->Debug(
kLogXrdClHttp,
"Creating a callout wait request on socket %d", callout_socket);
1696 broker_reqs[callout_socket] = {easy_handle, expiry};
1697 m_conncall_req.fetch_add(1, std::memory_order_relaxed);
1701 if (!waiting_on_callout && !options_pending) {
1702 auto redirect_res = curl_multi_add_handle(multi_handle, easy_handle);
1703 if (redirect_res != CURLM_OK) {
1705 "Unable to add redirected operation to the curl multi-handle");
1706 OpRecord(*op, OpKind::Error);
1707 op->ReleaseHandle();
1708 queue.RecycleHandle(easy_handle);
1709 keep_handle =
false;
1713 if (!op->IsDone()) {
1717 op->ReleaseHandle();
1719 queue.RecycleHandle(easy_handle);
1725 auto next_res = curl_multi_add_handle(multi_handle, easy_handle);
1726 if (next_res == CURLM_OK) {
1728 OpRecord(*op, OpKind::Start);
1731 "Unable to add the next operation request to the curl multi-handle");
1732 OpRecord(*op, OpKind::Error);
1733 op->ReleaseHandle();
1734 queue.RecycleHandle(easy_handle);
1740 }
else if (res == CURLE_COULDNT_CONNECT && op->UseConnectionCallout() && !op->GetTriedBoker()) {
1744 op->SetTriedBoker();
1746 int wait_socket = -1;
1747 if (!op->StartConnectionCallout(err) || (wait_socket=op->WaitSocket()) == -1) {
1748 m_logger->Error(
kLogXrdClHttp,
"Failed to start broker-based connection: %s", err.c_str());
1751 OpRecord(*op, OpKind::Error);
1752 if (
auto options_op = std::dynamic_pointer_cast<CurlOptionsOp>(op))
1753 reactivate_parent(options_op);
1754 op->ReleaseHandle();
1755 keep_handle =
false;
1757 curl_multi_remove_handle(multi_handle, easy_handle);
1758 auto expiry = time(
nullptr) + 20;
1759 m_logger->Debug(
kLogXrdClHttp,
"Curl operation requires a new TCP socket; waiting for callout to respond on socket %d", wait_socket);
1760 broker_reqs[wait_socket] = {easy_handle, expiry};
1761 m_conncall_req.fetch_add(1, std::memory_order_relaxed);
1764 if (res == CURLE_ABORTED_BY_CALLBACK || res == CURLE_WRITE_ERROR) {
1767 switch (op->GetError()) {
1769#ifdef HAVE_XPROTOCOL_TIMEREXPIRED
1774 OpRecord(*op, OpKind::Error);
1777 auto [ecode,
emsg] = op->GetCallbackError();
1779 OpRecord(*op, OpKind::Error);
1784 OpRecord(*op, op->IsPaused() ? OpKind::ClientTimeout : OpKind::ServerTimeout);
1788 OpRecord(*op, OpKind::ServerTimeout);
1792 OpRecord(*op, OpKind::ClientTimeout);
1796 OpRecord(*op, OpKind::ServerTimeout);
1799 op->Fail(
XrdCl::errInternal, 0,
"Operation was aborted without recording an abort reason");
1800 OpRecord(*op, OpKind::Error);
1803 if (
auto options_op = std::dynamic_pointer_cast<CurlOptionsOp>(op))
1804 reactivate_parent(options_op);
1807 const auto curl_err = op->GetCurlErrorMessage();
1808 const char *curl_easy_err = curl_easy_strerror(res);
1809 const std::string fail_err = !curl_err.empty() ? curl_err : curl_easy_err;
1810 m_logger->Debug(
kLogXrdClHttp,
"Curl generated an error: %s (%d)", fail_err.c_str(), res);
1811 op->Fail(code, number, fail_err);
1812 OpRecord(*op, OpKind::Error);
1813 if (
auto options_op = std::dynamic_pointer_cast<CurlOptionsOp>(op))
1814 reactivate_parent(options_op);
1816 op->ReleaseHandle();
1819 curl_multi_remove_handle(multi_handle, easy_handle);
1820 if (res != CURLE_OK) {
1821 curl_easy_cleanup(easy_handle);
1823 for (
auto req = broker_reqs.begin(); req != broker_reqs.end();) {
1824 if (req->second.curl == easy_handle) {
1825 m_logger->Warning(
kLogXrdClHttp,
"Curl handle finished while a broker operation was outstanding");
1826 m_conncall_errors.fetch_add(1, std::memory_order_relaxed);
1827 req = broker_reqs.erase(req);
1832 m_op_map.erase(easy_handle);
1833 running_handles -= 1;
1837 if (mres != CURLM_OK)
break;
1840 for (
const auto &[handle, entry] : m_op_map) {
1841 const auto &[op, started_at] = entry;
1842 if (!op->IsDone()) {
1843 const auto err_num = mres == CURLM_OK ? ECANCELED :
static_cast<int>(mres);
1844 const std::string err_msg = mres == CURLM_OK
1845 ?
"Curl worker shut down before the operation completed"
1846 : curl_multi_strerror(mres);
1848 OpRecord(*op, OpKind::Error);
1850 release_handle(op, handle);
1854 m_queue->ReleaseHandles();
1855 curl_multi_cleanup(multi_handle);