XRootD
Loading...
Searching...
No Matches
XrdClHttp::CurlWorker Class Reference

#include <XrdClHttpWorker.hh>

Collaboration diagram for XrdClHttp::CurlWorker:

Public Member Functions

 CurlWorker (const CurlWorker &)=delete
 CurlWorker (std::shared_ptr< HandlerQueue > queue, VerbsCache &cache, XrdCl::Log *logger)
std::tuple< std::string, std::string > ClientX509CertKeyFile () const
void Run ()
void Start (std::unique_ptr< XrdClHttp::CurlWorker > self, std::thread tid)

Static Public Member Functions

static std::string GetMonitoringJson ()
static void RunStatic (CurlWorker *myself)
static void SetMaintenancePeriod (unsigned maint)

Detailed Description

Definition at line 51 of file XrdClHttpWorker.hh.

Constructor & Destructor Documentation

◆ CurlWorker() [1/2]

CurlWorker::CurlWorker ( std::shared_ptr< HandlerQueue > queue,
VerbsCache & cache,
XrdCl::Log * logger )

Definition at line 887 of file XrdClHttpUtil.cc.

887 :
888 m_cache(cache),
889 m_queue(queue),
890 m_logger(logger)
891{
892 {
893 std::unique_lock lk(m_worker_stats_mutex);
894 m_stats_offset = m_workers_last_completed_cycle.size();
895 m_workers_last_completed_cycle.push_back(&m_last_completed_cycle);
896 m_workers_oldest_op.push_back(&m_oldest_op);
897 }
898 int pipeInfo[2];
899 if ((pipe(pipeInfo) == -1) || (fcntl(pipeInfo[0], F_SETFD, FD_CLOEXEC)) || (fcntl(pipeInfo[1], F_SETFD, FD_CLOEXEC))) {
900 throw std::runtime_error("Failed to create shutdown monitoring pipe for curl worker");
901 }
902 m_shutdown_pipe_r = pipeInfo[0];
903 m_shutdown_pipe_w = pipeInfo[1];
904
905 // Handle setup of the X509 authentication
906 auto env = XrdCl::DefaultEnv::GetEnv();
907 env->GetString("HttpClientCertFile", m_x509_client_cert_file);
908 env->GetString("HttpClientKeyFile", m_x509_client_key_file);
909}
static Env * GetEnv()
Get default client environment.

References XrdCl::DefaultEnv::GetEnv().

Referenced by CurlWorker(), and RunStatic().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ CurlWorker() [2/2]

XrdClHttp::CurlWorker::CurlWorker ( const CurlWorker & )
delete

References CurlWorker().

Here is the call graph for this function:

Member Function Documentation

◆ ClientX509CertKeyFile()

std::tuple< std::string, std::string > CurlWorker::ClientX509CertKeyFile ( ) const

Definition at line 911 of file XrdClHttpUtil.cc.

912{
913 return std::make_tuple(m_x509_client_cert_file, m_x509_client_key_file);
914}

Referenced by XrdClHttp::CurlOperation::Setup().

Here is the caller graph for this function:

◆ GetMonitoringJson()

std::string CurlWorker::GetMonitoringJson ( )
static

Definition at line 917 of file XrdClHttpUtil.cc.

918{
919 auto now = std::chrono::system_clock::now().time_since_epoch().count();
920 auto oldest_op = now;
921 auto oldest_cycle = now;
922 {
923 std::unique_lock lk(m_worker_stats_mutex);
924 for (const auto &entry : m_workers_last_completed_cycle) {
925 if (!entry) {continue;}
926 auto cycle = entry->load(std::memory_order_relaxed);
927 if (cycle < oldest_cycle) oldest_cycle = cycle;
928 }
929 for (const auto &entry : m_workers_oldest_op) {
930 if (!entry) {continue;}
931 auto op = entry->load(std::memory_order_relaxed);
932 if (op < oldest_op) oldest_op = op;
933 }
934 }
935 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();
936 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();
937 std::string retval = "{"
938 "\"oldest_op\":" + std::to_string(oldest_op_dbl) + ","
939 "\"oldest_cycle\":" + std::to_string(oldest_cycle_dbl) + ","
940 ;
941
942 for (size_t verb_idx = 0; verb_idx < static_cast<int>(XrdClHttp::CurlOperation::HttpVerb::Count); verb_idx++) {
943 const auto &verb_str = XrdClHttp::CurlOperation::GetVerbString(static_cast<XrdClHttp::CurlOperation::HttpVerb>(verb_idx));
944 for (size_t op_idx = 0; op_idx < 402; op_idx++) {
945 if (op_idx == 401) continue;
946
947 auto &op_stats = m_ops[verb_idx][op_idx];
948 auto duration = op_stats.m_duration.load(std::memory_order_relaxed);
949 if (duration == 0) continue;
950
951 std::string prefix = "http_" + verb_str + "_" + ((op_idx == 402) ? "invalid" : std::to_string(200 + op_idx)) + "_";
952
953 auto duration_dbl = std::chrono::duration<double>(std::chrono::steady_clock::duration(duration)).count();
954 retval += "\"" + prefix + "duration\":" + std::to_string(duration_dbl) + ",";
955
956 duration = op_stats.m_pause_duration.load(std::memory_order_relaxed);
957 if (duration > 0) {
958 duration_dbl = std::chrono::duration<double>(std::chrono::steady_clock::duration(duration)).count();
959 retval += "\"" + prefix + "pause_duration\":" + std::to_string(duration_dbl) + ",";
960 }
961
962 auto count = op_stats.m_bytes.load(std::memory_order_relaxed);
963 if (count) retval += "\"" + prefix + "bytes\":" + std::to_string(count) + ",";
964 count = op_stats.m_error.load(std::memory_order_relaxed);
965 if (count) retval += "\"" + prefix + "error\":" + std::to_string(count) + ",";
966 count = op_stats.m_finished.load(std::memory_order_relaxed);
967 if (count) retval += "\"" + prefix + "finished\":" + std::to_string(count) + ",";
968 count = op_stats.m_client_timeout.load(std::memory_order_relaxed);
969 if (count) retval += "\"" + prefix + "client_timeout\":" + std::to_string(count) + ",";
970 count = op_stats.m_server_timeout.load(std::memory_order_relaxed);
971 if (count) retval += "\"" + prefix + "server_timeout\":" + std::to_string(count) + ",";
972 }
973 {
974 auto &op_stats = m_ops[verb_idx][401];
975 auto duration = op_stats.m_duration.load(std::memory_order_relaxed);
976 if (duration == 0) continue;
977
978 std::string prefix = "http_" + verb_str + "_";
979
980 auto duration_dbl = std::chrono::duration<double>(std::chrono::steady_clock::duration(duration)).count();
981 retval += "\"" + prefix + "preheader_duration\":" + std::to_string(duration_dbl) + ",";
982
983 auto count = op_stats.m_started.load(std::memory_order_relaxed);
984 if (count) retval += "\"" + prefix + "started\":" + std::to_string(count) + ",";
985 count = op_stats.m_error.load(std::memory_order_relaxed);
986 if (count) retval += "\"" + prefix + "preheader_error\":" + std::to_string(count) + ",";
987 count = op_stats.m_finished.load(std::memory_order_relaxed);
988 if (count) retval += "\"" + prefix + "preheader_finished\":" + std::to_string(count) + ",";
989 count = op_stats.m_server_timeout.load(std::memory_order_relaxed);
990 if (count) retval += "\"" + prefix + "preheader_timeout\":" + std::to_string(count) + ",";
991 count = op_stats.m_conncall_timeout.load(std::memory_order_relaxed);
992 if (count) retval += "\"" + prefix + "conncall_timeout\":" + std::to_string(count) + ",";
993 }
994 }
995
996 retval +=
997 "\"conncall_error\":" + std::to_string(m_conncall_errors.load(std::memory_order_relaxed)) + ","
998 "\"conncall_started\":" + std::to_string(m_conncall_req.load(std::memory_order_relaxed)) + ","
999 "\"conncall_success\":" + std::to_string(m_conncall_success.load(std::memory_order_relaxed)) + ","
1000 "\"conncall_timeout\":" + std::to_string(m_conncall_timeout.load(std::memory_order_relaxed)) +
1001 "}";
1002
1003 return retval;
1004}
static const std::string GetVerbString(HttpVerb)

References XrdClHttp::CurlOperation::Count, and XrdClHttp::CurlOperation::GetVerbString().

Here is the call graph for this function:

◆ Run()

void CurlWorker::Run ( )

Definition at line 1086 of file XrdClHttpUtil.cc.

1086 {
1087 int max_pending = 50;
1088 XrdCl::DefaultEnv::GetEnv()->GetInt("HttpMaxPendingOps", max_pending);
1089 m_continue_queue.reset(new HandlerQueue(max_pending));
1090 auto &queue = *m_queue.get();
1091 m_logger->Debug(kLogXrdClHttp, "Started a curl worker");
1092
1093 CURLM *multi_handle = curl_multi_init();
1094 if (multi_handle == nullptr) {
1095 throw std::runtime_error("Failed to create curl multi-handle");
1096 }
1097
1098 int running_handles = 0;
1099 time_t last_maintenance = time(NULL);
1100 CURLMcode mres = CURLM_OK;
1101
1102 // Map from a file descriptor that has an outstanding broker request
1103 // to the corresponding CURL handle.
1104 std::unordered_map<int, WaitingForBroker> broker_reqs;
1105 std::vector<struct curl_waitfd> waitfds;
1106
1107 bool want_shutdown = false;
1108 while (!want_shutdown) {
1109 m_last_completed_cycle.store(std::chrono::system_clock::now().time_since_epoch().count());
1110 auto oldest_op = std::chrono::system_clock::now();
1111 for (const auto &entry : m_op_map) {
1112 OpRecord(*entry.second.first, OpKind::Update);
1113 if (entry.second.second < oldest_op) {
1114 oldest_op = entry.second.second;
1115 }
1116 }
1117 m_oldest_op.store(oldest_op.time_since_epoch().count());
1118
1119 // Try continuing any available handles that have more data
1120 while (true) {
1121 auto op = m_continue_queue->TryConsume();
1122 if (!op) {
1123 break;
1124 }
1125 // Avoid race condition where external thread added a continue operation to queue
1126 // while the curl worker thread failed the transfer.
1127 if (op->IsDone()) {
1128 m_logger->Debug(kLogXrdClHttp, "Ignoring continuation of operation that has already completed");
1129 continue;
1130 }
1131 m_logger->Debug(kLogXrdClHttp, "Continuing the curl handle from op %p on thread %d", op.get(), getthreadid());
1132 auto curl = op->GetCurlHandle();
1133 if (!op->ContinueHandle()) {
1134 op->Fail(XrdCl::errInternal, 0, "Failed to continue the curl handle for the operation");
1135 OpRecord(*op, OpKind::Error);
1136 op->ReleaseHandle();
1137 if (curl) {
1138 curl_multi_remove_handle(multi_handle, curl);
1139 curl_easy_cleanup(curl);
1140 m_op_map.erase(curl);
1141 }
1142 running_handles -= 1;
1143 continue;
1144 } else {
1145 auto iter = m_op_map.find(curl);
1146 if (iter != m_op_map.end()) iter->second.second = std::chrono::system_clock::now();
1147 }
1148 }
1149 // Consume from the shared new operation queue
1150 while (running_handles < static_cast<int>(m_max_ops)) {
1151 auto op = running_handles == 0 ? queue.Consume(std::chrono::seconds(1)) : queue.TryConsume();
1152 if (!op) {
1153 break;
1154 }
1155 auto curl = queue.GetHandle();
1156 if (curl == nullptr) {
1157 m_logger->Debug(kLogXrdClHttp, "Unable to allocate a curl handle");
1158 op->Fail(XrdCl::errInternal, ENOMEM, "Unable to get allocate a curl handle");
1159 continue;
1160 }
1161 try {
1162 auto rv = op->Setup(curl, *this);
1163 if (!rv) {
1164 m_logger->Debug(kLogXrdClHttp, "Failed to setup the curl handle");
1165 op->Fail(XrdCl::errInternal, ENOMEM, "Failed to setup the curl handle for the operation");
1166 continue;
1167 }
1168 if (!op->FinishSetup(curl)) {
1169 m_logger->Debug(kLogXrdClHttp, "Failed to finish setup of the curl handle");
1170 op->Fail(XrdCl::errInternal, ENOMEM, "Failed to finish setup of the curl handle for the operation");
1171 continue;
1172 }
1173 } catch (...) {
1174 m_logger->Debug(kLogXrdClHttp, "Unable to setup the curl handle");
1175 op->Fail(XrdCl::errInternal, ENOMEM, "Failed to setup the curl handle for the operation");
1176 continue;
1177 }
1178 op->SetContinueQueue(m_continue_queue);
1179
1180 if (op->IsDone()) {
1181 continue;
1182 }
1183 m_op_map[curl] = {op, std::chrono::system_clock::now()};
1184
1185 // If the operation requires the result of the OPTIONS verb to function, then
1186 // we add that to the multi-handle instead, chaining the two calls together.
1187 if (op->RequiresOptions()) {
1188 std::string modified_url;
1189 std::shared_ptr<CurlOptionsOp> options_op(
1190 new CurlOptionsOp(
1191 curl, op,
1192 std::string(
1193 VerbsCache::GetUrlKey(op->GetUrl(), modified_url)
1194 ),
1195 m_logger, op->GetConnCalloutFunc()
1196 )
1197 );
1198 // Note this `curl` variable is not local to the conditional; it is the curl handle of the
1199 // CurlOptionsOp and will be added below to the multi-handle, causing it - not the parent's
1200 // curl handle - to be executed.
1201 curl = queue.GetHandle();
1202 if (curl == nullptr) {
1203 m_logger->Debug(kLogXrdClHttp, "Unable to allocate a curl handle");
1204 op->Fail(XrdCl::errInternal, ENOMEM, "Unable to get allocate a curl handle");
1205 OpRecord(*op, OpKind::Error);
1206 continue;
1207 }
1208 auto rv = options_op->Setup(curl, *this);
1209 if (!rv) {
1210 m_logger->Debug(kLogXrdClHttp, "Failed to allocate a curl handle for OPTIONS");
1211 continue;
1212 }
1213 m_op_map[curl] = {options_op, std::chrono::system_clock::now()};
1214 OpRecord(*options_op, OpKind::Start);
1215 running_handles += 1;
1216 } else {
1217 OpRecord(*op, OpKind::Start);
1218 }
1219
1220 auto mres = curl_multi_add_handle(multi_handle, curl);
1221 if (mres != CURLM_OK) {
1222 m_logger->Debug(kLogXrdClHttp, "Unable to add operation to the curl multi-handle");
1223 op->Fail(XrdCl::errInternal, mres, "Unable to add operation to the curl multi-handle");
1224 OpRecord(*op, OpKind::Error);
1225 continue;
1226 }
1227 m_logger->Debug(kLogXrdClHttp, "Added request for URL %s to worker thread for processing", op->GetUrl().c_str());
1228 running_handles += 1;
1229 }
1230
1231 // Maintain the periodic reporting of thread activity and fail any operations
1232 // that have expired / timed out.
1233 time_t now = time(NULL);
1234 time_t next_maintenance = last_maintenance + m_maintenance_period.load(std::memory_order_relaxed);
1235 if (now >= next_maintenance) {
1236 m_queue->Expire();
1237 m_continue_queue->Expire();
1238 m_logger->Debug(kLogXrdClHttp, "Curl worker thread %d is running %d operations",
1239 getthreadid(), running_handles);
1240 last_maintenance = now;
1241
1242 // Timeout all the pending broker requests.
1243 std::vector<std::pair<int, CURL *>> expired_ops;
1244 for (const auto &entry : broker_reqs) {
1245 if (entry.second.expiry < now) {
1246 expired_ops.emplace_back(entry.first, entry.second.curl);
1247 }
1248 }
1249 for (const auto &entry : expired_ops) {
1250 auto iter = m_op_map.find(entry.second);
1251 if (iter == m_op_map.end()) {
1252 m_logger->Warning(kLogXrdClHttp, "Found an expired curl handle with no corresponding operation!");
1253 } else {
1254
1255 CurlOptionsOp *options_op = nullptr;
1256 if ((options_op = dynamic_cast<CurlOptionsOp*>(iter->second.first.get())) != nullptr) {
1257 auto parent_op = options_op->GetOperation();
1258 bool parent_op_failed = false;
1259 if (parent_op->IsRedirect()) {
1260 std::string target;
1261 if (parent_op->Redirect(target) == CurlOperation::RedirectAction::Fail) {
1262 auto iter = m_op_map.find(options_op->GetParentCurlHandle());
1263 if (iter != m_op_map.end()) {
1264 OpRecord(*iter->second.first, OpKind::Error);
1265 iter->second.first->Fail(XrdCl::errErrorResponse, 0, "Failed to send OPTIONS to redirect target");
1266 m_op_map.erase(iter);
1267 running_handles -= 1;
1268 }
1269 parent_op_failed = true;
1270 } else {
1271 OpRecord(*parent_op, OpKind::Start);
1272 }
1273 } else {
1274 OpRecord(*parent_op, OpKind::Start);
1275 }
1276 if (!parent_op_failed){
1277 curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
1278 }
1279 }
1280
1281 iter->second.first->Fail(XrdCl::errConnectionError, 1, "Timeout: connection never provided for request");
1282 iter->second.first->ReleaseHandle();
1283 OpRecord(*(iter->second.first), OpKind::ConncallTimeout);
1284 m_op_map.erase(entry.second);
1285 curl_easy_cleanup(entry.second);
1286 running_handles -= 1;
1287 }
1288 broker_reqs.erase(entry.first);
1289 m_conncall_timeout.fetch_add(1, std::memory_order_relaxed);
1290 }
1291
1292 // Cleanup the fake connection cache entries.
1294 }
1295
1296 waitfds.clear();
1297 waitfds.resize(3 + broker_reqs.size());
1298
1299 waitfds[0].fd = queue.PollFD();
1300 waitfds[0].events = CURL_WAIT_POLLIN;
1301 waitfds[0].revents = 0;
1302 waitfds[1].fd = m_continue_queue->PollFD();
1303 waitfds[1].events = CURL_WAIT_POLLIN;
1304 waitfds[1].revents = 0;
1305 waitfds[2].fd = m_shutdown_pipe_r;
1306 waitfds[2].revents = 0;
1307 waitfds[2].events = CURL_WAIT_POLLIN | CURL_WAIT_POLLPRI;
1308
1309 int idx = 3;
1310 for (const auto &entry : broker_reqs) {
1311 waitfds[idx].fd = entry.first;
1312 waitfds[idx].events = CURL_WAIT_POLLIN|CURL_WAIT_POLLPRI;
1313 waitfds[idx].revents = 0;
1314 idx += 1;
1315 }
1316
1317 long timeo;
1318 curl_multi_timeout(multi_handle, &timeo);
1319 // These commented-out lines are purposely left; will need to revisit after the 0.9.1 release;
1320 // for now, they are too verbose on RHEL7.
1321 //m_logger->Debug(kLogXrdClHttp, "Curl advises a timeout of %ld ms", timeo);
1322 if (running_handles && timeo == -1) {
1323 // Bug workaround: we've seen RHEL7 libcurl have a race condition where it'll not
1324 // set a timeout while doing the DNS lookup; assume that if there are running handles
1325 // but no timeout, we've hit this bug.
1326 //m_logger->Debug(kLogXrdClHttp, "Will sleep for up to 50ms");
1327 mres = curl_multi_wait(multi_handle, &waitfds[0], waitfds.size(), 50, nullptr);
1328 } else {
1329 //m_logger->Debug(kLogXrdClHttp, "Will sleep for up to %d seconds", max_sleep_time);
1330 //mres = curl_multi_wait(multi_handle, &waitfds[0], waitfds.size(), max_sleep_time*1000, nullptr);
1331 // Temporary test: we've been seeing DNS lookups timeout on additional platforms. Switch to always
1332 // poll as curl_multi_wait doesn't seem to get notified when DNS lookups are done.
1333 mres = curl_multi_wait(multi_handle, &waitfds[0], waitfds.size(), 50, nullptr);
1334 }
1335 if (mres != CURLM_OK) {
1336 m_logger->Warning(kLogXrdClHttp, "Failed to wait on multi-handle: %d", mres);
1337 }
1338
1339 // Iterate through the waiting broker callbacks.
1340 for (const auto &entry : waitfds) {
1341 // Ignore the queue's poll fd.
1342 if (waitfds[0].fd == entry.fd || waitfds[1].fd == entry.fd) {
1343 continue;
1344 }
1345 // Handle shutdown requests
1346 if ((waitfds[2].fd == entry.fd) && entry.revents) {
1347 want_shutdown = true;
1348 break;
1349 }
1350 if ((entry.revents & CURL_WAIT_POLLIN) != CURL_WAIT_POLLIN) {
1351 continue;
1352 }
1353 auto handle = broker_reqs[entry.fd].curl;
1354 auto iter = m_op_map.find(handle);
1355 if (iter == m_op_map.end()) {
1356 m_logger->Warning(kLogXrdClHttp, "Internal error: broker responded on FD %d but no corresponding curl operation", entry.fd);
1357 broker_reqs.erase(entry.fd);
1358 m_conncall_errors.fetch_add(1, std::memory_order_relaxed);
1359 continue;
1360 }
1361 std::string err;
1362 auto result = iter->second.first->WaitSocketCallback(err);
1363 if (result == -1) {
1364 m_logger->Warning(kLogXrdClHttp, "Error when invoking the broker callback: %s", err.c_str());
1365
1366 CurlOptionsOp *options_op = nullptr;
1367 if ((options_op = dynamic_cast<CurlOptionsOp*>(iter->second.first.get())) != nullptr) {
1368 auto parent_op = options_op->GetOperation();
1369 bool parent_op_failed = false;
1370 if (parent_op->IsRedirect()) {
1371 std::string target;
1372 if (parent_op->Redirect(target) == CurlOperation::RedirectAction::Fail) {
1373 auto iter = m_op_map.find(options_op->GetParentCurlHandle());
1374 if (iter != m_op_map.end()) {
1375 OpRecord(*iter->second.first, OpKind::Error);
1376 iter->second.first->Fail(XrdCl::errErrorResponse, 0, "Failed to send OPTIONS to redirect target");
1377 m_op_map.erase(iter);
1378 running_handles -= 1;
1379 }
1380 parent_op_failed = true;
1381 } else {
1382 OpRecord(*parent_op, OpKind::Start);
1383 }
1384 } else {
1385 OpRecord(*parent_op, OpKind::Start);
1386 }
1387 if (!parent_op_failed){
1388 curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
1389 }
1390 }
1391
1392 iter->second.first->Fail(XrdCl::errErrorResponse, 1, err);
1393 OpRecord(*iter->second.first, OpKind::Error);
1394 m_op_map.erase(handle);
1395 broker_reqs.erase(entry.fd);
1396 m_conncall_errors.fetch_add(1, std::memory_order_relaxed);
1397 running_handles -= 1;
1398 } else {
1399 broker_reqs.erase(entry.fd);
1400 curl_multi_add_handle(multi_handle, handle);
1401 m_conncall_success.fetch_add(1, std::memory_order_relaxed);
1402 }
1403 }
1404
1405 // Do maintenance on the multi-handle
1406 int still_running;
1407 auto mres = curl_multi_perform(multi_handle, &still_running);
1408 if (mres == CURLM_CALL_MULTI_PERFORM) {
1409 continue;
1410 } else if (mres != CURLM_OK) {
1411 m_logger->Warning(kLogXrdClHttp, "Failed to perform multi-handle operation: %d", mres);
1412 break;
1413 }
1414
1415 CURLMsg *msg;
1416 do {
1417 int msgq = 0;
1418 msg = curl_multi_info_read(multi_handle, &msgq);
1419 if (msg && (msg->msg == CURLMSG_DONE)) {
1420 if (!msg->easy_handle) {
1421 m_logger->Warning(kLogXrdClHttp, "Logic error: got a callback for a null handle");
1422 mres = CURLM_BAD_EASY_HANDLE;
1423 break;
1424 }
1425 auto iter = m_op_map.find(msg->easy_handle);
1426 if (iter == m_op_map.end()) {
1427 m_logger->Error(kLogXrdClHttp, "Logic error: got a callback for an entry that doesn't exist");
1428 mres = CURLM_BAD_EASY_HANDLE;
1429 break;
1430 }
1431 auto op = iter->second.first;
1432 auto res = msg->data.result;
1433 bool keep_handle = false;
1434 bool waiting_on_callout = false;
1435 if (res == CURLE_OK) {
1436 auto sc = op->GetStatusCode();
1437 OpRecord(*op, OpKind::Finish);
1438 if (HTTPStatusIsError(sc)) {
1439 auto httpErr = HTTPStatusConvert(sc);
1440 op->Fail(httpErr.first, httpErr.second, op->GetStatusMessage());
1441 op->ReleaseHandle();
1442 // If this was a failed CurlOptionsOp, then we re-activate the parent handle.
1443 // If the parent handle was stopped at a redirect that now returns failure, then
1444 // we'll clean it up.
1445 CurlOptionsOp *options_op = nullptr;
1446 if ((options_op = dynamic_cast<CurlOptionsOp*>(op.get())) != nullptr) {
1447 auto parent_op = options_op->GetOperation();
1448 bool parent_op_failed = false;
1449 if (parent_op->IsRedirect()) {
1450 std::string target;
1451 if (parent_op->Redirect(target) == CurlOperation::RedirectAction::Fail) {
1452 OpRecord(*parent_op, OpKind::Error);
1453 m_op_map.erase(options_op->GetParentCurlHandle());
1454 running_handles -= 1;
1455 parent_op_failed = true;
1456 } else {
1457 OpRecord(*parent_op, OpKind::Start);
1458 }
1459 } else {
1460 OpRecord(*parent_op, OpKind::Start);
1461 }
1462 // Have curl execute the parent operation
1463 if (!parent_op_failed) {
1464 curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
1465 }
1466 }
1467 // The curl operation was successful, it's just the HTTP request failed; recycle the handle.
1468 queue.RecycleHandle(iter->first);
1469 } else {
1470 CurlOptionsOp *options_op = nullptr;
1471 // If this was a successful OPTIONS op, invoke the parent operation.
1472 if ((options_op = dynamic_cast<CurlOptionsOp*>(op.get()))) {
1473 options_op->Success();
1474 options_op->ReleaseHandle();
1475 // Note: op is scoped external to the conditional block
1476 op = options_op->GetOperation();
1477 op->OptionsDone();
1478 OpRecord(*op, OpKind::Start);
1479 curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
1480 curl_multi_remove_handle(multi_handle, iter->first);
1481 queue.RecycleHandle(iter->first);
1482 }
1483 // Check to see if the operation ended in a redirect (note: this might)
1484 // be invoked a second time if this was the parent operation of an OPTIONS
1485 // op.
1486 if (op->IsRedirect()) {
1487 std::string target;
1488 switch (op->Redirect(target)) {
1490 if (options_op) {
1491 // In this case, we failed immediately after an OPTIONS finished.
1492 // Since there's a Start recorded after the OPTIONS processing, we
1493 // must record an error.
1494 // In the non-OPTIONS case, we never recorded a second start and
1495 // don't need a matching failure.
1496 OpRecord(*op, OpKind::Error);
1497 }
1498 keep_handle = false;
1499 break;
1501 if (!options_op) {
1502 // In this case, the redirect occurred without any prior
1503 // OPTIONS call. This implies that `op` is the original call
1504 // and we need to restart it later and record another op start.
1505 keep_handle = true;
1506 OpRecord(*op, OpKind::Start);
1507 }
1508 break;
1510 {
1511 // The redirect resulted in a new endpoint where the cache lookup failed;
1512 // we need to know what HTTP verbs are in the server's Allow list before this
1513 // operation can continue. Inject a new CurlOptionsOp and chain it to the one
1514 // being processed. Once the OPTIONS request is done, then we'll restart this
1515 // operation.
1516 std::string modified_url;
1517 target = VerbsCache::GetUrlKey(target, modified_url);
1518 options_op = new CurlOptionsOp(iter->first, op, target, m_logger, op->GetConnCalloutFunc());
1519 std::shared_ptr<CurlOperation> new_op(options_op);
1520 auto curl = queue.GetHandle();
1521 if (curl == nullptr) {
1522 m_logger->Debug(kLogXrdClHttp, "Unable to allocate a curl handle");
1523 op->Fail(XrdCl::errInternal, ENOMEM, "Unable to get allocate a curl handle");
1524 keep_handle = false;
1525 options_op = nullptr;
1526 break;
1527 }
1528 OpRecord(*new_op, OpKind::Start);
1529 try {
1530 auto rv = new_op->Setup(curl, *this);
1531 if (!rv) {
1532 m_logger->Debug(kLogXrdClHttp, "Unable to configure a curl handle for OPTIONS");
1533 keep_handle = false;
1534 options_op = nullptr;
1535 break;
1536 }
1537 } catch (...) {
1538 m_logger->Debug(kLogXrdClHttp, "Unable to setup the curl handle for the OPTIONS operation");
1539 new_op->Fail(XrdCl::errInternal, ENOMEM, "Failed to setup the curl handle for the OPTIONS operation");
1540 OpRecord(*new_op, OpKind::Error);
1541 keep_handle = false;
1542 break;
1543 }
1544 new_op->SetContinueQueue(m_continue_queue);
1545 m_op_map[curl] = {new_op, std::chrono::system_clock::now()};
1546 auto mres = curl_multi_add_handle(multi_handle, curl);
1547 if (mres != CURLM_OK) {
1548 m_logger->Debug(kLogXrdClHttp, "Unable to add OPTIONS operation to the curl multi-handle: %s", curl_multi_strerror(mres));
1549 op->Fail(XrdCl::errInternal, mres, "Unable to add OPTIONS operation to the curl multi-handle");
1550 OpRecord(*new_op, OpKind::Error);
1551 break;
1552 }
1553 running_handles += 1;
1554 m_logger->Debug(kLogXrdClHttp, "Invoking the OPTIONS operation before redirect to %s", target.c_str());
1555 // The original curl operation needs to be kept around. Note that because options_op
1556 // is non-nil, we won't re-add the handle to the multi-handle.
1557 keep_handle = true;
1558 }
1559 }
1560 int callout_socket = op->WaitSocket();
1561 if ((waiting_on_callout = callout_socket >= 0)) {
1562 auto expiry = time(nullptr) + 20;
1563 m_logger->Debug(kLogXrdClHttp, "Creating a callout wait request on socket %d", callout_socket);
1564 broker_reqs[callout_socket] = {iter->first, expiry};
1565 m_conncall_req.fetch_add(1, std::memory_order_relaxed);
1566 }
1567 } else if (options_op) {
1568 // In this case, the OPTIONS call happened before the parent operation was started.
1569 curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
1570 }
1571 if (keep_handle) {
1572 curl_multi_remove_handle(multi_handle, iter->first);
1573 if (!waiting_on_callout && !options_op) {
1574 curl_multi_add_handle(multi_handle, iter->first);
1575 }
1576 } else if (!options_op) {
1577 op->Success();
1578 op->ReleaseHandle();
1579 // If the handle was successful, then we can recycle it.
1580 queue.RecycleHandle(iter->first);
1581 }
1582 }
1583 } else if (res == CURLE_COULDNT_CONNECT && op->UseConnectionCallout() && !op->GetTriedBoker()) {
1584 // In this case, we need to use the broker and the curl handle couldn't reuse
1585 // an existing socket.
1586 keep_handle = true;
1587 op->SetTriedBoker(); // Flag to ensure we try a connection only once per operation.
1588 std::string err;
1589 int wait_socket = -1;
1590 if (!op->StartConnectionCallout(err) || (wait_socket=op->WaitSocket()) == -1) {
1591 m_logger->Error(kLogXrdClHttp, "Failed to start broker-based connection: %s", err.c_str());
1592 op->ReleaseHandle();
1593 keep_handle = false;
1594 } else {
1595 curl_multi_remove_handle(multi_handle, iter->first);
1596 auto expiry = time(nullptr) + 20;
1597 m_logger->Debug(kLogXrdClHttp, "Curl operation requires a new TCP socket; waiting for callout to respond on socket %d", wait_socket);
1598 broker_reqs[wait_socket] = {iter->first, expiry};
1599 m_conncall_req.fetch_add(1, std::memory_order_relaxed);
1600 }
1601 } else {
1602 if (res == CURLE_ABORTED_BY_CALLBACK || res == CURLE_WRITE_ERROR) {
1603 // We cannot invoke the failure from within a callback as the curl thread and
1604 // original thread of execution may fight over the ownership of the handle memory.
1605 switch (op->GetError()) {
1607#ifdef HAVE_XPROTOCOL_TIMEREXPIRED
1608 op->Fail(XrdCl::errOperationExpired, 0, "Origin did not respond with headers within timeout");
1609#else
1610 op->Fail(XrdCl::errOperationExpired, 0, "Origin did not respond within timeout");
1611#endif
1612 OpRecord(*op, OpKind::Error);
1613 break;
1615 auto [ecode, emsg] = op->GetCallbackError();
1616 op->Fail(XrdCl::errErrorResponse, ecode, emsg);
1617 OpRecord(*op, OpKind::Error);
1618 break;
1619 }
1621 op->Fail(XrdCl::errOperationExpired, 0, "Operation timed out");
1622 OpRecord(*op, op->IsPaused() ? OpKind::ClientTimeout : OpKind::ServerTimeout);
1623 break;
1625 op->Fail(XrdCl::errOperationExpired, 0, "Transfer speed below minimum threshold");
1626 OpRecord(*op, OpKind::ServerTimeout);
1627 break;
1629 op->Fail(XrdCl::errOperationExpired, 0, "Transfer stalled for too long");
1630 OpRecord(*op, OpKind::ClientTimeout);
1631 break;
1633 op->Fail(XrdCl::errOperationExpired, 0, "Transfer stalled for too long");
1634 OpRecord(*op, OpKind::ServerTimeout);
1635 break;
1637 op->Fail(XrdCl::errInternal, 0, "Operation was aborted without recording an abort reason");
1638 OpRecord(*op, OpKind::Error);
1639 break;
1640 };
1641 CurlOptionsOp *options_op = nullptr;
1642 if ((options_op = dynamic_cast<CurlOptionsOp*>(op.get())) != nullptr) {
1643 auto parent_op = options_op->GetOperation();
1644 bool parent_op_failed = false;
1645 if (parent_op->IsRedirect()) {
1646 std::string target;
1647 if (parent_op->Redirect(target) == CurlOperation::RedirectAction::Fail) {
1648 auto iter = m_op_map.find(options_op->GetParentCurlHandle());
1649 if (iter != m_op_map.end()) {
1650 OpRecord(*iter->second.first, OpKind::Error);
1651 iter->second.first->Fail(XrdCl::errErrorResponse, 0, "Failed to send OPTIONS to redirect target");
1652 m_op_map.erase(iter);
1653 running_handles -= 1;
1654 }
1655 parent_op_failed = true;
1656 } else {
1657 OpRecord(*parent_op, OpKind::Start);
1658 }
1659 } else {
1660 OpRecord(*parent_op, OpKind::Start);
1661 }
1662 if (!parent_op_failed){
1663 curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
1664 }
1665 }
1666 } else {
1667 auto xrdCode = CurlCodeConvert(res);
1668 const auto curl_err = op->GetCurlErrorMessage();
1669 const char *curl_easy_err = curl_easy_strerror(res);
1670 const std::string fail_err = !curl_err.empty() ? curl_err : curl_easy_err;
1671 m_logger->Debug(kLogXrdClHttp, "Curl generated an error: %s (%d)", fail_err.c_str(), res);
1672 op->Fail(xrdCode.first, xrdCode.second, fail_err);
1673 OpRecord(*op, OpKind::Error);
1674 CurlOptionsOp *options_op = nullptr;
1675 if ((options_op = dynamic_cast<CurlOptionsOp*>(op.get())) != nullptr) {
1676 auto parent_op = options_op->GetOperation();
1677 bool parent_op_failed = false;
1678 if (parent_op->IsRedirect()) {
1679 std::string target;
1680 if (parent_op->Redirect(target) == CurlOperation::RedirectAction::Fail) {
1681 auto iter = m_op_map.find(options_op->GetParentCurlHandle());
1682 if (iter != m_op_map.end()) {
1683 OpRecord(*iter->second.first, OpKind::Error);
1684 iter->second.first->Fail(XrdCl::errErrorResponse, 0, "Failed to send OPTIONS to redirect target");
1685 m_op_map.erase(iter);
1686 running_handles -= 1;
1687 }
1688 parent_op_failed = true;
1689 }
1690 }
1691 if (!parent_op_failed){
1692 curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
1693 }
1694 }
1695 }
1696 op->ReleaseHandle();
1697 }
1698 if (!keep_handle) {
1699 curl_multi_remove_handle(multi_handle, iter->first);
1700 if (res != CURLE_OK) {
1701 curl_easy_cleanup(iter->first);
1702 }
1703 for (auto &req : broker_reqs) {
1704 if (req.second.curl == iter->first) {
1705 m_logger->Warning(kLogXrdClHttp, "Curl handle finished while a broker operation was outstanding");
1706 m_conncall_errors.fetch_add(1, std::memory_order_relaxed);
1707 }
1708 }
1709 m_op_map.erase(iter);
1710 running_handles -= 1;
1711 }
1712 }
1713 } while (msg);
1714 }
1715
1716 for (auto map_entry : m_op_map) {
1717 if (mres) {
1718 map_entry.second.first->Fail(XrdCl::errInternal, mres, curl_multi_strerror(mres));
1719 OpRecord(*map_entry.second.first, OpKind::Error);
1720 }
1721 if (multi_handle && map_entry.first) curl_multi_remove_handle(multi_handle, map_entry.first);
1722 }
1723
1724 m_queue->ReleaseHandles();
1725 curl_multi_cleanup(multi_handle);
1726}
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 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)
Definition XrdClEnv.cc:89
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

References XrdClHttp::CurlOperation::CleanupDnsCache(), XrdClHttp::CurlOperation::ContinueHandle(), CurlCodeConvert(), emsg(), XrdClHttp::CurlOperation::ErrCallback, XrdCl::errConnectionError, XrdCl::errErrorResponse, XrdClHttp::CurlOperation::ErrHeaderTimeout, XrdCl::errInternal, XrdClHttp::CurlOperation::ErrNone, XrdCl::errOperationExpired, XrdClHttp::CurlOperation::ErrOperationTimeout, XrdClHttp::CurlOperation::ErrTransferClientStall, XrdClHttp::CurlOperation::ErrTransferSlow, XrdClHttp::CurlOperation::ErrTransferStall, XrdClHttp::CurlOperation::Fail(), XrdClHttp::CurlOperation::Fail, XrdClHttp::CurlOptionsOp::Fail(), XrdClHttp::CurlOperation::FinishSetup(), XrdClHttp::CurlOperation::GetCallbackError(), XrdClHttp::CurlOperation::GetConnCalloutFunc(), XrdClHttp::CurlOperation::GetCurlErrorMessage(), XrdClHttp::CurlOperation::GetCurlHandle(), XrdCl::DefaultEnv::GetEnv(), XrdClHttp::CurlOperation::GetError(), XrdCl::Env::GetInt(), XrdClHttp::CurlOptionsOp::GetOperation(), XrdClHttp::CurlOptionsOp::GetParentCurlHandle(), XrdClHttp::CurlOperation::GetStatusCode(), XrdClHttp::CurlOperation::GetStatusMessage(), XrdClHttp::CurlOperation::GetTriedBoker(), XrdClHttp::CurlOperation::GetUrl(), XrdClHttp::VerbsCache::GetUrlKey(), XrdClHttp::HTTPStatusConvert(), XrdClHttp::HTTPStatusIsError(), XrdClHttp::CurlOperation::IsDone(), XrdClHttp::CurlOperation::IsPaused(), XrdClHttp::CurlOperation::IsRedirect(), XrdClHttp::kLogXrdClHttp, XrdClHttp::CurlOperation::OptionsDone(), XrdClHttp::CurlOperation::Redirect(), XrdClHttp::CurlOperation::Reinvoke, XrdClHttp::CurlOperation::ReinvokeAfterAllow, XrdClHttp::CurlOperation::ReleaseHandle(), XrdClHttp::CurlOptionsOp::ReleaseHandle(), XrdClHttp::CurlOperation::RequiresOptions(), XrdClHttp::CurlOperation::SetContinueQueue(), XrdClHttp::CurlOperation::SetTriedBoker(), XrdClHttp::CurlOperation::Setup(), XrdClHttp::CurlOperation::StartConnectionCallout(), XrdClHttp::CurlOperation::Success(), XrdClHttp::CurlOptionsOp::Success(), XrdClHttp::CurlOperation::UseConnectionCallout(), and XrdClHttp::CurlOperation::WaitSocket().

Referenced by RunStatic().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ RunStatic()

void CurlWorker::RunStatic ( CurlWorker * myself)
static

Definition at line 1067 of file XrdClHttpUtil.cc.

1068{
1069 {
1070 std::unique_lock lock(myself->m_start_lock);
1071 myself->m_start_complete_cv.wait(lock, [&]{return myself->m_start_complete;});
1072 }
1073 try {
1074 myself->Run();
1075 } catch (...) {
1076 myself->m_logger->Warning(kLogXrdClHttp, "Curl worker got an exception");
1077 {
1078 std::unique_lock lock(m_workers_mutex);
1079 auto iter = std::remove_if(m_workers.begin(), m_workers.end(), [&](std::unique_ptr<XrdClHttp::CurlWorker> &worker){return worker.get() == myself;});
1080 m_workers.erase(iter);
1081 }
1082 }
1083}
void Warning(uint64_t topic, const char *format,...)
Report a warning.
Definition XrdClLog.cc:248

References CurlWorker(), XrdClHttp::kLogXrdClHttp, Run(), and XrdCl::Log::Warning().

Here is the call graph for this function:

◆ SetMaintenancePeriod()

void XrdClHttp::CurlWorker::SetMaintenancePeriod ( unsigned maint)
inlinestatic

Definition at line 69 of file XrdClHttpWorker.hh.

69 {
70 m_maintenance_period.store(maint, std::memory_order_relaxed);
71 }

Referenced by XrdClHttp::File::SetProperty().

Here is the caller graph for this function:

◆ Start()

void CurlWorker::Start ( std::unique_ptr< XrdClHttp::CurlWorker > self,
std::thread tid )

Definition at line 1054 of file XrdClHttpUtil.cc.

1055{
1056 {
1057 std::unique_lock lock(m_workers_mutex);
1058 m_workers.emplace_back(std::move(self));
1059 m_self_tid = std::move(tid);
1060 }
1061 std::unique_lock lock(m_start_lock);
1062 m_start_complete = true;
1063 m_start_complete_cv.notify_one();
1064}

The documentation for this class was generated from the following files: