1090 {
1091 int max_pending = 50;
1093 m_continue_queue.reset(new HandlerQueue(max_pending));
1094 auto &queue = *m_queue.get();
1096
1097 CURLM *multi_handle = curl_multi_init();
1098 if (multi_handle == nullptr) {
1099 throw std::runtime_error("Failed to create curl multi-handle");
1100 }
1101
1102 int running_handles = 0;
1103 time_t last_maintenance = time(NULL);
1104 CURLMcode mres = CURLM_OK;
1105
1106
1107
1108 std::unordered_map<int, WaitingForBroker> broker_reqs;
1109 std::vector<struct curl_waitfd> waitfds;
1110
1111 bool want_shutdown = false;
1112 while (!want_shutdown) {
1113 m_last_completed_cycle.store(std::chrono::system_clock::now().time_since_epoch().count());
1114 auto oldest_op = std::chrono::system_clock::now();
1115 for (const auto &entry : m_op_map) {
1116 OpRecord(*entry.second.first, OpKind::Update);
1117 if (entry.second.second < oldest_op) {
1118 oldest_op = entry.second.second;
1119 }
1120 }
1121 m_oldest_op.store(oldest_op.time_since_epoch().count());
1122
1123
1124 while (true) {
1125 auto op = m_continue_queue->TryConsume();
1126 if (!op) {
1127 break;
1128 }
1129
1130
1131 if (op->IsDone()) {
1132 m_logger->Debug(
kLogXrdClHttp,
"Ignoring continuation of operation that has already completed");
1133 continue;
1134 }
1135 m_logger->Debug(
kLogXrdClHttp,
"Continuing the curl handle from op %p on thread %d", op.get(), getthreadid());
1136 auto curl = op->GetCurlHandle();
1137 if (!op->ContinueHandle()) {
1138 op->Fail(
XrdCl::errInternal, 0,
"Failed to continue the curl handle for the operation");
1139 OpRecord(*op, OpKind::Error);
1140 op->ReleaseHandle();
1141 if (curl) {
1142 curl_multi_remove_handle(multi_handle, curl);
1143 curl_easy_cleanup(curl);
1144 m_op_map.erase(curl);
1145 }
1146 running_handles -= 1;
1147 continue;
1148 } else {
1149 auto iter = m_op_map.find(curl);
1150 if (iter != m_op_map.end()) iter->second.second = std::chrono::system_clock::now();
1151 }
1152 }
1153
1154 while (running_handles < static_cast<int>(m_max_ops)) {
1155 auto op = running_handles == 0 ? queue.Consume(std::chrono::seconds(1)) : queue.TryConsume();
1156 if (!op) {
1157 break;
1158 }
1159 auto curl = queue.GetHandle();
1160 if (curl == nullptr) {
1161 m_logger->Debug(
kLogXrdClHttp,
"Unable to allocate a curl handle");
1163 continue;
1164 }
1165 try {
1166 auto rv = op->Setup(curl, *this);
1167 if (!rv) {
1168 m_logger->Debug(
kLogXrdClHttp,
"Failed to setup the curl handle");
1169 op->Fail(
XrdCl::errInternal, ENOMEM,
"Failed to setup the curl handle for the operation");
1170 continue;
1171 }
1172 if (!op->FinishSetup(curl)) {
1173 m_logger->Debug(
kLogXrdClHttp,
"Failed to finish setup of the curl handle");
1174 op->Fail(
XrdCl::errInternal, ENOMEM,
"Failed to finish setup of the curl handle for the operation");
1175 continue;
1176 }
1177 } catch (...) {
1178 m_logger->Debug(
kLogXrdClHttp,
"Unable to setup the curl handle");
1179 op->Fail(
XrdCl::errInternal, ENOMEM,
"Failed to setup the curl handle for the operation");
1180 continue;
1181 }
1182 op->SetContinueQueue(m_continue_queue);
1183
1184 if (op->IsDone()) {
1185 continue;
1186 }
1187 m_op_map[curl] = {op, std::chrono::system_clock::now()};
1188
1189
1190
1191 if (op->RequiresOptions()) {
1192 std::string modified_url;
1193 std::shared_ptr<CurlOptionsOp> options_op(
1194 new CurlOptionsOp(
1195 curl, op,
1196 std::string(
1198 ),
1199 m_logger, op->GetConnCalloutFunc()
1200 )
1201 );
1202
1203
1204
1205 curl = queue.GetHandle();
1206 if (curl == nullptr) {
1207 m_logger->Debug(
kLogXrdClHttp,
"Unable to allocate a curl handle");
1209 OpRecord(*op, OpKind::Error);
1210 continue;
1211 }
1212 auto rv = options_op->Setup(curl, *this);
1213 if (!rv) {
1214 m_logger->Debug(
kLogXrdClHttp,
"Failed to allocate a curl handle for OPTIONS");
1215 continue;
1216 }
1217 m_op_map[curl] = {options_op, std::chrono::system_clock::now()};
1218 OpRecord(*options_op, OpKind::Start);
1219 running_handles += 1;
1220 } else {
1221 OpRecord(*op, OpKind::Start);
1222 }
1223
1224 auto mres = curl_multi_add_handle(multi_handle, curl);
1225 if (mres != CURLM_OK) {
1226 m_logger->Debug(
kLogXrdClHttp,
"Unable to add operation to the curl multi-handle");
1227 op->Fail(
XrdCl::errInternal, mres,
"Unable to add operation to the curl multi-handle");
1228 OpRecord(*op, OpKind::Error);
1229 continue;
1230 }
1231 m_logger->Debug(
kLogXrdClHttp,
"Added request for URL %s to worker thread for processing", op->GetUrl().c_str());
1232 running_handles += 1;
1233 }
1234
1235
1236
1237 time_t now = time(NULL);
1238 time_t next_maintenance = last_maintenance + m_maintenance_period.load(std::memory_order_relaxed);
1239 if (now >= next_maintenance) {
1240 m_queue->Expire();
1241 m_continue_queue->Expire();
1242 m_logger->Debug(
kLogXrdClHttp,
"Curl worker thread %d is running %d operations",
1243 getthreadid(), running_handles);
1244 last_maintenance = now;
1245
1246
1247 std::vector<std::pair<int, CURL *>> expired_ops;
1248 for (const auto &entry : broker_reqs) {
1249 if (entry.second.expiry < now) {
1250 expired_ops.emplace_back(entry.first, entry.second.curl);
1251 }
1252 }
1253 for (const auto &entry : expired_ops) {
1254 auto iter = m_op_map.find(entry.second);
1255 if (iter == m_op_map.end()) {
1256 m_logger->Warning(
kLogXrdClHttp,
"Found an expired curl handle with no corresponding operation!");
1257 } else {
1258
1259 CurlOptionsOp *options_op = nullptr;
1260 if ((options_op = dynamic_cast<CurlOptionsOp*>(iter->second.first.get())) != nullptr) {
1262 bool parent_op_failed = false;
1263 if (parent_op->IsRedirect()) {
1264 std::string target;
1267 if (iter != m_op_map.end()) {
1268 OpRecord(*iter->second.first, OpKind::Error);
1270 m_op_map.erase(iter);
1271 running_handles -= 1;
1272 }
1273 parent_op_failed = true;
1274 } else {
1275 OpRecord(*parent_op, OpKind::Start);
1276 }
1277 } else {
1278 OpRecord(*parent_op, OpKind::Start);
1279 }
1280 if (!parent_op_failed){
1282 }
1283 }
1284
1286 iter->second.first->ReleaseHandle();
1287 OpRecord(*(iter->second.first), OpKind::ConncallTimeout);
1288 m_op_map.erase(entry.second);
1289 curl_easy_cleanup(entry.second);
1290 running_handles -= 1;
1291 }
1292 broker_reqs.erase(entry.first);
1293 m_conncall_timeout.fetch_add(1, std::memory_order_relaxed);
1294 }
1295
1296
1298 }
1299
1300 waitfds.clear();
1301 waitfds.resize(3 + broker_reqs.size());
1302
1303 waitfds[0].fd = queue.PollFD();
1304 waitfds[0].events = CURL_WAIT_POLLIN;
1305 waitfds[0].revents = 0;
1306 waitfds[1].fd = m_continue_queue->PollFD();
1307 waitfds[1].events = CURL_WAIT_POLLIN;
1308 waitfds[1].revents = 0;
1309 waitfds[2].fd = m_shutdown_pipe_r;
1310 waitfds[2].revents = 0;
1311 waitfds[2].events = CURL_WAIT_POLLIN | CURL_WAIT_POLLPRI;
1312
1313 int idx = 3;
1314 for (const auto &entry : broker_reqs) {
1315 waitfds[idx].fd = entry.first;
1316 waitfds[idx].events = CURL_WAIT_POLLIN|CURL_WAIT_POLLPRI;
1317 waitfds[idx].revents = 0;
1318 idx += 1;
1319 }
1320
1321 long timeo;
1322 curl_multi_timeout(multi_handle, &timeo);
1323
1324
1325
1326 if (running_handles && timeo == -1) {
1327
1328
1329
1330
1331 mres = curl_multi_wait(multi_handle, &waitfds[0], waitfds.size(), 50, nullptr);
1332 } else {
1333
1334
1335
1336
1337 mres = curl_multi_wait(multi_handle, &waitfds[0], waitfds.size(), 50, nullptr);
1338 }
1339 if (mres != CURLM_OK) {
1340 m_logger->Warning(
kLogXrdClHttp,
"Failed to wait on multi-handle: %d", mres);
1341 }
1342
1343
1344 for (const auto &entry : waitfds) {
1345
1346 if (waitfds[0].fd == entry.fd || waitfds[1].fd == entry.fd) {
1347 continue;
1348 }
1349
1350 if ((waitfds[2].fd == entry.fd) && entry.revents) {
1351 want_shutdown = true;
1352 break;
1353 }
1354 if ((entry.revents & CURL_WAIT_POLLIN) != CURL_WAIT_POLLIN) {
1355 continue;
1356 }
1357 auto handle = broker_reqs[entry.fd].curl;
1358 auto iter = m_op_map.find(handle);
1359 if (iter == m_op_map.end()) {
1360 m_logger->Warning(
kLogXrdClHttp,
"Internal error: broker responded on FD %d but no corresponding curl operation", entry.fd);
1361 broker_reqs.erase(entry.fd);
1362 m_conncall_errors.fetch_add(1, std::memory_order_relaxed);
1363 continue;
1364 }
1365 std::string err;
1366 auto result = iter->second.first->WaitSocketCallback(err);
1367 if (result == -1) {
1368 m_logger->Warning(
kLogXrdClHttp,
"Error when invoking the broker callback: %s", err.c_str());
1369
1370 CurlOptionsOp *options_op = nullptr;
1371 if ((options_op = dynamic_cast<CurlOptionsOp*>(iter->second.first.get())) != nullptr) {
1373 bool parent_op_failed = false;
1374 if (parent_op->IsRedirect()) {
1375 std::string target;
1378 if (iter != m_op_map.end()) {
1379 OpRecord(*iter->second.first, OpKind::Error);
1381 m_op_map.erase(iter);
1382 running_handles -= 1;
1383 }
1384 parent_op_failed = true;
1385 } else {
1386 OpRecord(*parent_op, OpKind::Start);
1387 }
1388 } else {
1389 OpRecord(*parent_op, OpKind::Start);
1390 }
1391 if (!parent_op_failed){
1393 }
1394 }
1395
1397 OpRecord(*iter->second.first, OpKind::Error);
1398 m_op_map.erase(handle);
1399 broker_reqs.erase(entry.fd);
1400 m_conncall_errors.fetch_add(1, std::memory_order_relaxed);
1401 running_handles -= 1;
1402 } else {
1403 broker_reqs.erase(entry.fd);
1404 curl_multi_add_handle(multi_handle, handle);
1405 m_conncall_success.fetch_add(1, std::memory_order_relaxed);
1406 }
1407 }
1408
1409
1410 int still_running;
1411 auto mres = curl_multi_perform(multi_handle, &still_running);
1412 if (mres == CURLM_CALL_MULTI_PERFORM) {
1413 continue;
1414 } else if (mres != CURLM_OK) {
1415 m_logger->Warning(
kLogXrdClHttp,
"Failed to perform multi-handle operation: %d", mres);
1416 break;
1417 }
1418
1419 CURLMsg *msg;
1420 do {
1421 int msgq = 0;
1422 msg = curl_multi_info_read(multi_handle, &msgq);
1423 if (msg && (msg->msg == CURLMSG_DONE)) {
1424 if (!msg->easy_handle) {
1425 m_logger->Warning(
kLogXrdClHttp,
"Logic error: got a callback for a null handle");
1426 mres = CURLM_BAD_EASY_HANDLE;
1427 break;
1428 }
1429 auto iter = m_op_map.find(msg->easy_handle);
1430 if (iter == m_op_map.end()) {
1431 m_logger->Error(
kLogXrdClHttp,
"Logic error: got a callback for an entry that doesn't exist");
1432 mres = CURLM_BAD_EASY_HANDLE;
1433 break;
1434 }
1435 auto op = iter->second.first;
1436 auto res = msg->data.result;
1437 bool keep_handle = false;
1438 bool waiting_on_callout = false;
1439 if (res == CURLE_OK) {
1440 auto sc = op->GetStatusCode();
1441 OpRecord(*op, OpKind::Finish);
1444 op->Fail(httpErr.first, httpErr.second, op->GetStatusMessage());
1445 op->ReleaseHandle();
1446
1447
1448
1449 CurlOptionsOp *options_op = nullptr;
1450 if ((options_op = dynamic_cast<CurlOptionsOp*>(op.get())) != nullptr) {
1452 bool parent_op_failed = false;
1453 if (parent_op->IsRedirect()) {
1454 std::string target;
1456 OpRecord(*parent_op, OpKind::Error);
1458 running_handles -= 1;
1459 parent_op_failed = true;
1460 } else {
1461 OpRecord(*parent_op, OpKind::Start);
1462 }
1463 } else {
1464 OpRecord(*parent_op, OpKind::Start);
1465 }
1466
1467 if (!parent_op_failed) {
1469 }
1470 }
1471
1472 queue.RecycleHandle(iter->first);
1473 } else {
1474 CurlOptionsOp *options_op = nullptr;
1475
1476 if ((options_op = dynamic_cast<CurlOptionsOp*>(op.get()))) {
1479
1481 op->OptionsDone();
1482 OpRecord(*op, OpKind::Start);
1484 curl_multi_remove_handle(multi_handle, iter->first);
1485 queue.RecycleHandle(iter->first);
1486 }
1487
1488
1489
1490 if (op->IsRedirect()) {
1491 std::string target;
1492 switch (op->Redirect(target)) {
1494 if (options_op) {
1495
1496
1497
1498
1499
1500 OpRecord(*op, OpKind::Error);
1501 }
1502 keep_handle = false;
1503 break;
1505 if (!options_op) {
1506
1507
1508
1509 keep_handle = true;
1510 OpRecord(*op, OpKind::Start);
1511 }
1512 break;
1514 {
1515
1516
1517
1518
1519
1520 std::string modified_url;
1522 options_op = new CurlOptionsOp(iter->first, op, target, m_logger, op->GetConnCalloutFunc());
1523 std::shared_ptr<CurlOperation> new_op(options_op);
1524 auto curl = queue.GetHandle();
1525 if (curl == nullptr) {
1526 m_logger->Debug(
kLogXrdClHttp,
"Unable to allocate a curl handle");
1528 keep_handle = false;
1529 options_op = nullptr;
1530 break;
1531 }
1532 OpRecord(*new_op, OpKind::Start);
1533 try {
1534 auto rv = new_op->Setup(curl, *this);
1535 if (!rv) {
1536 m_logger->Debug(
kLogXrdClHttp,
"Unable to configure a curl handle for OPTIONS");
1537 keep_handle = false;
1538 options_op = nullptr;
1539 break;
1540 }
1541 } catch (...) {
1542 m_logger->Debug(
kLogXrdClHttp,
"Unable to setup the curl handle for the OPTIONS operation");
1543 new_op->Fail(
XrdCl::errInternal, ENOMEM,
"Failed to setup the curl handle for the OPTIONS operation");
1544 OpRecord(*new_op, OpKind::Error);
1545 keep_handle = false;
1546 break;
1547 }
1548 new_op->SetContinueQueue(m_continue_queue);
1549 m_op_map[curl] = {new_op, std::chrono::system_clock::now()};
1550 auto mres = curl_multi_add_handle(multi_handle, curl);
1551 if (mres != CURLM_OK) {
1552 m_logger->Debug(
kLogXrdClHttp,
"Unable to add OPTIONS operation to the curl multi-handle: %s", curl_multi_strerror(mres));
1553 op->Fail(
XrdCl::errInternal, mres,
"Unable to add OPTIONS operation to the curl multi-handle");
1554 OpRecord(*new_op, OpKind::Error);
1555 break;
1556 }
1557 running_handles += 1;
1558 m_logger->Debug(
kLogXrdClHttp,
"Invoking the OPTIONS operation before redirect to %s", target.c_str());
1559
1560
1561 keep_handle = true;
1562 }
1563 }
1564 int callout_socket = op->WaitSocket();
1565 if ((waiting_on_callout = callout_socket >= 0)) {
1566 auto expiry = time(nullptr) + 20;
1567 m_logger->Debug(
kLogXrdClHttp,
"Creating a callout wait request on socket %d", callout_socket);
1568 broker_reqs[callout_socket] = {iter->first, expiry};
1569 m_conncall_req.fetch_add(1, std::memory_order_relaxed);
1570 }
1571 } else if (options_op) {
1572
1574 }
1575 if (keep_handle) {
1576 curl_multi_remove_handle(multi_handle, iter->first);
1577 if (!waiting_on_callout && !options_op) {
1578 curl_multi_add_handle(multi_handle, iter->first);
1579 }
1580 } else if (!options_op) {
1581 op->Success();
1582 op->ReleaseHandle();
1583
1584 queue.RecycleHandle(iter->first);
1585 }
1586 }
1587 } else if (res == CURLE_COULDNT_CONNECT && op->UseConnectionCallout() && !op->GetTriedBoker()) {
1588
1589
1590 keep_handle = true;
1591 op->SetTriedBoker();
1592 std::string err;
1593 int wait_socket = -1;
1594 if (!op->StartConnectionCallout(err) || (wait_socket=op->WaitSocket()) == -1) {
1595 m_logger->Error(
kLogXrdClHttp,
"Failed to start broker-based connection: %s", err.c_str());
1596 op->ReleaseHandle();
1597 keep_handle = false;
1598 } else {
1599 curl_multi_remove_handle(multi_handle, iter->first);
1600 auto expiry = time(nullptr) + 20;
1601 m_logger->Debug(
kLogXrdClHttp,
"Curl operation requires a new TCP socket; waiting for callout to respond on socket %d", wait_socket);
1602 broker_reqs[wait_socket] = {iter->first, expiry};
1603 m_conncall_req.fetch_add(1, std::memory_order_relaxed);
1604 }
1605 } else {
1606 if (res == CURLE_ABORTED_BY_CALLBACK || res == CURLE_WRITE_ERROR) {
1607
1608
1609 switch (op->GetError()) {
1611#ifdef HAVE_XPROTOCOL_TIMEREXPIRED
1613#else
1615#endif
1616 OpRecord(*op, OpKind::Error);
1617 break;
1619 auto [ecode,
emsg] = op->GetCallbackError();
1621 OpRecord(*op, OpKind::Error);
1622 break;
1623 }
1626 OpRecord(*op, op->IsPaused() ? OpKind::ClientTimeout : OpKind::ServerTimeout);
1627 break;
1630 OpRecord(*op, OpKind::ServerTimeout);
1631 break;
1634 OpRecord(*op, OpKind::ClientTimeout);
1635 break;
1638 OpRecord(*op, OpKind::ServerTimeout);
1639 break;
1641 op->Fail(
XrdCl::errInternal, 0,
"Operation was aborted without recording an abort reason");
1642 OpRecord(*op, OpKind::Error);
1643 break;
1644 };
1645 CurlOptionsOp *options_op = nullptr;
1646 if ((options_op = dynamic_cast<CurlOptionsOp*>(op.get())) != nullptr) {
1648 bool parent_op_failed = false;
1649 if (parent_op->IsRedirect()) {
1650 std::string target;
1653 if (iter != m_op_map.end()) {
1654 OpRecord(*iter->second.first, OpKind::Error);
1656 m_op_map.erase(iter);
1657 running_handles -= 1;
1658 }
1659 parent_op_failed = true;
1660 } else {
1661 OpRecord(*parent_op, OpKind::Start);
1662 }
1663 } else {
1664 OpRecord(*parent_op, OpKind::Start);
1665 }
1666 if (!parent_op_failed){
1668 }
1669 }
1670 } else {
1672 const auto curl_err = op->GetCurlErrorMessage();
1673 const char *curl_easy_err = curl_easy_strerror(res);
1674 const std::string fail_err = !curl_err.empty() ? curl_err : curl_easy_err;
1675 m_logger->Debug(
kLogXrdClHttp,
"Curl generated an error: %s (%d)", fail_err.c_str(), res);
1676 op->Fail(xrdCode.first, xrdCode.second, fail_err);
1677 OpRecord(*op, OpKind::Error);
1678 CurlOptionsOp *options_op = nullptr;
1679 if ((options_op = dynamic_cast<CurlOptionsOp*>(op.get())) != nullptr) {
1681 bool parent_op_failed = false;
1682 if (parent_op->IsRedirect()) {
1683 std::string target;
1686 if (iter != m_op_map.end()) {
1687 OpRecord(*iter->second.first, OpKind::Error);
1689 m_op_map.erase(iter);
1690 running_handles -= 1;
1691 }
1692 parent_op_failed = true;
1693 }
1694 }
1695 if (!parent_op_failed){
1697 }
1698 }
1699 }
1700 op->ReleaseHandle();
1701 }
1702 if (!keep_handle) {
1703 curl_multi_remove_handle(multi_handle, iter->first);
1704 if (res != CURLE_OK) {
1705 curl_easy_cleanup(iter->first);
1706 }
1707 for (auto &req : broker_reqs) {
1708 if (req.second.curl == iter->first) {
1709 m_logger->Warning(
kLogXrdClHttp,
"Curl handle finished while a broker operation was outstanding");
1710 m_conncall_errors.fetch_add(1, std::memory_order_relaxed);
1711 }
1712 }
1713 m_op_map.erase(iter);
1714 running_handles -= 1;
1715 }
1716 }
1717 } while (msg);
1718 }
1719
1720 for (auto map_entry : m_op_map) {
1721 if (mres) {
1723 OpRecord(*map_entry.second.first, OpKind::Error);
1724 }
1725 if (multi_handle && map_entry.first) curl_multi_remove_handle(multi_handle, map_entry.first);
1726 }
1727
1728 m_queue->ReleaseHandles();
1729 curl_multi_cleanup(multi_handle);
1730}
std::pair< uint16_t, uint32_t > CurlCodeConvert(CURLcode res)
int emsg(int rc, char *msg)
static void CleanupDnsCache()
std::shared_ptr< CurlOperation > GetOperation() const
CURL * GetParentCurlHandle() const
void ReleaseHandle() override
void Fail(uint16_t errCode, uint32_t errNum, const std::string &) override
static std::string_view GetUrlKey(const std::string &url, std::string &modified_url)
bool GetInt(const std::string &key, int &value)
std::pair< uint16_t, uint32_t > HTTPStatusConvert(unsigned status)
bool HTTPStatusIsError(unsigned status)
const uint64_t kLogXrdClHttp
const uint16_t errErrorResponse
const uint16_t errOperationExpired
const uint16_t errInternal
Internal error.
const uint16_t errConnectionError