XRootD
XrdClHttp::HandlerQueue Class Reference

#include <XrdClHttpUtil.hh>

+ Collaboration diagram for XrdClHttp::HandlerQueue:

Public Member Functions

 HandlerQueue (unsigned max_pending_ops)
 
std::shared_ptr< CurlOperationConsume (std::chrono::steady_clock::duration)
 
void Expire ()
 
CURLGetHandle ()
 
int PollFD () const
 
void Produce (std::shared_ptr< CurlOperation > handler)
 
void RecycleHandle (CURL *)
 
void ReleaseHandles ()
 
void Shutdown ()
 
std::shared_ptr< CurlOperationTryConsume ()
 

Static Public Member Functions

static unsigned GetDefaultMaxPendingOps ()
 
static std::string GetMonitoringJson ()
 

Detailed Description

HandlerQueue is a deque of curl operations that need to be performed. The object is thread safe and can be waited on via poll().

The fact that it's poll'able is necessary because the multi-curl driver thread is based on polling FD's

Definition at line 167 of file XrdClHttpUtil.hh.

Constructor & Destructor Documentation

◆ HandlerQueue()

HandlerQueue::HandlerQueue ( unsigned  max_pending_ops)

Definition at line 559 of file XrdClHttpUtil.cc.

559  :
560  m_max_pending_ops(max_pending_ops)
561 {
562  int filedes[2];
563  auto result = pipe(filedes);
564  if (result == -1) {
565  throw std::runtime_error(strerror(errno));
566  }
567  if (fcntl(filedes[0], F_SETFL, O_NONBLOCK | O_CLOEXEC) == -1 || fcntl(filedes[1], F_SETFL, O_NONBLOCK | O_CLOEXEC) == -1) {
568  close(filedes[0]);
569  close(filedes[1]);
570  throw std::runtime_error(strerror(errno));
571  }
572  m_read_fd = filedes[0];
573  m_write_fd = filedes[1];
574 };
#define close(a)
Definition: XrdPosix.hh:48

References close.

Member Function Documentation

◆ Consume()

std::shared_ptr< CurlOperation > HandlerQueue::Consume ( std::chrono::steady_clock::duration  dur)

Definition at line 792 of file XrdClHttpUtil.cc.

793 {
794  std::unique_lock<std::mutex> lk(m_mutex);
795  m_consumer_cv.wait_for(lk, dur, [&]{return m_ops.size() > 0 || m_shutdown;});
796  if (m_shutdown || m_ops.empty()) {
797  return {};
798  }
799 
800  std::shared_ptr<CurlOperation> result = m_ops.front();
801  m_ops.pop_front();
802 
803  char ready[1];
804  while (true) {
805  auto result = read(m_read_fd, ready, 1);
806  if (result == -1) {
807  if (errno == EINTR) {
808  continue;
809  } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
810  // This should never happen, but if it does, just continue
811  // as if we successfully read the byte.
812  break;
813  }
814  throw std::runtime_error(strerror(errno));
815  }
816  break;
817  }
818 
819  lk.unlock();
820  m_producer_cv.notify_one();
821  m_ops_consumed.fetch_add(1, std::memory_order_relaxed);
822 
823  return result;
824 }
ssize_t read(int fildes, void *buf, size_t nbyte)

References read().

+ Here is the call graph for this function:

◆ Expire()

void HandlerQueue::Expire ( )

Definition at line 691 of file XrdClHttpUtil.cc.

692 {
693  std::unique_lock<std::mutex> lk(m_mutex);
694  auto now = std::chrono::steady_clock::now();
695 
696  // Iterate through the paused transfers, checking if they are done.
697  for (auto &op : m_ops) {
698  if (!op->IsPaused()) continue;
699 
700  if (op->TransferStalled(0, now)) {
701  op->ContinueHandle();
702  }
703  }
704 
705  std::vector<decltype(m_ops)::value_type> expired_ops;
706  unsigned expired_count = 0;
707  auto it = std::remove_if(m_ops.begin(), m_ops.end(),
708  [&](const std::shared_ptr<CurlOperation> &handler) {
709  auto expired = handler->GetOperationExpiry() < now;
710  if (expired) {
711  expired_ops.push_back(handler);
712  expired_count++;
713  }
714  return expired;
715  });
716  m_ops.erase(it, m_ops.end());
717 
718  // The contents of our pipe and the in-memory queue are now off by expired_count.
719  // Read exactly that many bytes from the pipe and throw them away.
720  char throwaway[64];
721  unsigned bytes_to_read = expired_count;
722  while (bytes_to_read > 0) {
723  size_t chunk = std::min<size_t>(sizeof(throwaway), bytes_to_read);
724  ssize_t n = read(m_read_fd, throwaway, chunk);
725  if (n > 0) {
726  bytes_to_read -= n;
727  } else if (n == -1) {
728  if (errno == EINTR) {
729  continue;
730  } else {
731  // EWOULDBLOCK is a possibility if there's a synchronization error;
732  // for now, just continue on as if we were successful in reading out
733  // the missing bytes
734  break;
735  }
736  } else {
737  break;
738  }
739  }
740 
741  // Note: the failure handler may trigger new operations submitted to the queue
742  // (which requires the lock to be held) such as a prefetch operation that gets split
743  // into multiple sub-operations.
744  //
745  // Thus, we must unlock the mutex protecting the queue and avoid touching the shared state of
746  // m_ops.
747  lk.unlock();
748  for (auto &handler : expired_ops) {
749  if (handler) handler->Fail(XrdCl::errOperationExpired, 0, "Operation expired while in queue");
750  }
751 }
const uint16_t errOperationExpired
Definition: XrdClStatus.hh:90

◆ GetDefaultMaxPendingOps()

static unsigned XrdClHttp::HandlerQueue::GetDefaultMaxPendingOps ( )
inlinestatic

Definition at line 194 of file XrdClHttpUtil.hh.

194 {return m_default_max_pending_ops;}

◆ GetHandle()

CURL * HandlerQueue::GetHandle ( )

Definition at line 675 of file XrdClHttpUtil.cc.

675  {
676  if (m_handles.size()) {
677  auto result = m_handles.back();
678  m_handles.pop_back();
679  return result;
680  }
681 
682  return ::GetHandle(EnableCurlHeaderDump());
683 }
CURL * GetHandle(bool verbose)

References XrdClHttp::GetHandle().

+ Here is the call graph for this function:

◆ GetMonitoringJson()

std::string HandlerQueue::GetMonitoringJson ( )
static

Definition at line 827 of file XrdClHttpUtil.cc.

828 {
829  auto consumed = m_ops_consumed.load(std::memory_order_relaxed);
830  auto produced = m_ops_produced.load(std::memory_order_relaxed);
831  return "{"
832  "\"produced\":" + std::to_string(produced) + ","
833  "\"consumed\":" + std::to_string(consumed) + ","
834  "\"pending\":" + std::to_string(produced - consumed) + ","
835  "\"rejected\":" + std::to_string(m_ops_rejected.load(std::memory_order_relaxed)) +
836  "}";
837 }

◆ PollFD()

int XrdClHttp::HandlerQueue::PollFD ( ) const
inline

Definition at line 176 of file XrdClHttpUtil.hh.

176 {return m_read_fd;}

◆ Produce()

void HandlerQueue::Produce ( std::shared_ptr< CurlOperation handler)

Definition at line 754 of file XrdClHttpUtil.cc.

755 {
756  auto handler_expiry = handler->GetOperationExpiry();
757  std::unique_lock<std::mutex> lk{m_mutex};
758  m_producer_cv.wait_until(lk,
759  handler_expiry,
760  [&]{return m_ops.size() < m_max_pending_ops;}
761  );
762  if (std::chrono::steady_clock::now() > handler_expiry) {
763  lk.unlock();
764  handler->Fail(XrdCl::errOperationExpired, 0, "Operation expired while waiting for worker");
765  m_ops_rejected.fetch_add(1, std::memory_order_relaxed);
766  return;
767  }
768 
769  m_ops.push_back(handler);
770  char ready[] = "1";
771  while (true) {
772  auto result = write(m_write_fd, ready, 1);
773  if (result == -1) {
774  if (errno == EINTR) {
775  continue;
776  } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
777  // This should never happen, but if it does, just continue
778  // as if we successfully wrote the notification to the pipe.
779  break;
780  }
781  throw std::runtime_error(strerror(errno));
782  }
783  break;
784  }
785 
786  lk.unlock();
787  m_consumer_cv.notify_one();
788  m_ops_produced.fetch_add(1, std::memory_order_relaxed);
789 }
#define write(a, b, c)
Definition: XrdPosix.hh:121

References XrdCl::errOperationExpired, and write.

◆ RecycleHandle()

void HandlerQueue::RecycleHandle ( CURL curl)

Definition at line 686 of file XrdClHttpUtil.cc.

686  {
687  m_handles.push_back(curl);
688 }

◆ ReleaseHandles()

void HandlerQueue::ReleaseHandles ( )

Definition at line 883 of file XrdClHttpUtil.cc.

884 {
885  for (auto handle : m_handles) {
886  curl_easy_cleanup(handle);
887  }
888  m_handles.clear();
889 }

◆ Shutdown()

void HandlerQueue::Shutdown ( )

Definition at line 875 of file XrdClHttpUtil.cc.

876 {
877  std::unique_lock lock(m_mutex);
878  m_shutdown = true;
879  m_consumer_cv.notify_all();
880 }

◆ TryConsume()

std::shared_ptr< CurlOperation > HandlerQueue::TryConsume ( )

Definition at line 840 of file XrdClHttpUtil.cc.

841 {
842  std::unique_lock<std::mutex> lk(m_mutex);
843  if (m_ops.size() == 0) {
844  std::shared_ptr<CurlOperation> result;
845  return result;
846  }
847 
848  std::shared_ptr<CurlOperation> result = m_ops.front();
849  m_ops.pop_front();
850 
851  char ready[1];
852  while (true) {
853  auto result = read(m_read_fd, ready, 1);
854  if (result == -1) {
855  if (errno == EINTR) {
856  continue;
857  } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
858  // This should never happen, but if it does, just continue
859  // as if we successfully read the byte.
860  break;
861  }
862  throw std::runtime_error(strerror(errno));
863  }
864  break;
865  }
866 
867  lk.unlock();
868  m_producer_cv.notify_one();
869  m_ops_consumed.fetch_add(1, std::memory_order_relaxed);
870 
871  return result;
872 }

References read().

+ Here is the call graph for this function:

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