Question

I'm trying to detect when a cURL request times out. I'm using curl_multi_exec if this makes a difference?

The output of curl_errno() is 0 which suggests it was a success. However the output of curl_error() is:

Operation timed out after 1435 milliseconds with 0 out of -1 bytes received

Any ideas why the error code is good, but the error message exists? I would expect an error code of 28 for a timeout.

Also, is there anything I can check in curl_getinfo() for a timeout?

I'm using PHP 5.4.4 / cURL 7.24.0.

Edit 1 - Sample code:

$mh = curl_multi_init();
curl_multi_add_handle($mh,$a);
curl_multi_add_handle($mh,$b);
curl_multi_add_handle($mh,...);

do {
    $mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);

while ($active && $mrc == CURLM_OK) {
    if (curl_multi_select($mh) == -1) usleep(100);
    do { $mrc = curl_multi_exec($mh, $active); }
    while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
Was it helpful?

Solution

When using curl_multi_exec(), you'll need to use curl_multi_info_read() to get the error code for specific handles. This is due to the way PHP interfaces with cURL in its easy and multiple interfaces, and how error codes are fetched on individual handles from cURL's curl_multi_info_read() function (see explanation below).

Basically, if you are using multi handles, calling curl_errno() and curl_error() are not reliable or accurate.

See this modified example from the manual:

<?php

$urls = array(
   "http://www.cnn.com/",
   "http://www.bbc.co.uk/",
   "http://www.yahoo.com/",
   'http://wijgeiwojgieowjg.com/',
   'http://www.example.com/',
);

$infos = array();

$mh = curl_multi_init();

foreach ($urls as $i => $url) {
    $conn[$i] = curl_init($url);
    curl_setopt($conn[$i], CURLOPT_RETURNTRANSFER, 1);

    if (strpos($url, 'example.com') !== false) {
        // set a really short timeout for one url
        curl_setopt($conn[$i], CURLOPT_TIMEOUT_MS, 10);
    }

    curl_multi_add_handle($mh, $conn[$i]);
}

do {
    $status = curl_multi_exec($mh, $active);

    if (($info = curl_multi_info_read($mh)) !== false) {
        $infos[$info['handle']] = $info;
    }

} while ($status === CURLM_CALL_MULTI_PERFORM || $active);

foreach ($urls as $i => $url) {
    $info = $infos[$conn[$i]];

    echo "$url returned code {$info['result']}";
    if (version_compare(PHP_VERSION, '5.5.0') >= 0) {
        echo ": " . curl_strerror($info['result']);
    }
    echo "\n";

    if ($info['result'] === 0) {
        $res[$i] = curl_multi_getcontent($conn[$i]);
    }

    curl_close($conn[$i]);
}

Output:

http://www.cnn.com/ returned code 0

http://www.bbc.co.uk/ returned code 0

http://www.yahoo.com/ returned code 0

http://wijgeiwojgieowjg.com/ returned code 6

http://www.example.com/ returned code 28

Explanation:

Specifically, this is due to how PHP's curl_exec() calls cURL's curl_easy_perform which returns a CURLcode (error code) and PHP specifies the cURL option CURLOPT_ERRORBUFFER which causes a buffer to automatically get filled with an error message if one occurs.

But when using PHP's curl_multi_exec(), PHP calls cURL's curl_multi_perform which returns immediately and doesn't return error codes for the multi handles. You must call cURL's curl_multi_info_read function to get error codes for the individual handles.

PHP 5.5.0 provides a wrapper to cURL's curl_easy_strerror() which returns a string corresponding to a curl error code.

OTHER TIPS

This can be really frustrating to debug as the documentation and examples on php.net are really poor relating to async curl.

Here is some example code to help demonstrate what DOES work when it comes to curl_multi_exec:

// Main work loop

do {
    $mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);

while($status = curl_multi_info_read($mh)) {
    if($status['msg'] == CURLMSG_DONE) {

        $errno = $status['result'];
        $errstr = curl_strerror($errno);

        if($errno == CURLE_OK) {

            // This request completed successfully

            // Do something with the info
            $info = curl_getinfo($status['handle');

        } else {

            // There was an error handling this request,
            // like a timeout or something.
            // Note: curl_errno($ch) will probably say success
            // but it's lying to you. Ignore it.

            fwrite(STDERR, "Request failed: Error($errno): $errstr\n");

        }
    }
}

With a main work loop like the above, you can then call it as many times as you need to. Something like

while($this->workRemaining()) {
    $this->work();
    usleep(100); // Sleep, or better yet do something productive
}

I'm not going to print out the entire class. You can make it do what you want.

The important part is to check $status['result'] to determine if there was an error. Never rely on curl_getinfo($ch) as it is wrong in a multi_curl environment.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top