-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Error 28 depending on time elapsed between multi_curl_exec() execution #4632
Comments
First, you're using the CURL/PHP binding and not the actual curl or libcurl directly so you should rather file bugs on this module to the PHP team. This is not the home of the CURL/PHP binding. But... So if you call it first, then sleep for a while, there will most likely be network activity happening that isn't acted upon because your code sleeps instead of asking curl to do that. If you then at the same time reduces the maximum allowed time you of course risk that you sleep all into the end time you've asked curl to give up on. You're asking for this. This is not a curl bug. Not even a CURL/PHP bug. |
Thanks for getting back to me despite it being more a PHP related question. I did some studying and read your documentation and the PHP code that is mapping the multi curl functions (they are indeed pretty close). Also it seems clearly not a bug but simply how it is working. I summarized my understanding on how it is working below. Could you please let me know if it is correct? Could you also clarify what is meant by a #pending internal timeout# in the documentation concerning multi curl, I am so far assuming that timeout means the CURLOPT_xx settings, but wonder if they are others I should be aware of? So, my understanding is: Assuming the TOW time is 50ms and a curl transfer finishes or times out during these 50ms then I get the error code 28 (timeout being CURLOPT_TIMEOUT, CURLOPT_CONNECTTIMEOUT, others?). The final consequence of any time spent outside of curl_multi_wait() is to not get the result of the transfer TX even if it succeeded. Best, |
First, "internal timeouts" refer to a timeout handled internally. They are plenty and the timeout values you can set with the API will be included among them but will not be the only ones possible. They're internal and thus not externally visible. The application will just know when it should call the API again so that libcurl can continue its business as it wants.
Generally speaking, this is not true at all. In most times during a transfer's life time you can wait a long time before you call libcurl again and the only thing you'll do is get a slower transfer. In fact, if you can reproduce your hypothesis that not calling curl_multi_wait() within 50ms causes a problem from a plain libcurl program against a public URL then I'd be most interested in that recipe (and I'm afraid it would need to not use PHP for me to be able to do anything fun with it). I can't even think of a plausible situation how that can happen. |
Hello again, Sorry, this is going a bit long and not absolutely clear. I put my understanding and conclusion at the end, your insight would be appreciated. The script is longer than the below but it the rest is mostly about building the tables you can see in the attached files. In the files I put comments for when a request fails due do the time out, this is my best guess/ conclusion.
Best, failed_wait_1.txt -------------------- Main CODE: -------------------
// request creation
$nbCurls = 10;
$refTime = time();
$addStart = microtime(true);
for ($nbc=1; $nbc <=$nbCurls; $nbc++) {
$csh = curl_init();
$chIdx = 'c'.explode('#',(string)$csh)[1];
$chAddTimes[$chIdx] = microtime(true);
$expecteResp[$chIdx] = $chAddTimes[$chIdx] + 1.010;
$timeOutAt[$chIdx] = $chAddTimes[$chIdx] + $curlOpts[CURLOPT_TIMEOUT_MS]/1000;
$curlOpts[CURLOPT_URL] = 'http://192.168.1.212:8022/?'.http_build_query(['chIdx' => $chIdx, 'addTime' => $chAddTimes[$chIdx]]);
curl_setopt_array($csh, $curlOpts);
// add and execute curl
curl_multi_add_handle($mch, $csh);
curl_multi_exec($mch, $active);
// wait before sending the next request
usleep(10000);
}
// MAIN LOOP
do {
$l++;
$start = microtime(true);
$lpIdx = (string)$start;
$lp[$lpIdx]['lpNum'] = $l;
$lp[$lpIdx]['start_T'] = $start;
//$lp[$lpIdx]['lpIdx'] = $lpIdx;
//-----
curl_multi_exec($mch, $active);
$lp[$lpIdx]['active'] = $active;
$lp[$lpIdx]['aExec_T'] = microtime(true);
//-----
curl_multi_select($mch, $waitTimeOut);
$lp[$lpIdx]['aWait_T'] = microtime(true);
//--------------------------------
$nbReads = 0;
if ($active >= 0) {
do {
//-----
$info = curl_multi_info_read($mch);
if ($info !== false) {
$nbReads++;
$chIdx = 'c'.explode('#',(string)$info['handle'])[1];
$infoRead[$chIdx] = [
'lpIdx' => $lpIdx,
'info' => $info,
'readTime' => microtime(true),
'lpNum' => $l,
'nbReads' => $nbReads,
];
}
} while ($info != false);
}
//--------------------------------
$lp[$lpIdx]['nbReads'] = $nbReads;
$lp[$lpIdx]['aRead_T'] = microtime(true);
//... Do some stuff ...
usleep($microSleep);
$lp[$lpIdx]['end_T'] = microtime(true);
} while ($active>0); |
You're not adding new information so I can only repeat what I've already said. What timeout(s) are you telling libcurl to use? And also, I wrote up a C example that has a 500 millisecond wait in an attempt to reproduce. It works just fine for me: #include <stdio.h>
#include <string.h>
/* somewhat unix-specific */
#include <sys/time.h>
#include <unistd.h>
/* curl stuff */
#include <curl/curl.h>
/*
* Simply download two HTTP files!
*/
int main(void)
{
CURL *http_handle;
CURL *http_handle2;
CURLM *multi_handle;
int still_running = 0; /* keep number of running handles */
http_handle = curl_easy_init();
http_handle2 = curl_easy_init();
/* set options */
curl_easy_setopt(http_handle, CURLOPT_URL, "http://localhost/512M");
/* set options */
curl_easy_setopt(http_handle2, CURLOPT_URL, "http://localhost/512M");
/* init a multi stack */
multi_handle = curl_multi_init();
/* add the individual transfers */
curl_multi_add_handle(multi_handle, http_handle);
curl_multi_add_handle(multi_handle, http_handle2);
/* we start some action by calling perform right away */
curl_multi_perform(multi_handle, &still_running);
do {
CURLMcode mc; /* curl_multi_poll() return code */
int numfds;
/* we start some action by calling perform right away */
mc = curl_multi_perform(multi_handle, &still_running);
if(still_running)
/* wait for activity, timeout or "nothing" */
mc = curl_multi_wait(multi_handle, NULL, 0, 1000, &numfds);
if(mc != CURLM_OK) {
fprintf(stderr, "curl_multi_wait() failed, code %d.\n", mc);
break;
}
usleep(500000); /* 500 millisecond useless wait */
} while(still_running);
curl_multi_cleanup(multi_handle);
curl_easy_cleanup(http_handle);
curl_easy_cleanup(http_handle2);
return 0;
} |
Hello and thanks for taking the time, |
That's not a "false error". If you tell curl that it must be done with the request within 200 milliseconds and it detects that it has spent more than so (even if that happened because the application - you - sort of did it on purpose), it should and will return a timeout as soon as it detects that fact. Any other behavior would be a bug. |
Hello and thanks for taking the time, |
I completely agree with it is not a bug, except for the edge case. Creating the error on purpose is only a way to demonstrate it. I don’t have any issue with this A) happening before or after B) since it only concerns the edge case. Best, |
I disagree. I think it works as intended even for that case, which I don't think is an edge case but a normal case: the timeout time was reached in between curl-invokes. The transfer was not complete from curl's point of view, which is why it returns an error. There's more work to do but the timeout limit is reached. If there was nothing more to do for curl, then it would already had completed that transfer. The transfer might be complete from the server's point of view, but that's certainly not the same thing. Now I'm going to stop responding to this issue. |
Hello,
I am not sure this is a bug or just how it is supposed to work. I was expecting to be able to use multi curl and add a handle, execute it, and then wait 10 seconds, and then read the result.
I seems that "reading the response" needs to be done frequently (<0.1 seconds seems a minimum to be safe).
The issue I observe is that if the curl requested has fully executed before the second read or the connect timeout the response is "lost" and there is a error code 28.
I have tried different sleep times before the loop and in the loop and came to some kind of conclusions.
Is this a bug or simply how it works and the curl_multi_exec() needs to be executed very often to be sure it is executed before the curl has returned or in a minimum time after curl returned?
Please see code with my observation and best guesses below,
PS: apologies if this is not the right format/ way to post an issue (it's my first)
//--------------------
// Test Code Stats here
// some related reports:
// #619
// aws/aws-sdk-php#924
//PHP version: PHP 7.3.9-1+ubuntu16.04.1+deb.sury.org+1 (cli) (built: Sep 2 2019 12:54:04) ( NTS )
// cUrl version (php -r "print_r(curl_version());")
// [version_number] => 470784
// [age] => 3
// [features] => 968605
// [ssl_version_number] => 0
// [version] => 7.47.0
// [host] => x86_64-pc-linux-gnu
// [ssl_version] => OpenSSL/1.0.2g
// [libz_version] => 1.2.8
$execs = [];
$reads = [];
$getInfo = [];
$mch = curl_multi_init();
$csh = curl_init();
// setup curl and add the request
$curlOpts = [
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_VERBOSE => true,
CURLOPT_URL => 'https://www.php.net', //'www.tf1.fr', //
];
curl_setopt_array($csh, $curlOpts);
$addChRetCode = curl_multi_add_handle($mch, $csh);
// execute curl
$time = number_format(microtime(true), 5, '.', '');
$status = curl_multi_exec($mch, $active);
$execs[$time] = ' Status: '.$status.' Active: '.$active;
//-------------------------------------------------------------------
// Observation:
// if no curl_multi_exec() is executed for a long time the timeout erro number 28 is triggered
// - something is timing out and it is like the reponse is lost?
// - this seems to happen when sleeping longuer than the total time it takes to fully execute the request
// - meaning something times out if the curl returned before the end of the sleep, and the response is lost
//
// There are different error messages for error 28. It seems to depend on the time elapse between the 1st, 2nd and 3rd curl_multi_exec()
// A) Error Num: 28 --> Message: Connection timed out after 6005 milliseconds
// B) Error Num: 28 --> Message: Operation timed out after 0 milliseconds with 0 out of 0 bytes received
// C) Error Num: 28 --> Message: Resolving timed out after 5001 milliseconds
// ==> this seems to be depending on what time the 2nd and 3rd curl_multi_exec() is execute in relation to the CURLOPT_CONNECTTIMEOUT
// -> message B): 1st exec ... 10 secs 2nd exec ->
// -> message A) or C): 1st exec ... (less than CURLOPT_CONNECTTIMEOUT) ... 2nd exec (time<5secs) .... (time>5secs) -> 3rd exec
//
// Conclusion/ best guess:
// - if a curl request returns before the second curl_multi_exec() the response is lost and the error 28 is triggered
// - curl_multi_exec() must be executed very often (<x millisecons?) to ensure curl multi is working?
// - there is some timout/ minimum or maximum time the curl returned is kept, after an error 28 is triggered
print 'sleep some seconds at (error 28 if longuer than total_time):'. microtime(true).PHP_EOL;
sleep(3);
//------------------------------------------
do {
$status = curl_multi_exec($mch, $active);
$time = number_format(microtime(true), 5, '.', '');
$execs[$time] = ' Status: '.$status.' Active: '.$active;
} while ($active && $status == CURLM_OK);
//------------------------------------------
// result
print_r($execs);
print_r($reads);
print PHP_EOL.'Get Info in loop and after loop: '.PHP_EOL.$errorMsg.PHP_EOL;
print_r($getInfo);
print_r(curl_getinfo($csh));
//close the handles
curl_multi_remove_handle($mch, $csh);
curl_multi_close($mch);
// End of test code
//------------------
Best,
Pascal
The text was updated successfully, but these errors were encountered: