diff --git a/src/ExtUvLoop.php b/src/ExtUvLoop.php index aade9943..70471b59 100644 --- a/src/ExtUvLoop.php +++ b/src/ExtUvLoop.php @@ -127,7 +127,7 @@ public function addTimer($interval, $callback) $this->timers->attach($timer, $event); \uv_timer_start( $event, - (int) ($interval * 1000) + 1, + $this->convertFloatSecondsToMilliseconds($interval), 0, $callback ); @@ -146,12 +146,13 @@ public function addPeriodicTimer($interval, $callback) \call_user_func($timer->getCallback(), $timer); }; + $interval = $this->convertFloatSecondsToMilliseconds($interval); $event = \uv_timer_init($this->uv); $this->timers->attach($timer, $event); \uv_timer_start( $event, - (int) ($interval * 1000) + 1, - (int) ($interval * 1000) + 1, + $interval, + (int) $interval === 0 ? 1 : $interval, $callback ); @@ -313,4 +314,26 @@ private function createStreamListener() return $callback; } + + /** + * @param float $interval + * @return int + */ + private function convertFloatSecondsToMilliseconds($interval) + { + if ($interval < 0) { + return 0; + } + + $maxValue = (int) (\PHP_INT_MAX / 1000); + $intInterval = (int) $interval; + + if (($intInterval <= 0 && $interval > 1) || $intInterval >= $maxValue) { + throw new \InvalidArgumentException( + "Interval overflow, value must be lower than '{$maxValue}', but '{$interval}' passed." + ); + } + + return (int) \floor($interval * 1000); + } } diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 83f5756b..2e46b66e 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -591,9 +591,11 @@ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() public function testTimerIntervalCanBeFarInFuture() { + // Maximum interval for ExtUvLoop implementation + $interval = ((int) (PHP_INT_MAX / 1000)) - 1; $loop = $this->loop; // start a timer very far in the future - $timer = $this->loop->addTimer(PHP_INT_MAX, function () { }); + $timer = $this->loop->addTimer($interval, function () { }); $this->loop->futureTick(function () use ($timer, $loop) { $loop->cancelTimer($timer); diff --git a/tests/ExtUvLoopTest.php b/tests/ExtUvLoopTest.php index 61a94a9f..267eddf1 100644 --- a/tests/ExtUvLoopTest.php +++ b/tests/ExtUvLoopTest.php @@ -14,4 +14,82 @@ public function createLoop() return new ExtUvLoop(); } + + /** @dataProvider intervalProvider */ + public function testTimerInterval($interval, $expectedExceptionMessage) + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage($expectedExceptionMessage); + + $this->loop + ->addTimer( + $interval, + function () { + return 0; + } + ); + } + + public function intervalProvider() + { + $oversizeInterval = PHP_INT_MAX / 1000; + $maxValue = (int) (PHP_INT_MAX / 1000); + $oneMaxValue = $maxValue + 1; + $tenMaxValue = $maxValue + 10; + $tenMillionsMaxValue = $maxValue + 10000000; + $intMax = PHP_INT_MAX; + $oneIntMax = PHP_INT_MAX + 1; + $tenIntMax = PHP_INT_MAX + 10; + $oneHundredIntMax = PHP_INT_MAX + 100; + $oneThousandIntMax = PHP_INT_MAX + 1000; + $tenMillionsIntMax = PHP_INT_MAX + 10000000; + $tenThousandsTimesIntMax = PHP_INT_MAX * 1000; + + return array( + array( + $oversizeInterval, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oversizeInterval}' passed." + ), + array( + $oneMaxValue, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oneMaxValue}' passed.", + ), + array( + $tenMaxValue, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenMaxValue}' passed.", + ), + array( + $tenMillionsMaxValue, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenMillionsMaxValue}' passed.", + ), + array( + $intMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$intMax}' passed.", + ), + array( + $oneIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oneIntMax}' passed.", + ), + array( + $tenIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenIntMax}' passed.", + ), + array( + $oneHundredIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oneHundredIntMax}' passed.", + ), + array( + $oneThousandIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oneThousandIntMax}' passed.", + ), + array( + $tenMillionsIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenMillionsIntMax}' passed.", + ), + array( + $tenThousandsTimesIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenThousandsTimesIntMax}' passed.", + ), + ); + } } diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index 11187b31..0f96c9fe 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -137,4 +137,22 @@ public function testMinimumIntervalOneMicrosecond() $this->assertEquals(0.000001, $timer->getInterval()); } + + public function testTimerIntervalBelowZeroRunsImmediately() + { + $loop = $this->createLoop(); + $start = 0; + $loop->addTimer( + -1, + function () use (&$start) { + $start = \microtime(true); + } + ); + + $loop->run(); + $end = \microtime(true); + + // 1ms should be enough even on slow machines + $this->assertLessThan(0.001, $end - $start); + } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy