Skip to content

Commit 437cecf

Browse files
committed
Fixes #283 : Fixes CPU busy loop when using request_multiple.
See #284 for background information This will require more testing across various libcurl versions. I doubt that the timeout is necessary for curl_multi_select (calls select() if it can), but leaving in one just in case of bugs, so that it will end. - Haven't thoroughly checked for relevant libcurl bugs. Asynchronously wait for events with a short timeout if CURLM_CALL_MULTI_PERFORM fails. Use shorter timeouts when the total time elapsed so far is shorter. Make the largest possible manual usleep 2ms.
1 parent 58a0bbc commit 437cecf

File tree

1 file changed

+31
-0
lines changed

1 file changed

+31
-0
lines changed

library/Requests/Transport/cURL.php

+31
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,44 @@ public function request_multiple($requests, $options) {
214214

215215
$request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle));
216216

217+
$made_progress = true;
218+
$all_requests_start = microtime(true);
219+
$microseconds_taken_by_last_curl_operation = 0;
217220
do {
218221
$active = false;
219222

223+
if (!$made_progress) {
224+
// For a small fraction of slow requests, curl_multi_select will busy loop and return immediately with no indication of error (it returns 0 immediately with/without setting curl_multi_errno).
225+
// (https://github.com/rmccue/Requests/pull/284)
226+
// To mitigate this, add our own sleep if curl returned but no requests completed (failing or succeeding)
227+
// - The amount of time to sleep is the smaller of 1ms or 10% of total time spent waiting for curl requests
228+
// (10% was arbitrarily chosen to slowing down requests that would normally take less than 1 millisecond)
229+
// - The amount of time that was already spent in curl_multi_exec+curl_multi_select in the previous request is subtracted from that.
230+
// (E.g. if curl_multi_select already slept for 2 milliseconds, curl_multi_select might be operating normally)
231+
$elapsed_microseconds = (microtime(true) - $all_requests_start) * 1000000;
232+
$microseconds_to_sleep = min($elapsed_microseconds / 10, 2000) - $microseconds_taken_by_last_curl_operation;
233+
if ($microseconds_to_sleep >= 1) {
234+
usleep($microseconds_to_sleep);
235+
}
236+
}
237+
$operation_start = microtime(true);
238+
$made_progress = false;
239+
$is_first_multi_exec = true;
220240
do {
241+
if (!$is_first_multi_exec) {
242+
// Sleep for 1 millisecond to avoid a busy loop that would chew CPU
243+
// See ORA2PG-498
244+
usleep(1000);
245+
}
221246
$status = curl_multi_exec($multihandle, $active);
247+
$is_first_multi_exec = false;
222248
}
223249
while ($status === CURLM_CALL_MULTI_PERFORM);
224250

251+
// curl_multi_select will sleep for at most 50 milliseconds before returning.
252+
curl_multi_select($multihandle, 0.05);
253+
$microseconds_taken_by_last_curl_operation = (microtime(true) - $operation_start) * 100000;
254+
225255
$to_process = array();
226256

227257
// Read the information as needed
@@ -234,6 +264,7 @@ public function request_multiple($requests, $options) {
234264

235265
// Parse the finished requests before we start getting the new ones
236266
foreach ($to_process as $key => $done) {
267+
$made_progress = true;
237268
$options = $requests[$key]['options'];
238269
if (CURLE_OK !== $done['result']) {
239270
//get error string for handle.

0 commit comments

Comments
 (0)