From 5d2e21969a8772b20fd7b5891bf7946999611a17 Mon Sep 17 00:00:00 2001 From: PabloKowalczyk <11366345+PabloKowalczyk@users.noreply.github.com> Date: Sat, 27 Jul 2019 08:20:31 +0200 Subject: [PATCH 1/2] Prevent "interval" overflow in ExtUvLoop --- src/ExtUvLoop.php | 29 ++++++++++++-- tests/AbstractLoopTest.php | 11 +++++- tests/ExtUvLoopTest.php | 78 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 4 deletions(-) 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..a2d24513 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -589,11 +589,20 @@ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() $this->assertRunFasterThan(1.6); } + public function testTimerIntervalBelowZeroRunsImmediately() + { + $this->loop->addTimer(-1, function () {}); + + $this->assertRunFasterThan(0.002); + } + 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.", + ), + ); + } } From a3165da14fcf467518445be4a0bd794d96cf5ce9 Mon Sep 17 00:00:00 2001 From: PabloKowalczyk <11366345+PabloKowalczyk@users.noreply.github.com> Date: Sat, 3 Aug 2019 09:55:15 +0200 Subject: [PATCH 2/2] Move "testTimerIntervalBelowZeroRunsImmediately" from AbstractLoopTest to AbstractTimerTest --- tests/AbstractLoopTest.php | 7 ------- tests/Timer/AbstractTimerTest.php | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index a2d24513..2e46b66e 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -589,13 +589,6 @@ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() $this->assertRunFasterThan(1.6); } - public function testTimerIntervalBelowZeroRunsImmediately() - { - $this->loop->addTimer(-1, function () {}); - - $this->assertRunFasterThan(0.002); - } - public function testTimerIntervalCanBeFarInFuture() { // Maximum interval for ExtUvLoop implementation 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); + } }