18#include <unordered_map>
25#include <condition_variable>
29#include <sys/socket.h>
30#include <fairlogger/Logger.h>
31#include <boost/asio/ip/host_name.hpp>
42 uv_strerror_r(code,
buf, 1000);
54 if (code != CURLE_OK) {
62 if (code != CURLM_OK) {
71 std::string host = boost::asio::ip::host_name();
72 char const* jobID = getenv(
"ALIEN_PROC_ID");
89 setupInternalUVLoop();
93 mTimeoutTimer = (
uv_timer_t*)malloc(
sizeof(*mTimeoutTimer));
94 mTimeoutTimer->data =
this;
98 initializeMultiHandle();
101void CCDBDownloader::setupInternalUVLoop()
107void CCDBDownloader::initializeMultiHandle()
109 mCurlMultiHandle = curl_multi_init();
110 curlMultiErrorCheck(curl_multi_setopt(mCurlMultiHandle, CURLMOPT_SOCKETFUNCTION, handleSocket));
111 auto socketData = &mSocketData;
112 socketData->curlm = mCurlMultiHandle;
113 socketData->CD =
this;
115 curlMultiErrorCheck(curl_multi_setopt(mCurlMultiHandle, CURLMOPT_TIMERFUNCTION, startTimeout));
116 curlMultiErrorCheck(curl_multi_setopt(mCurlMultiHandle, CURLMOPT_TIMERDATA, mTimeoutTimer));
127 while (uv_loop_alive(
mUVLoop) || (uv_loop_close(
mUVLoop) == UV_EBUSY)) {
139 if (CD->mHandleMap.find(handle) != CD->mHandleMap.end()) {
140 if (!uv_is_closing(handle)) {
148 if (handle !=
nullptr) {
153void CCDBDownloader::closesocketCallback(
void* clientp, curl_socket_t item)
155 auto CD = (CCDBDownloader*)clientp;
156 if (CD->mExternalLoop) {
158 if (CD->mSocketTimerMap.find(item) != CD->mSocketTimerMap.end()) {
159 auto timer = CD->mSocketTimerMap[item];
164 delete (DataForClosingSocket*)timer->data;
166 CD->mSocketTimerMap.erase(item);
167 if (close(item) == -1) {
173 if (
close(item) == -1) {
189 if (CD->mExternalLoop) {
190 CD->mSocketTimerMap[sock] = (
uv_timer_t*)malloc(
sizeof(*CD->mSocketTimerMap[sock]));
192 CD->mHandleMap[(
uv_handle_t*)CD->mSocketTimerMap[sock]] =
true;
197 CD->mSocketTimerMap[sock]->data =
data;
203void CCDBDownloader::closeSocketByTimer(
uv_timer_t* handle)
205 auto data = (DataForClosingSocket*)handle->data;
207 auto sock =
data->socket;
209 if (CD->mSocketTimerMap.find(sock) != CD->mSocketTimerMap.end()) {
211 CD->mSocketTimerMap.erase(sock);
212 if (close(sock) == -1) {
220void CCDBDownloader::curlTimeout(
uv_timer_t* handle)
224 curl_multi_socket_action(CD->mCurlMultiHandle, CURL_SOCKET_TIMEOUT, 0, &running_handles);
225 CD->checkMultiInfo();
228void CCDBDownloader::curlPerform(
uv_poll_t* handle,
int status,
int events)
233 if (events & UV_READABLE) {
234 flags |= CURL_CSELECT_IN;
236 if (events & UV_WRITABLE) {
237 flags |= CURL_CSELECT_OUT;
240 auto context = (CCDBDownloader::curl_context_t*)handle->data;
242 curlMultiErrorCheck(curl_multi_socket_action(context->CD->mCurlMultiHandle, context->sockfd,
flags, &running_handles));
243 context->CD->checkMultiInfo();
246int CCDBDownloader::handleSocket(CURL* easy, curl_socket_t s,
int action,
void* userp,
void* socketp)
248 auto socketData = (CCDBDownloader::DataForSocket*)userp;
250 CCDBDownloader::curl_context_t* curl_context;
256 case CURL_POLL_INOUT:
258 curl_context = socketp ? (CCDBDownloader::curl_context_t*)socketp : CD->createCurlContext(
s);
261 if (action != CURL_POLL_IN) {
262 events |= UV_WRITABLE;
264 if (action != CURL_POLL_OUT) {
265 events |= UV_READABLE;
268 if (CD->mExternalLoop && CD->mSocketTimerMap.find(s) != CD->mSocketTimerMap.end()) {
274 case CURL_POLL_REMOVE:
276 if (CD->mExternalLoop) {
278 if (CD->mSocketTimerMap.find(s) != CD->mSocketTimerMap.end()) {
279 uvErrorCheck(uv_timer_start(CD->mSocketTimerMap[s], closeSocketByTimer, CD->mKeepaliveTimeoutMS, 0),
SEVERE);
282 uvErrorCheck(uv_poll_stop(((CCDBDownloader::curl_context_t*)socketp)->poll_handle),
SEVERE);
283 CD->destroyCurlContext((CCDBDownloader::curl_context_t*)socketp);
333CCDBDownloader::curl_context_t* CCDBDownloader::createCurlContext(curl_socket_t sockfd)
335 curl_context_t* context;
337 context = (curl_context_t*)malloc(
sizeof(*context));
339 context->sockfd = sockfd;
340 context->poll_handle = (
uv_poll_t*)malloc(
sizeof(*context->poll_handle));
344 context->poll_handle->data = context;
349void CCDBDownloader::curlCloseCB(
uv_handle_t* handle)
351 auto* context = (curl_context_t*)handle->data;
352 free(context->poll_handle);
356void CCDBDownloader::destroyCurlContext(curl_context_t* context)
358 uv_close((
uv_handle_t*)context->poll_handle, curlCloseCB);
361void CCDBDownloader::tryNewHost(PerformData* performData, CURL* easy_handle)
363 auto requestData = performData->requestData;
364 std::string newUrl = requestData->hosts.at(performData->hostInd) +
"/" + requestData->path +
"/" +
std::to_string(requestData->timestamp);
365 LOG(
debug) <<
"Connecting to another host " << newUrl <<
"\n";
366 requestData->hoPair.header.clear();
367 curl_easy_setopt(easy_handle, CURLOPT_URL, newUrl.c_str());
372 if (performData->hostInd <
static_cast<int>(requestData->optionsLists.size())) {
373 curl_easy_setopt(easy_handle, CURLOPT_HTTPHEADER,
374 requestData->optionsLists.at(performData->hostInd));
376 mHandlesToBeAdded.push_back(easy_handle);
379void CCDBDownloader::getLocalContent(PerformData* performData, std::string& newLocation,
bool& contentRetrieved, std::vector<std::string>&
locations)
381 auto requestData = performData->requestData;
382 LOG(
debug) <<
"Redirecting to local content " << newLocation <<
"\n";
383 if (requestData->localContentCallback(newLocation)) {
384 contentRetrieved =
true;
385 LOG(
debug) <<
"Local content retrieved succesfully: " << newLocation <<
" n";
388 newLocation = getNewLocation(performData,
locations);
389 LOG(
debug) <<
"Failed to retrieve local content: " << newLocation <<
"\n";
393std::string CCDBDownloader::getNewLocation(PerformData* performData, std::vector<std::string>&
locations)
const
395 auto requestData = performData->requestData;
396 if (performData->locInd <
locations.size()) {
397 std::string newLocation =
locations.at(performData->locInd++);
398 std::string hostUrl = requestData->hosts.at(performData->hostInd);
399 std::string newUrl = prepareRedirectedURL(newLocation, hostUrl);
406void CCDBDownloader::httpRedirect(PerformData* performData, std::string& newLocation, CURL* easy_handle)
408 auto requestData = performData->requestData;
409 LOG(
debug) <<
"Trying content location " << newLocation <<
"\n";
410 curl_easy_setopt(easy_handle, CURLOPT_URL, newLocation.c_str());
411 mHandlesToBeAdded.push_back(easy_handle);
414void CCDBDownloader::followRedirect(PerformData* performData, CURL* easy_handle, std::vector<std::string>&
locations,
bool& rescheduled,
bool& contentRetrieved)
416 std::string newLocation = getNewLocation(performData,
locations);
417 while (!contentRetrieved && (newLocation.find(
"alien:/", 0) != std::string::npos || newLocation.find(
"file:/", 0) != std::string::npos)) {
418 getLocalContent(performData, newLocation, contentRetrieved,
locations);
420 if (!contentRetrieved && newLocation !=
"") {
421 httpRedirect(performData, newLocation, easy_handle);
428 CURLU* host_url = curl_url();
429 curl_url_set(host_url, CURLUPART_URL, full_host_url.c_str(), 0);
433 CURLUcode host_result = curl_url_get(host_url, CURLUPART_HOST, &host, 0);
434 if (host_result != CURLUE_OK) {
435 LOG(error) <<
"CCDBDownloader: Malformed url detected when processing redirect, could not identify the host part: " << full_host_url;
436 curl_url_cleanup(host_url);
441 CURLUcode scheme_result = curl_url_get(host_url, CURLUPART_SCHEME, &scheme, 0);
444 CURLUcode port_result = curl_url_get(host_url, CURLUPART_PORT, &port, 0);
446 curl_url_cleanup(host_url);
449 std::string trimmed_url =
"";
450 if (scheme_result == CURLUE_OK) {
451 trimmed_url += scheme + std::string(
"://");
456 if (port_result == CURLUE_OK) {
457 trimmed_url += std::string(
":") + port;
463std::string CCDBDownloader::prepareRedirectedURL(std::string
address, std::string potentialHost)
const
466 if (
address.find(
"alien:/") != std::string::npos ||
address.find(
"file:/") != std::string::npos) {
470 CURLU* redirected_url = curl_url();
471 curl_url_set(redirected_url, CURLUPART_URL,
address.c_str(), 0);
473 CURLUcode scheme_result = curl_url_get(redirected_url, CURLUPART_SCHEME, &scheme, 0);
475 curl_url_cleanup(redirected_url);
476 if (scheme_result == CURLUE_OK) {
485void CCDBDownloader::transferFinished(CURL* easy_handle, CURLcode curlCode)
488 PerformData* performData;
492 *performData->codeDestination = curlCode;
494 bool rescheduled =
false;
495 bool contentRetrieved =
false;
498 LOG(error) <<
"CCDBDownloader CURL transfer error - " << curl_easy_strerror(curlCode) <<
"\n";
501 switch (performData->type) {
503 --(*performData->requestsLeft);
507 if (requestData->headers) {
508 for (
auto& p : requestData->hoPair.header) {
509 (*requestData->headers)[
p.first] =
p.second;
514 curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &httpCode);
516 curl_easy_getinfo(easy_handle, CURLINFO_EFFECTIVE_URL, &
url);
517 LOG(
debug) <<
"Transfer for " <<
url <<
" finished with code " << httpCode <<
"\n";
518 std::string currentHost = requestData->hosts[performData->hostInd];
519 std::string loggingMessage =
prepareLogMessage(currentHost, requestData->userAgent, requestData->path, requestData->timestamp, requestData->headers, httpCode);
522 updateLocations(&(requestData->hoPair.header), &requestData->locations, &performData->locInd);
525 if (200 <= httpCode && httpCode < 400) {
527 if (304 == httpCode) {
528 LOGP(
debug,
"Object exists but I am not serving it since it's already in your possession");
529 contentRetrieved =
true;
530 }
else if (300 <= httpCode && httpCode < 400 && performData->locInd < requestData->
locations.size()) {
531 followRedirect(performData, easy_handle, requestData->locations, rescheduled, contentRetrieved);
532 }
else if (200 <= httpCode && httpCode < 300) {
533 contentRetrieved =
true;
536 LOG(error) << loggingMessage;
541 contentRetrieved =
false;
545 if (!rescheduled && !contentRetrieved) {
547 if (performData->locInd < requestData->locations.size()) {
548 followRedirect(performData, easy_handle, requestData->locations, rescheduled, contentRetrieved);
553 if (!rescheduled && !contentRetrieved) {
555 if (++performData->hostInd < requestData->hosts.size()) {
556 tryNewHost(performData, easy_handle);
559 LOG(error) <<
"File " << requestData->path <<
" could not be retrieved. No more hosts to try.";
565 if (!contentRetrieved) {
566 if (requestData->hoPair.object) {
567 requestData->hoPair.object->clear();
569 if (requestData->headers) {
570 (*requestData->headers)[
"Error"] =
"An error occurred during retrieval";
572 LOGP(alarm,
"Curl request to {}, response code: {}",
url, httpCode);
574 if (requestData->headers && requestData->headers->find(
"fileSize") == requestData->headers->end()) {
575 (*requestData->headers)[
"fileSize"] = fmt::format(
"{}", requestData->hoPair.object ? requestData->hoPair.object->size() : 0);
578 --(*performData->requestsLeft);
579 for (
auto* optionList : *performData->options) {
580 curl_slist_free_all(optionList);
583 delete performData->codeDestination;
584 curl_easy_cleanup(easy_handle);
597 curlMultiErrorCheck(curl_multi_socket_action(mCurlMultiHandle, CURL_SOCKET_TIMEOUT, 0, &running_handles));
601void CCDBDownloader::checkMultiInfo()
606 while ((
message = curl_multi_info_read(mCurlMultiHandle, &pending))) {
609 CURLcode code =
message->data.result;
610 transferFinished(
message->easy_handle, code);
614 fprintf(stderr,
"CURLMSG default\n");
620int CCDBDownloader::startTimeout(CURLM* multi,
long timeout_ms,
void* userp)
624 if (timeout_ms < 0) {
627 if (timeout_ms == 0) {
635void CCDBDownloader::setHandleOptions(CURL* handle, PerformData*
data)
638 curlEasyErrorCheck(curl_easy_setopt(handle, CURLOPT_CLOSESOCKETFUNCTION, closesocketCallback));
646 curlEasyErrorCheck(curl_easy_setopt(handle, CURLOPT_USERAGENT, mUserAgentId.c_str()));
649void CCDBDownloader::checkHandleQueue()
651 if (mHandlesToBeAdded.size() > 0) {
656 mHandlesToBeAdded.erase(mHandlesToBeAdded.begin());
663 uv_run(
mUVLoop, noWait ? UV_RUN_NOWAIT : UV_RUN_ONCE);
668 std::vector<CURL*> handleVector;
669 handleVector.push_back(handle);
673void CCDBDownloader::updateLocations(std::multimap<std::string, std::string>* headerMap, std::vector<std::string>*
locations,
int* locIndex)
const
675 std::vector<std::string> newLocations;
677 auto iter = headerMap->find(
"Location");
678 if (iter != headerMap->end()) {
679 auto range = headerMap->equal_range(
"Location");
680 for (
auto it =
range.first; it !=
range.second; ++it) {
682 if (std::find(newLocations.begin(), newLocations.end(), it->second) == newLocations.end()) {
683 newLocations.push_back(it->second);
690 auto iter2 = headerMap->find(
"Content-Location");
691 if (iter2 != headerMap->end()) {
692 auto range = headerMap->equal_range(
"Content-Location");
693 for (
auto it =
range.first; it !=
range.second; ++it) {
695 if (std::find(newLocations.begin(), newLocations.end(), it->second) == newLocations.end()) {
696 newLocations.push_back(it->second);
703 locations->insert(
locations->begin() + (*locIndex), newLocations.begin(), newLocations.end());
708 std::vector<CURLcode> codeVector(handleVector.size());
709 size_t requestsLeft = handleVector.size();
711 for (
int i = 0;
i < handleVector.size();
i++) {
712 auto*
data =
new CCDBDownloader::PerformData();
713 data->codeDestination = &codeVector[
i];
714 codeVector[
i] = CURLE_FAILED_INIT;
716 data->type = BLOCKING;
717 data->requestsLeft = &requestsLeft;
718 setHandleOptions(handleVector[
i],
data);
719 mHandlesToBeAdded.push_back(handleVector[
i]);
722 while (requestsLeft > 0) {
733 CURLcode* codeVector =
new CURLcode();
737 std::multimap<std::string, std::string>* headerMap;
738 std::vector<std::string>* hostsPool;
739 curl_easy_getinfo(handle, CURLINFO_PRIVATE, &requestData);
741 hostsPool = &(requestData->
hosts);
745 auto*
data =
new CCDBDownloader::PerformData();
746 data->codeDestination = codeVector;
747 *codeVector = CURLE_FAILED_INIT;
749 data->type = ASYNCHRONOUS;
750 data->requestsLeft = requestCounter;
753 data->requestData = requestData;
754 data->options = options;
757 setHandleOptions(handle,
data);
758 mHandlesToBeAdded.push_back(handle);
767 std::string upath{
path};
769 auto ent = headers->find(
"Valid-From");
770 if (ent != headers->end()) {
771 upath +=
"/" + ent->second;
773 ent = headers->find(
"ETag");
774 if (ent != headers->end()) {
775 upath +=
"/" + ent->second;
778 upath.erase(remove(upath.begin(), upath.end(),
'\"'), upath.end());
779 return fmt::format(
"CcdbDownloader finished transfer {}{}{} for {} (agent_id: {}) with http code: {}", host_url, (host_url.back() ==
'/') ?
"" :
"/", upath, (ts < 0) ?
getCurrentTimestamp() : ts, userAgent, httpCode);
struct uv_timer_s uv_timer_t
struct uv_handle_s uv_handle_t
struct uv_poll_s uv_poll_t
struct uv_loop_s uv_loop_t
#define O2_SIGNPOST_EVENT_EMIT_ERROR(log, id, name, format,...)
#define O2_DECLARE_DYNAMIC_STACKTRACE_LOG(name)
For the moment we do not support logs with a stacktrace.
#define O2_SIGNPOST_ID_GENERATE(name, log)
#define O2_SIGNPOST_EVENT_EMIT_WARN(log, id, name, format,...)
void setRequestTimeoutTime(int timeoutMS)
void setOfflineTimeoutSettings()
CURLcode perform(CURL *handle)
void asynchSchedule(CURL *handle, size_t *requestCounter)
std::string trimHostUrl(std::string full_host_url) const
void setKeepaliveTimeoutTime(int timeoutMS)
void runLoop(bool noWait)
std::vector< CURLcode > batchBlockingPerform(std::vector< CURL * > const &handleVector)
void setConnectionTimeoutTime(int timeoutMS)
void setHappyEyeballsHeadstartTime(int headstartMS)
std::string prepareLogMessage(std::string host_url, std::string userAgent, const std::string &path, long ts, const std::map< std::string, std::string > *headers, long httpCode) const
std::unordered_map< uv_handle_t *, bool > mHandleMap
int mHappyEyeballsHeadstartMS
void setMaxParallelConnections(int limit)
CCDBDownloader(uv_loop_t *uv_loop=nullptr)
void setOnlineTimeoutSettings()
GLuint const GLint * locations
GLuint GLuint64EXT address
GLsizei const GLchar *const * path
GLuint GLsizei const GLchar * message
GLbitfield GLuint64 timeout
GLenum GLuint GLenum GLsizei const GLchar * buf
information complementary to a CCDB object (path, metadata, startTimeValidity, endTimeValidity etc)
void curlEasyErrorCheck(CURLcode code)
std::string uniqueAgentID()
void uvErrorCheck(int code, DownloaderErrorLevel level)
struct o2::ccdb::DownloaderRequestData DownloaderRequestData
long getCurrentTimestamp()
returns the timestamp in long corresponding to "now"
void curlMultiErrorCheck(CURLMcode code)
void closeHandles(uv_handle_t *handle, void *arg)
curl_socket_t opensocketCallback(void *clientp, curlsocktype purpose, struct curl_sockaddr *address)
void onUVClose(uv_handle_t *handle)
Polygon< T > close(Polygon< T > polygon)
std::string to_string(gsl::span< T, Size > span)
std::vector< curl_slist * > optionsLists
std::vector< std::string > hosts
HeaderObjectPair_t hoPair
static std::string getRandomString(int length)
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"