Skip to content

Commit 7e85fdd

Browse files
committed
rest_client: Improve cURL compatibility when using async()
This patch aims to fix a regression in 1ecb324, breaking the "SUCCESS" async download test case. Mitigation as follows: * improved detection for the "Request Sent" state, before putting the download on async hold. It seems that whenever both the CURLINFO_CONNECT_TIME_T and CURLINFO_REQUEST_SIZE become available, the file descriptor can be safely polled on, awaiting the reply. Note: there is no official cURL library mechanism to detect this state. * make the async() statement timeout accessible to modules. This fixes a bug where a GET on a dead HTTP server would time out after `curl_timeout` seconds, instead of `async()` seconds (lower). Fixes OpenSIPS#3286
1 parent 57b28d8 commit 7e85fdd

File tree

5 files changed

+42
-22
lines changed

5 files changed

+42
-22
lines changed

async.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,11 @@ typedef struct _async_ctx {
5858
void *resume_param;
5959
/* the function to be called upon a timeout event while waiting to read */
6060
void *timeout_f;
61-
/* the maximum allowed time for the async op to complete, hinted by the
62-
* more complex async implementation (seconds). Default: 0 (no limit) */
61+
62+
/* Optional time limit (seconds) for the async op to complete:
63+
* - on input: the core async() timeout or 0, presented to the module
64+
* - on output: an updated timeout, if any, as processed by the module
65+
* Default: 0 (no timeout) */
6366
unsigned int timeout_s;
6467
} async_ctx;
6568

modules/rest_client/rest_client.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,9 @@ int async_rest_method(enum rest_client_method method, struct sip_msg *msg,
615615
if (no_concurrent_connects && (lrc=rcl_acquire_url(url, &host)) < RCL_OK)
616616
return lrc;
617617

618+
param->timeout_s = (ctx->timeout_s && ctx->timeout_s < curl_timeout) ?
619+
ctx->timeout_s : curl_timeout;
620+
618621
rc = start_async_http_req(msg, method, url, body, ctype,
619622
param, &param->body, ctype_pv ? &param->ctype : NULL, &read_fd);
620623

@@ -678,8 +681,8 @@ int async_rest_method(enum rest_client_method method, struct sip_msg *msg,
678681
rcl_release_url(host, rc == RCL_OK);
679682

680683
ctx->resume_f = resume_async_http_req;
681-
ctx->timeout_s = curl_timeout;
682684
ctx->timeout_f = time_out_async_http_req;
685+
ctx->timeout_s = param->timeout_s;
683686

684687
param->method = method;
685688
param->body_pv = (pv_spec_p)body_pv;

modules/rest_client/rest_methods.c

+31-19
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ static inline int get_easy_status(CURL *handle, CURLM *multi, CURLcode *code)
399399
return -1;
400400
}
401401

402-
static int init_transfer(CURL *handle, char *url)
402+
static int init_transfer(CURL *handle, char *url, unsigned long timeout_s)
403403
{
404404
CURLcode rc;
405405

@@ -414,8 +414,10 @@ static int init_transfer(CURL *handle, char *url)
414414
tls_dom = NULL;
415415
}
416416

417-
w_curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, connection_timeout);
418-
w_curl_easy_setopt(handle, CURLOPT_TIMEOUT, curl_timeout);
417+
w_curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT,
418+
timeout_s && timeout_s < connection_timeout ? timeout_s : connection_timeout);
419+
w_curl_easy_setopt(handle, CURLOPT_TIMEOUT,
420+
timeout_s && timeout_s < curl_timeout ? timeout_s : curl_timeout);
419421

420422
w_curl_easy_setopt(handle, CURLOPT_VERBOSE, 1);
421423
w_curl_easy_setopt(handle, CURLOPT_STDERR, stdout);
@@ -702,7 +704,7 @@ int rest_sync_transfer(enum rest_client_method method, struct sip_msg *msg,
702704
str st = STR_NULL, res_body = STR_NULL, tbody, ttype;
703705

704706
curl_easy_reset(sync_handle);
705-
if (init_transfer(sync_handle, url) != 0) {
707+
if (init_transfer(sync_handle, url, 0) != 0) {
706708
LM_ERR("failed to init transfer to %s\n", url);
707709
goto cleanup;
708710
}
@@ -802,7 +804,7 @@ int rest_sync_transfer(enum rest_client_method method, struct sip_msg *msg,
802804
* @url: HTTP URL to be queried
803805
* @req_body: Body of the request (NULL if not needed)
804806
* @req_ctype: Value for the "Content-Type: " header of the request (same as ^)
805-
* @async_parm: output param, will contain async handles
807+
* @async_parm: in/out param, will contain async handles
806808
* @body: reply body; gradually reallocated as data arrives
807809
* @ctype: will eventually hold the last "Content-Type" header of the reply
808810
* @out_fd: the fd to poll on, or a negative error code
@@ -819,7 +821,7 @@ int start_async_http_req(struct sip_msg *msg, enum rest_client_method method,
819821
CURLMcode mrc;
820822
fd_set rset, wset, eset;
821823
int max_fd, fd, http_rc, ret = RCL_INTERNAL_ERR;
822-
long busy_wait, timeout;
824+
long busy_wait, timeout, connect_timeout;
823825
long retry_time;
824826
OSS_CURLM *multi_list;
825827
CURLM *multi_handle;
@@ -835,7 +837,7 @@ int start_async_http_req(struct sip_msg *msg, enum rest_client_method method,
835837
goto cleanup;
836838
}
837839

838-
if (init_transfer(handle, url) != 0) {
840+
if (init_transfer(handle, url, async_parm->timeout_s) != 0) {
839841
LM_ERR("failed to init transfer to %s\n", url);
840842
goto cleanup;
841843
}
@@ -889,18 +891,27 @@ int start_async_http_req(struct sip_msg *msg, enum rest_client_method method,
889891
multi_handle = multi_list->multi_handle;
890892
curl_multi_add_handle(multi_handle, handle);
891893

892-
timeout = connection_timeout_ms;
894+
connect_timeout = (async_parm->timeout_s*1000) < connection_timeout_ms ?
895+
(async_parm->timeout_s*1000) : connection_timeout_ms;
896+
timeout = connect_timeout;
893897
busy_wait = connect_poll_interval;
894898

895899
/* obtain a read fd in "connection_timeout" seconds at worst */
896-
for (timeout = connection_timeout_ms; timeout > 0; timeout -= busy_wait) {
900+
for (timeout = connect_timeout; timeout > 0; timeout -= busy_wait) {
901+
curl_off_t connect = -1;
902+
long req_sz = -1;
903+
897904
mrc = curl_multi_perform(multi_handle, &running_handles);
898905
if (mrc != CURLM_OK && mrc != CURLM_CALL_MULTI_PERFORM) {
899906
LM_ERR("curl_multi_perform: %s\n", curl_multi_strerror(mrc));
900907
goto error;
901908
}
902909

903-
LM_DBG("perform code: %d, handles: %d\n", mrc, running_handles);
910+
curl_easy_getinfo(handle, CURLINFO_CONNECT_TIME_T, &connect);
911+
curl_easy_getinfo(handle, CURLINFO_REQUEST_SIZE, &req_sz);
912+
913+
LM_DBG("perform code: %d, handles: %d, connect: %ldµs, reqsz: %ldB\n",
914+
mrc, running_handles, connect, req_sz);
904915

905916
/* transfer completed! But how well? */
906917
if (running_handles == 0) {
@@ -923,8 +934,8 @@ int start_async_http_req(struct sip_msg *msg, enum rest_client_method method,
923934

924935
case CURLE_OPERATION_TIMEDOUT:
925936
if (http_rc == 0) {
926-
LM_ERR("connect timeout on %s (%lds)\n", url,
927-
connection_timeout);
937+
LM_ERR("connect timeout on %s (%ldms)\n", url,
938+
connect_timeout);
928939
ret = RCL_CONNECT_TIMEOUT;
929940
goto error;
930941
}
@@ -962,9 +973,8 @@ int start_async_http_req(struct sip_msg *msg, enum rest_client_method method,
962973
if (max_fd != -1) {
963974
for (fd = 0; fd <= max_fd; fd++) {
964975
if (FD_ISSET(fd, &rset)) {
965-
966976
LM_DBG("ongoing transfer on fd %d\n", fd);
967-
if (is_new_transfer(fd)) {
977+
if (connect > 0 && req_sz > 0 && is_new_transfer(fd)) {
968978
LM_DBG(">>> add fd %d to ongoing transfers\n", fd);
969979
add_transfer(fd);
970980
goto success;
@@ -981,7 +991,7 @@ int start_async_http_req(struct sip_msg *msg, enum rest_client_method method,
981991

982992
LM_DBG("libcurl TCP connect: we should wait up to %ldms "
983993
"(timeout=%ldms, poll=%ldms)!\n", retry_time,
984-
connection_timeout_ms, connect_poll_interval);
994+
connect_timeout, connect_poll_interval);
985995

986996
/*
987997
from curl_multi_timeout() docs:
@@ -1055,8 +1065,8 @@ static enum async_ret_code _resume_async_http_req(int fd, struct sip_msg *msg,
10551065
if (timed_out) {
10561066
char *url = NULL;
10571067
curl_easy_getinfo(param->handle, CURLINFO_EFFECTIVE_URL, &url);
1058-
LM_ERR("async %s timed out, URL: %s\n",
1059-
rest_client_method_str(param->method), url);
1068+
LM_ERR("async %s timed out, URL: %s (timeout: %lds)\n",
1069+
rest_client_method_str(param->method), url, param->timeout_s);
10601070
goto cleanup;
10611071
}
10621072

@@ -1069,10 +1079,12 @@ static enum async_ret_code _resume_async_http_req(int fd, struct sip_msg *msg,
10691079
LM_DBG("perform result: %d, running: %d (break: %d)\n", mrc, running,
10701080
mrc != CURLM_CALL_MULTI_PERFORM && (mrc != CURLM_OK || !running));
10711081

1072-
if (mrc == CURLM_OK && running) {
1082+
if (mrc == CURLM_OK) {
1083+
if (!running)
1084+
break;
1085+
10731086
async_status = ASYNC_CONTINUE;
10741087
return 1;
1075-
10761088
/* this rc has been removed since cURL 7.20.0 (Feb 2010), but it's not
10771089
* yet marked as deprecated, so let's keep the do/while loop */
10781090
} else if (mrc != CURLM_CALL_MULTI_PERFORM) {

modules/rest_client/rest_methods.h

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ typedef struct rest_async_param_ {
115115
struct curl_slist *header_list;
116116
str body;
117117
str ctype;
118+
unsigned long timeout_s; /* max possible duration for the entire cURL op */
118119

119120
rest_trace_param_t* tparam;
120121

modules/tm/async.c

+1
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ int t_handle_async(struct sip_msg *msg, struct action* a,
268268
}
269269

270270
memset(ctx,0,sizeof(async_tm_ctx));
271+
ctx->async.timeout_s = timeout;
271272

272273
async_status = ASYNC_NO_IO; /*assume default status "no IO done" */
273274
return_code = ((const acmd_export_t*)(a->elem[0].u.data_const))->function(msg,

0 commit comments

Comments
 (0)