1239 {
1240 int max_pending = 50;
1242 m_continue_queue.reset(new HandlerQueue(max_pending));
1243 auto &queue = *m_queue.get();
1245
1246 CURLM *multi_handle = curl_multi_init();
1247 if (multi_handle == nullptr) {
1248 throw std::runtime_error("Failed to create curl multi-handle");
1249 }
1250
1251 int running_handles = 0;
1252 time_t last_maintenance = time(NULL);
1253 CURLMcode mres = CURLM_OK;
1254
1255
1256
1257 std::unordered_map<int, WaitingForBroker> broker_reqs;
1258 std::vector<struct curl_waitfd> waitfds;
1259
1260
1261
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);
1267 };
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);
1278 } else {
1279 ++req;
1280 }
1281 }
1282 };
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;
1287 try {
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");
1295 }
1296 } catch (...) {
1297 m_logger->Debug(
kLogXrdClHttp,
"Failed to setup the curl handle for OPTIONS");
1298 if (options_op) {
1300 "Failed to setup the curl handle for the OPTIONS operation");
1301 release_handle(options_op, options_handle);
1302 }
1304 "Failed to setup the curl handle for the OPTIONS operation");
1305 OpRecord(*parent_op, OpKind::Error);
1306 return false;
1307 }
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);
1320 return false;
1321 };
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);
1327 return false;
1328 }
1329
1330
1331 if (parent_op->IsRedirect()) {
1332 std::string target;
1333 auto action = parent_op->Redirect(target);
1335 OpRecord(*parent_op, OpKind::Error);
1336 remove_op(parent_handle);
1337 return false;
1338 }
1340 if (start_options(parent_handle, parent_op, target)) return true;
1341 remove_op(parent_handle);
1342 return false;
1343 }
1344 }
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);
1350 return true;
1351 }
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);
1361 return false;
1362 };
1363
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;
1373 }
1374 }
1375 m_oldest_op.store(oldest_op.time_since_epoch().count());
1376
1377
1378 while (true) {
1379 auto op = m_continue_queue->TryConsume();
1380 if (!op) {
1381 break;
1382 }
1383
1384
1385 if (op->IsDone()) {
1386 m_logger->Debug(
kLogXrdClHttp,
"Ignoring continuation of operation that has already completed");
1387 continue;
1388 }
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);
1394 remove_op(curl);
1395 continue;
1396 } else {
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();
1401 }
1402 }
1403 }
1404
1405 while (running_handles < static_cast<int>(m_max_ops)) {
1406 auto op = running_handles == 0 ? queue.Consume(std::chrono::seconds(1)) : queue.TryConsume();
1407 if (!op) {
1408 break;
1409 }
1410 auto curl = queue.GetHandle();
1411 if (curl == nullptr) {
1412 m_logger->Debug(
kLogXrdClHttp,
"Unable to allocate a curl handle");
1414 continue;
1415 }
1416 try {
1417 auto rv = op->Setup(curl, *this);
1418 if (!rv) {
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);
1422 continue;
1423 }
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);
1428 continue;
1429 }
1430 } catch (...) {
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);
1434 continue;
1435 }
1436 op->SetContinueQueue(m_continue_queue);
1437
1438 if (op->IsDone()) {
1439 op->ReleaseHandle();
1440 queue.RecycleHandle(curl);
1441 continue;
1442 }
1443 m_op_map[curl] = {op, std::chrono::system_clock::now()};
1444 running_handles += 1;
1445
1446
1447
1448 if (op->RequiresOptions()) {
1449 if (!start_options(curl, op, op->GetUrl())) {
1450 remove_op(curl);
1451 continue;
1452 }
1453 } else {
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);
1460 remove_op(curl);
1461 continue;
1462 }
1463 }
1464 m_logger->Debug(
kLogXrdClHttp,
"Added request for URL %s to worker thread for processing", op->GetUrl().c_str());
1465 }
1466
1467
1468
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) {
1472 m_queue->Expire();
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;
1477
1478
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);
1483 }
1484 }
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!");
1489 } else {
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);
1495 remove_op(handle);
1496 }
1497 broker_reqs.erase(fd);
1498 m_conncall_timeout.fetch_add(1, std::memory_order_relaxed);
1499 }
1500
1501
1503 }
1504
1505 waitfds.clear();
1506 waitfds.resize(3 + broker_reqs.size());
1507
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;
1517
1518 int idx = 3;
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;
1523 idx += 1;
1524 }
1525
1526 long timeo;
1527 curl_multi_timeout(multi_handle, &timeo);
1528
1529
1530
1531 if (running_handles && timeo == -1) {
1532
1533
1534
1535
1536 mres = curl_multi_wait(multi_handle, &waitfds[0], waitfds.size(), 50, nullptr);
1537 } else {
1538
1539
1540
1541
1542 mres = curl_multi_wait(multi_handle, &waitfds[0], waitfds.size(), 50, nullptr);
1543 }
1544 if (mres != CURLM_OK) {
1545 m_logger->Warning(
kLogXrdClHttp,
"Failed to wait on multi-handle: %d", mres);
1546 }
1547
1548
1549 for (const auto &entry : waitfds) {
1550
1551 if (waitfds[0].fd == entry.fd || waitfds[1].fd == entry.fd) {
1552 continue;
1553 }
1554
1555 if ((waitfds[2].fd == entry.fd) && entry.revents) {
1556 want_shutdown = true;
1557 break;
1558 }
1559 if ((entry.revents & CURL_WAIT_POLLIN) != CURL_WAIT_POLLIN) {
1560 continue;
1561 }
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);
1570 continue;
1571 }
1572 std::string err;
1573 auto [op, started_at] = iter->second;
1574 auto result = op->WaitSocketCallback(err);
1575 if (result == -1) {
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);
1581 remove_op(handle);
1582 broker_reqs.erase(entry.fd);
1583 m_conncall_errors.fetch_add(1, std::memory_order_relaxed);
1584 } else {
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);
1593 remove_op(handle);
1594 m_conncall_errors.fetch_add(1, std::memory_order_relaxed);
1595 continue;
1596 }
1597 m_conncall_success.fetch_add(1, std::memory_order_relaxed);
1598 }
1599 }
1600
1601
1602 int still_running;
1603 mres = curl_multi_perform(multi_handle, &still_running);
1604 if (mres == CURLM_CALL_MULTI_PERFORM) {
1605 mres = CURLM_OK;
1606 continue;
1607 } else if (mres != CURLM_OK) {
1608 m_logger->Warning(
kLogXrdClHttp,
"Failed to perform multi-handle operation: %d", mres);
1609 break;
1610 }
1611
1612 CURLMsg *msg;
1613 do {
1614 int msgq = 0;
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;
1620 break;
1621 }
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;
1627 break;
1628 }
1629 auto [op, started_at] = iter->second;
1630 auto res = msg->data.result;
1631
1632
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();
1644
1645
1646
1647 if (auto options_op = std::dynamic_pointer_cast<CurlOptionsOp>(op))
1648 reactivate_parent(options_op);
1649
1650 queue.RecycleHandle(easy_handle);
1651 } else {
1652
1653
1654
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();
1660 }
1661 reactivate_parent(options_op);
1662 queue.RecycleHandle(easy_handle);
1663 } else {
1664 if (op->IsRedirect()) {
1665 std::string target;
1666 switch (op->Redirect(target)) {
1668 keep_handle = false;
1669 break;
1671 keep_handle = true;
1672 OpRecord(*op, OpKind::Start);
1673 break;
1675 {
1676
1677
1678
1679
1680
1681 if (!start_options(easy_handle, op, target)) {
1682 keep_handle = false;
1683 break;
1684 }
1685 m_logger->Debug(
kLogXrdClHttp,
"Invoking the OPTIONS operation before redirect to %s", target.c_str());
1686
1687
1688 options_pending = true;
1689 keep_handle = true;
1690 }
1691 }
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);
1698 }
1699 }
1700 if (keep_handle) {
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;
1710 }
1711 }
1712 } else {
1713 if (!op->IsDone()) {
1714 op->Success();
1715 }
1716 if (op->IsDone()) {
1717 op->ReleaseHandle();
1718
1719 queue.RecycleHandle(easy_handle);
1720 } else {
1721
1722
1723
1724
1725 auto next_res = curl_multi_add_handle(multi_handle, easy_handle);
1726 if (next_res == CURLM_OK) {
1727 keep_handle = true;
1728 OpRecord(*op, OpKind::Start);
1729 } else {
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);
1735 }
1736 }
1737 }
1738 }
1739 }
1740 } else if (res == CURLE_COULDNT_CONNECT && op->UseConnectionCallout() && !op->GetTriedBoker()) {
1741
1742
1743 keep_handle = true;
1744 op->SetTriedBoker();
1745 std::string err;
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());
1749 if (!op->IsDone())
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;
1756 } else {
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);
1762 }
1763 } else {
1764 if (res == CURLE_ABORTED_BY_CALLBACK || res == CURLE_WRITE_ERROR) {
1765
1766
1767 switch (op->GetError()) {
1769#ifdef HAVE_XPROTOCOL_TIMEREXPIRED
1771#else
1773#endif
1774 OpRecord(*op, OpKind::Error);
1775 break;
1777 auto [ecode,
emsg] = op->GetCallbackError();
1779 OpRecord(*op, OpKind::Error);
1780 break;
1781 }
1784 OpRecord(*op, op->IsPaused() ? OpKind::ClientTimeout : OpKind::ServerTimeout);
1785 break;
1788 OpRecord(*op, OpKind::ServerTimeout);
1789 break;
1792 OpRecord(*op, OpKind::ClientTimeout);
1793 break;
1796 OpRecord(*op, OpKind::ServerTimeout);
1797 break;
1799 op->Fail(
XrdCl::errInternal, 0,
"Operation was aborted without recording an abort reason");
1800 OpRecord(*op, OpKind::Error);
1801 break;
1802 };
1803 if (auto options_op = std::dynamic_pointer_cast<CurlOptionsOp>(op))
1804 reactivate_parent(options_op);
1805 } else {
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);
1815 }
1816 op->ReleaseHandle();
1817 }
1818 if (!keep_handle) {
1819 curl_multi_remove_handle(multi_handle, easy_handle);
1820 if (res != CURLE_OK) {
1821 curl_easy_cleanup(easy_handle);
1822 }
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);
1828 } else {
1829 ++req;
1830 }
1831 }
1832 m_op_map.erase(easy_handle);
1833 running_handles -= 1;
1834 }
1835 }
1836 } while (msg);
1837 if (mres != CURLM_OK) break;
1838 }
1839
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);
1849 }
1850 release_handle(op, handle);
1851 }
1852 m_op_map.clear();
1853
1854 m_queue->ReleaseHandles();
1855 curl_multi_cleanup(multi_handle);
1856}
std::pair< uint16_t, uint32_t > CurlCodeConvert(CURLcode res)
int emsg(int rc, char *msg)
static void CleanupDnsCache()
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