Skip to content

Commit f058672

Browse files
authored
Improve MultiCurl::waitUntilRequestQuotaAvailable (#1016)
* Include keyword * Use numeric literal separators * Improve waitUntilRequestQuotaAvailable using new TimeUtil
1 parent ccbac89 commit f058672

File tree

5 files changed

+141
-19
lines changed

5 files changed

+141
-19
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Installation instructions to use the `composer` command can be found on https://
3838

3939
### 📋 Requirements
4040

41-
PHP Curl Class works with PHP 8.4, 8.3, 8.2, 8.1, and 8.0.
41+
PHP Curl Class works with PHP versions 8.4, 8.3, 8.2, 8.1, and 8.0.
4242

4343
### 🚀 Quick Start and Examples
4444

src/Curl/MultiCurl.php

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ public function start()
687687
// pending requests to have more accurate start times. Without a shorter timeout, it can be nearly a
688688
// full second before available request quota is rechecked and pending requests can be initialized.
689689
if (curl_multi_select($this->multiCurl, 0.2) === -1) {
690-
usleep(100000);
690+
usleep(100_000);
691691
}
692692

693693
curl_multi_exec($this->multiCurl, $active);
@@ -944,27 +944,28 @@ private function hasRequestQuota()
944944
*/
945945
private function waitUntilRequestQuotaAvailable()
946946
{
947-
$sleep_until = (float)($this->currentStartTime + $this->intervalSeconds);
948-
$sleep_seconds = $sleep_until - microtime(true);
947+
$sleep_until = TimeUtil::getSleepUntilMicrotime(
948+
$this->currentStartTime,
949+
$this->intervalSeconds,
950+
);
949951

950-
// Avoid using time_sleep_until() as it appears to be less precise and not sleep long enough.
951-
// Avoid using usleep(): "Values larger than 1000000 (i.e. sleeping for
952-
// more than a second) may not be supported by the operating system.
953-
// Use sleep() instead."
954-
$sleep_seconds_int = (int)$sleep_seconds;
955-
if ($sleep_seconds_int >= 1) {
956-
sleep($sleep_seconds_int);
952+
$current_microtime = microtime(true);
953+
$sleep_seconds = TimeUtil::getSleepSecondsUntilMicrotime(
954+
$sleep_until,
955+
$current_microtime,
956+
);
957+
958+
list($whole_seconds, $microseconds_remainder) = TimeUtil::getWholeAndRemainderSeconds($sleep_seconds);
959+
960+
if ($whole_seconds >= 1) {
961+
sleep($whole_seconds);
957962
}
958963

959-
// Ensure that enough time has passed as usleep() may not have waited long enough.
960-
$this->currentStartTime = microtime(true);
961-
if ($this->currentStartTime < $sleep_until) {
962-
do {
963-
usleep(1000000 / 4);
964-
$this->currentStartTime = microtime(true);
965-
} while ($this->currentStartTime < $sleep_until);
964+
if ($microseconds_remainder > 0) {
965+
usleep($microseconds_remainder);
966966
}
967967

968+
$this->currentStartTime = microtime(true);
968969
$this->currentRequestCount = 0;
969970
}
970971

src/Curl/TimeUtil.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Curl;
6+
7+
class TimeUtil
8+
{
9+
/**
10+
* Get the microtime (in microseconds) at which to sleep until.
11+
*
12+
* @param float $start_time The start time in seconds with microsecond precision.
13+
* @param int $interval_seconds The interval in seconds.
14+
* @return float The microtime (in microseconds) at which to sleep until.
15+
*/
16+
public static function getSleepUntilMicrotime(
17+
float $start_time,
18+
int $interval_seconds,
19+
): float {
20+
$result = $start_time + (float)$interval_seconds;
21+
return $result;
22+
}
23+
24+
/**
25+
* Get the number of seconds to sleep until the specified microtime.
26+
*
27+
* @param float $sleep_until_microtime The microtime (in microseconds) at which to sleep until.
28+
* @param float $current_microtime The current microtime (in microseconds).
29+
* @return float The number of seconds to sleep.
30+
*/
31+
public static function getSleepSecondsUntilMicrotime(
32+
float $sleep_until_microtime,
33+
float $current_microtime,
34+
): float {
35+
$result = $sleep_until_microtime - $current_microtime;
36+
37+
// Always round up with microsecond precision to avoid sleeping less
38+
// than required.
39+
$rounded_up_result = ceil($result * (float)1_000_000) / (float)1_000_000;
40+
return $rounded_up_result;
41+
}
42+
43+
/**
44+
* Get the whole seconds and microseconds remainder from the given sleep
45+
* seconds.
46+
*
47+
* @param float $sleep_seconds The number of seconds to sleep.
48+
* @return array An array containing the whole seconds and microseconds remainder.
49+
*/
50+
public static function getWholeAndRemainderSeconds(
51+
float $sleep_seconds,
52+
): array {
53+
$micros = (int) ceil($sleep_seconds * (float)1_000_000);
54+
$whole_seconds = intdiv($micros, 1_000_000);
55+
$microseconds_remainder = $micros % 1_000_000;
56+
return [
57+
$whole_seconds,
58+
$microseconds_remainder,
59+
];
60+
}
61+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CurlTest;
6+
7+
use Curl\TimeUtil;
8+
9+
class TimeUtilTest extends \PHPUnit\Framework\TestCase
10+
{
11+
public function testGetSleepUntilMicrotime()
12+
{
13+
// Use a fixed start time.
14+
// [...]ime = microtime(true);
15+
$start_time = (float)1_750_000_000.123456;
16+
17+
// Use a fixed current time that occurs after the start time.
18+
// [...]_microtime = (float)1_750_000_045.000001;
19+
$current_microtime = (float)1_750_000_044.999999;
20+
21+
$interval_seconds = 60;
22+
$sleep_until_microtime = TimeUtil::getSleepUntilMicrotime(
23+
$start_time,
24+
$interval_seconds,
25+
);
26+
27+
$this->assertEquals(
28+
(float)1_750_000_060.123456,
29+
$sleep_until_microtime,
30+
);
31+
}
32+
33+
public function testGetSleepSecondsUntilMicrotime()
34+
{
35+
$sleep_until_microtime = (float)1_750_000_060.123456;
36+
$current_microtime = (float)1_750_000_044.999999;
37+
38+
$sleep_seconds = TimeUtil::getSleepSecondsUntilMicrotime(
39+
$sleep_until_microtime,
40+
$current_microtime,
41+
);
42+
43+
$this->assertEquals(
44+
(float)15.123457,
45+
$sleep_seconds,
46+
);
47+
}
48+
49+
public function testGetWholeAndRemainderSeconds()
50+
{
51+
$sleep_seconds = (float)15.123457;
52+
53+
list($whole_seconds, $microseconds_remainder) = TimeUtil::getWholeAndRemainderSeconds(
54+
$sleep_seconds,
55+
);
56+
57+
$this->assertEquals(15, $whole_seconds);
58+
$this->assertEquals(123457, $microseconds_remainder);
59+
}
60+
}

tests/server.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@
323323

324324
$dots_printed = 0;
325325
while (true) {
326-
usleep(1000000 / 100);
326+
usleep(1_000_000 / 100);
327327

328328
$elapsed = microtime(true) - $start;
329329
$dots_to_print = floor($elapsed) - $dots_printed;

0 commit comments

Comments
 (0)