XRootD
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 891 of file XrdClHttpUtil.cc.

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

References XrdCl::DefaultEnv::GetEnv().

+ Here is the call graph for this function:

◆ CurlWorker() [2/2]

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

Member Function Documentation

◆ ClientX509CertKeyFile()

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

Definition at line 915 of file XrdClHttpUtil.cc.

916 {
917  return std::make_tuple(m_x509_client_cert_file, m_x509_client_key_file);
918 }

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

+ Here is the caller graph for this function:

◆ GetMonitoringJson()

std::string CurlWorker::GetMonitoringJson ( )
static

Definition at line 921 of file XrdClHttpUtil.cc.

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

References XrdClHttp::CurlOperation::GetVerbString().

+ Here is the call graph for this function:

◆ Run()

void CurlWorker::Run ( )

Definition at line 1090 of file XrdClHttpUtil.cc.

1090  {
1091  int max_pending = 50;
1092  XrdCl::DefaultEnv::GetEnv()->GetInt("HttpMaxPendingOps", max_pending);
1093  m_continue_queue.reset(new HandlerQueue(max_pending));
1094  auto &queue = *m_queue.get();
1095  m_logger->Debug(kLogXrdClHttp, "Started a curl worker");
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  // Map from a file descriptor that has an outstanding broker request
1107  // to the corresponding CURL handle.
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  // Try continuing any available handles that have more data
1124  while (true) {
1125  auto op = m_continue_queue->TryConsume();
1126  if (!op) {
1127  break;
1128  }
1129  // Avoid race condition where external thread added a continue operation to queue
1130  // while the curl worker thread failed the transfer.
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  // Consume from the shared new operation queue
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");
1162  op->Fail(XrdCl::errInternal, ENOMEM, "Unable to get 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  // If the operation requires the result of the OPTIONS verb to function, then
1190  // we add that to the multi-handle instead, chaining the two calls together.
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(
1197  VerbsCache::GetUrlKey(op->GetUrl(), modified_url)
1198  ),
1199  m_logger, op->GetConnCalloutFunc()
1200  )
1201  );
1202  // Note this `curl` variable is not local to the conditional; it is the curl handle of the
1203  // CurlOptionsOp and will be added below to the multi-handle, causing it - not the parent's
1204  // curl handle - to be executed.
1205  curl = queue.GetHandle();
1206  if (curl == nullptr) {
1207  m_logger->Debug(kLogXrdClHttp, "Unable to allocate a curl handle");
1208  op->Fail(XrdCl::errInternal, ENOMEM, "Unable to get 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  // Maintain the periodic reporting of thread activity and fail any operations
1236  // that have expired / timed out.
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  // Timeout all the pending broker requests.
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) {
1261  auto parent_op = options_op->GetOperation();
1262  bool parent_op_failed = false;
1263  if (parent_op->IsRedirect()) {
1264  std::string target;
1265  if (parent_op->Redirect(target) == CurlOperation::RedirectAction::Fail) {
1266  auto iter = m_op_map.find(options_op->GetParentCurlHandle());
1267  if (iter != m_op_map.end()) {
1268  OpRecord(*iter->second.first, OpKind::Error);
1269  iter->second.first->Fail(XrdCl::errErrorResponse, 0, "Failed to send OPTIONS to redirect target");
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){
1281  curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
1282  }
1283  }
1284 
1285  iter->second.first->Fail(XrdCl::errConnectionError, 1, "Timeout: connection never provided for request");
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  // Cleanup the fake connection cache entries.
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  // These commented-out lines are purposely left; will need to revisit after the 0.9.1 release;
1324  // for now, they are too verbose on RHEL7.
1325  //m_logger->Debug(kLogXrdClHttp, "Curl advises a timeout of %ld ms", timeo);
1326  if (running_handles && timeo == -1) {
1327  // Bug workaround: we've seen RHEL7 libcurl have a race condition where it'll not
1328  // set a timeout while doing the DNS lookup; assume that if there are running handles
1329  // but no timeout, we've hit this bug.
1330  //m_logger->Debug(kLogXrdClHttp, "Will sleep for up to 50ms");
1331  mres = curl_multi_wait(multi_handle, &waitfds[0], waitfds.size(), 50, nullptr);
1332  } else {
1333  //m_logger->Debug(kLogXrdClHttp, "Will sleep for up to %d seconds", max_sleep_time);
1334  //mres = curl_multi_wait(multi_handle, &waitfds[0], waitfds.size(), max_sleep_time*1000, nullptr);
1335  // Temporary test: we've been seeing DNS lookups timeout on additional platforms. Switch to always
1336  // poll as curl_multi_wait doesn't seem to get notified when DNS lookups are done.
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  // Iterate through the waiting broker callbacks.
1344  for (const auto &entry : waitfds) {
1345  // Ignore the queue's poll fd.
1346  if (waitfds[0].fd == entry.fd || waitfds[1].fd == entry.fd) {
1347  continue;
1348  }
1349  // Handle shutdown requests
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) {
1372  auto parent_op = options_op->GetOperation();
1373  bool parent_op_failed = false;
1374  if (parent_op->IsRedirect()) {
1375  std::string target;
1376  if (parent_op->Redirect(target) == CurlOperation::RedirectAction::Fail) {
1377  auto iter = m_op_map.find(options_op->GetParentCurlHandle());
1378  if (iter != m_op_map.end()) {
1379  OpRecord(*iter->second.first, OpKind::Error);
1380  iter->second.first->Fail(XrdCl::errErrorResponse, 0, "Failed to send OPTIONS to redirect target");
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){
1392  curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
1393  }
1394  }
1395 
1396  iter->second.first->Fail(XrdCl::errErrorResponse, 1, err);
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  // Do maintenance on the multi-handle
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);
1442  if (HTTPStatusIsError(sc)) {
1443  auto httpErr = HTTPStatusConvert(sc);
1444  op->Fail(httpErr.first, httpErr.second, op->GetStatusMessage());
1445  op->ReleaseHandle();
1446  // If this was a failed CurlOptionsOp, then we re-activate the parent handle.
1447  // If the parent handle was stopped at a redirect that now returns failure, then
1448  // we'll clean it up.
1449  CurlOptionsOp *options_op = nullptr;
1450  if ((options_op = dynamic_cast<CurlOptionsOp*>(op.get())) != nullptr) {
1451  auto parent_op = options_op->GetOperation();
1452  bool parent_op_failed = false;
1453  if (parent_op->IsRedirect()) {
1454  std::string target;
1455  if (parent_op->Redirect(target) == CurlOperation::RedirectAction::Fail) {
1456  OpRecord(*parent_op, OpKind::Error);
1457  m_op_map.erase(options_op->GetParentCurlHandle());
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  // Have curl execute the parent operation
1467  if (!parent_op_failed) {
1468  curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
1469  }
1470  }
1471  // The curl operation was successful, it's just the HTTP request failed; recycle the handle.
1472  queue.RecycleHandle(iter->first);
1473  } else {
1474  CurlOptionsOp *options_op = nullptr;
1475  // If this was a successful OPTIONS op, invoke the parent operation.
1476  if ((options_op = dynamic_cast<CurlOptionsOp*>(op.get()))) {
1477  options_op->Success();
1478  options_op->ReleaseHandle();
1479  // Note: op is scoped external to the conditional block
1480  op = options_op->GetOperation();
1481  op->OptionsDone();
1482  OpRecord(*op, OpKind::Start);
1483  curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
1484  curl_multi_remove_handle(multi_handle, iter->first);
1485  queue.RecycleHandle(iter->first);
1486  }
1487  // Check to see if the operation ended in a redirect (note: this might)
1488  // be invoked a second time if this was the parent operation of an OPTIONS
1489  // op.
1490  if (op->IsRedirect()) {
1491  std::string target;
1492  switch (op->Redirect(target)) {
1493  case CurlOperation::RedirectAction::Fail:
1494  if (options_op) {
1495  // In this case, we failed immediately after an OPTIONS finished.
1496  // Since there's a Start recorded after the OPTIONS processing, we
1497  // must record an error.
1498  // In the non-OPTIONS case, we never recorded a second start and
1499  // don't need a matching failure.
1500  OpRecord(*op, OpKind::Error);
1501  }
1502  keep_handle = false;
1503  break;
1504  case CurlOperation::RedirectAction::Reinvoke:
1505  if (!options_op) {
1506  // In this case, the redirect occurred without any prior
1507  // OPTIONS call. This implies that `op` is the original call
1508  // and we need to restart it later and record another op start.
1509  keep_handle = true;
1510  OpRecord(*op, OpKind::Start);
1511  }
1512  break;
1513  case CurlOperation::RedirectAction::ReinvokeAfterAllow:
1514  {
1515  // The redirect resulted in a new endpoint where the cache lookup failed;
1516  // we need to know what HTTP verbs are in the server's Allow list before this
1517  // operation can continue. Inject a new CurlOptionsOp and chain it to the one
1518  // being processed. Once the OPTIONS request is done, then we'll restart this
1519  // operation.
1520  std::string modified_url;
1521  target = VerbsCache::GetUrlKey(target, 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");
1527  op->Fail(XrdCl::errInternal, ENOMEM, "Unable to get 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  // The original curl operation needs to be kept around. Note that because options_op
1560  // is non-nil, we won't re-add the handle to the multi-handle.
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  // In this case, the OPTIONS call happened before the parent operation was started.
1573  curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
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  // If the handle was successful, then we can recycle it.
1584  queue.RecycleHandle(iter->first);
1585  }
1586  }
1587  } else if (res == CURLE_COULDNT_CONNECT && op->UseConnectionCallout() && !op->GetTriedBoker()) {
1588  // In this case, we need to use the broker and the curl handle couldn't reuse
1589  // an existing socket.
1590  keep_handle = true;
1591  op->SetTriedBoker(); // Flag to ensure we try a connection only once per operation.
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  // We cannot invoke the failure from within a callback as the curl thread and
1608  // original thread of execution may fight over the ownership of the handle memory.
1609  switch (op->GetError()) {
1610  case CurlOperation::OpError::ErrHeaderTimeout:
1611 #ifdef HAVE_XPROTOCOL_TIMEREXPIRED
1612  op->Fail(XrdCl::errOperationExpired, 0, "Origin did not respond with headers within timeout");
1613 #else
1614  op->Fail(XrdCl::errOperationExpired, 0, "Origin did not respond within timeout");
1615 #endif
1616  OpRecord(*op, OpKind::Error);
1617  break;
1618  case CurlOperation::OpError::ErrCallback: {
1619  auto [ecode, emsg] = op->GetCallbackError();
1620  op->Fail(XrdCl::errErrorResponse, ecode, emsg);
1621  OpRecord(*op, OpKind::Error);
1622  break;
1623  }
1624  case CurlOperation::OpError::ErrOperationTimeout:
1625  op->Fail(XrdCl::errOperationExpired, 0, "Operation timed out");
1626  OpRecord(*op, op->IsPaused() ? OpKind::ClientTimeout : OpKind::ServerTimeout);
1627  break;
1628  case CurlOperation::OpError::ErrTransferSlow:
1629  op->Fail(XrdCl::errOperationExpired, 0, "Transfer speed below minimum threshold");
1630  OpRecord(*op, OpKind::ServerTimeout);
1631  break;
1632  case CurlOperation::OpError::ErrTransferClientStall:
1633  op->Fail(XrdCl::errOperationExpired, 0, "Transfer stalled for too long");
1634  OpRecord(*op, OpKind::ClientTimeout);
1635  break;
1636  case CurlOperation::OpError::ErrTransferStall:
1637  op->Fail(XrdCl::errOperationExpired, 0, "Transfer stalled for too long");
1638  OpRecord(*op, OpKind::ServerTimeout);
1639  break;
1640  case CurlOperation::OpError::ErrNone:
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) {
1647  auto parent_op = options_op->GetOperation();
1648  bool parent_op_failed = false;
1649  if (parent_op->IsRedirect()) {
1650  std::string target;
1651  if (parent_op->Redirect(target) == CurlOperation::RedirectAction::Fail) {
1652  auto iter = m_op_map.find(options_op->GetParentCurlHandle());
1653  if (iter != m_op_map.end()) {
1654  OpRecord(*iter->second.first, OpKind::Error);
1655  iter->second.first->Fail(XrdCl::errErrorResponse, 0, "Failed to send OPTIONS to redirect target");
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){
1667  curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
1668  }
1669  }
1670  } else {
1671  auto xrdCode = CurlCodeConvert(res);
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) {
1680  auto parent_op = options_op->GetOperation();
1681  bool parent_op_failed = false;
1682  if (parent_op->IsRedirect()) {
1683  std::string target;
1684  if (parent_op->Redirect(target) == CurlOperation::RedirectAction::Fail) {
1685  auto iter = m_op_map.find(options_op->GetParentCurlHandle());
1686  if (iter != m_op_map.end()) {
1687  OpRecord(*iter->second.first, OpKind::Error);
1688  iter->second.first->Fail(XrdCl::errErrorResponse, 0, "Failed to send OPTIONS to redirect target");
1689  m_op_map.erase(iter);
1690  running_handles -= 1;
1691  }
1692  parent_op_failed = true;
1693  }
1694  }
1695  if (!parent_op_failed){
1696  curl_multi_add_handle(multi_handle, options_op->GetParentCurlHandle());
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) {
1722  map_entry.second.first->Fail(XrdCl::errInternal, mres, curl_multi_strerror(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)
@ Error
static void CleanupDnsCache()
static std::string_view GetUrlKey(const std::string &url, std::string &modified_url)
bool GetInt(const std::string &key, int &value)
Definition: XrdClEnv.cc:115
void Error(uint64_t topic, const char *format,...)
Report an error.
Definition: XrdClLog.cc:231
void Warning(uint64_t topic, const char *format,...)
Report a warning.
Definition: XrdClLog.cc:248
void Debug(uint64_t topic, const char *format,...)
Print a debug message.
Definition: XrdClLog.cc:282
std::pair< uint16_t, uint32_t > HTTPStatusConvert(unsigned status)
bool HTTPStatusIsError(unsigned status)
const uint16_t errErrorResponse
Definition: XrdClStatus.hh:105
const uint16_t errOperationExpired
Definition: XrdClStatus.hh:90
const uint16_t errInternal
Internal error.
Definition: XrdClStatus.hh:56
const uint16_t errConnectionError
Definition: XrdClStatus.hh:78
const uint64_t kLogXrdClHttp

References XrdClHttp::CurlOperation::CleanupDnsCache(), XrdClHttp::CurlOperation::ContinueHandle(), CurlCodeConvert(), XrdCl::Log::Debug(), emsg(), XrdCl::errConnectionError, XrdCl::errErrorResponse, XrdCl::errInternal, XrdCl::errOperationExpired, XrdCl::Log::Error(), Error, XrdClHttp::CurlOperation::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::ReleaseHandle(), XrdClHttp::CurlOptionsOp::ReleaseHandle(), XrdClHttp::CurlOperation::RequiresOptions(), XrdClHttp::CurlOperation::SetContinueQueue(), XrdClHttp::CurlOperation::SetTriedBoker(), XrdClHttp::CurlOperation::Setup(), XrdClHttp::CurlOperation::StartConnectionCallout(), XrdClHttp::CurlOptionsOp::Success(), XrdClHttp::CurlOperation::Success(), XrdClHttp::CurlOperation::UseConnectionCallout(), XrdClHttp::CurlOperation::WaitSocket(), and XrdCl::Log::Warning().

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 1071 of file XrdClHttpUtil.cc.

1072 {
1073  {
1074  std::unique_lock lock(myself->m_start_lock);
1075  myself->m_start_complete_cv.wait(lock, [&]{return myself->m_start_complete;});
1076  }
1077  try {
1078  myself->Run();
1079  } catch (...) {
1080  myself->m_logger->Warning(kLogXrdClHttp, "Curl worker got an exception");
1081  {
1082  std::unique_lock lock(m_workers_mutex);
1083  auto iter = std::remove_if(m_workers.begin(), m_workers.end(), [&](std::unique_ptr<XrdClHttp::CurlWorker> &worker){return worker.get() == myself;});
1084  m_workers.erase(iter);
1085  }
1086  }
1087 }

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

+ Here is the call graph for this function:

◆ SetMaintenancePeriod()

static 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  }

◆ Start()

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

Definition at line 1058 of file XrdClHttpUtil.cc.

1059 {
1060  {
1061  std::unique_lock lock(m_workers_mutex);
1062  m_workers.emplace_back(std::move(self));
1063  m_self_tid = std::move(tid);
1064  }
1065  std::unique_lock lock(m_start_lock);
1066  m_start_complete = true;
1067  m_start_complete_cv.notify_one();
1068 }

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