8000 Merge pull request #196 from PabloKowalczyk/interval-overflow-fix · reactphp/event-loop@85a0b7c · GitHub
[go: up one dir, main page]

Skip to content

Commit 85a0b7c

Browse files
authored
Merge pull request #196 from PabloKowalczyk/interval-overflow-fix
Prevent "interval" overflow in ExtUvLoop
2 parents babf91e + a3165da commit 85a0b7c

File tree

4 files changed

+125
-4
lines changed

4 files changed

+125
-4
lines changed

src/ExtUvLoop.php

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public function addTimer($interval, $callback)
127127
$this->timers->attach($timer, $event);
128128
\uv_timer_start(
129129
$event,
130-
(int) ($interval * 1000) + 1,
130+
$this->convertFloatSecondsToMilliseconds($interval),
131131
0,
132132
$callback
133133
);
@@ -146,12 +146,13 @@ public function addPeriodicTimer($interval, $callback)
146146
\call_user_func($timer->getCallback(), $timer);
147147
};
148148

149+
$interval = $this->convertFloatSecondsToMilliseconds($interval);
149150
$event = \uv_timer_init($this->uv);
150151
$this->timers->attach($timer, $event);
151152
\uv_timer_start(
152153
$event,
153-
(int) ($interval * 1000) + 1,
154-
(int) ($interval * 1000) + 1,
154+
$interval,
155+
(int) $interval === 0 ? 1 : $interval,
155156
$callback
156157
);
157158

@@ -313,4 +314,26 @@ private function createStreamListener()
313314

314315
return $callback;
315316
}
317+
318+
/**
319+
* @param float $interval
320+
* @return int
321+
*/
322+
private function convertFloatSecondsToMilliseconds($interval)
323+
{
324+
if ($interval < 0) {
325+
return 0;
326+
}
327+
328+
$maxValue = (int) (\PHP_INT_MAX / 1000);
329+
$intInterval = (int) $interval;
330+
331+
if (($intInterval <= 0 && $interval > 1) || $intInterval >= $maxValue) {
332+
throw new \InvalidArgumentException(
333+
"Interval overflow, value must be lower than '{$maxValue}', but '{$interval}' passed."
334+
);
335+
}
336+
337+
return (int) \floor($interval * 1000);
338+
}
316339
}

tests/AbstractLoopTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,9 +591,11 @@ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop()
591591

592592
public function testTimerIntervalCanBeFarInFuture()
593593
{
594+
// Maximum interval for ExtUvLoop implementation
595+
$interval = ((int) (PHP_INT_MAX / 1000)) - 1;
594596
$loop = $this->loop;
595597
// start a timer very far in the future
596-
$timer = $this->loop->addTimer(PHP_INT_MAX, function () { });
598+
$timer = $this->loop->addTimer($interval, function () { });
597599

598600
$this->loop->futureTick(function () use ($timer, $loop) {
599601
$loop->cancelTimer($timer);

tests/ExtUvLoopTest.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,82 @@ public function createLoop()
1414

1515
return new ExtUvLoop();
1616
}
17+
18+
/** @dataProvider intervalProvider */
19+
public function testTimerInterval($interval, $expectedExceptionMessage)
20+
{
21+
$this->expectException('InvalidArgumentException');
22+
$this->expectExceptionMessage($expectedExceptionMessage);
23+
24+
$this->loop
25+
->addTimer(
26+
$interval,
27+
function () {
28+
return 0;
29+
}
30+
);
31+
}
32+
33+
public function intervalProvider()
34+
{
35+
$oversizeInterval = PHP_INT_MAX / 1000;
36+
$maxValue = (int) (PHP_INT_MAX / 1000);
37+
$oneMaxValue = $maxValue + 1;
38+
$tenMaxValue = $maxValue + 10;
39+
$tenMillionsMaxValue = $maxValue + 10000000;
40+
$intMax = PHP_INT_MAX;
41+
$oneIntMax = PHP_INT_MAX + 1;
42+
$tenIntMax = PHP_INT_MAX + 10;
43+
$oneHundredIntMax = PHP_INT_MAX + 100;
44+
$oneThousandIntMax = PHP_INT_MAX + 1000;
45+
$tenMillionsIntMax = PHP_INT_MAX + 10000000;
46+
$tenThousandsTimesIntMax = PHP_INT_MAX * 1000;
47+
48+
return array(
49+
array(
50+
$oversizeInterval,
51+
"Interval overflow, value must be lower than '{$maxValue}', but '{$oversizeInterval}' passed."
52+
),
53+
array(
54+
$oneMaxValue,
55+
"Interval overflow, value must be lower than '{$maxValue}', but '{$oneMaxValue}' passed.",
56+
),
57+
array(
58+
$tenMaxValue,
59+
"Interval overflow, value must be lower than '{$maxValue}', but '{$tenMaxValue}' passed.",
60+
),
61+
array(
62+
$tenMillionsMaxValue,
63+
"Interval overflow, value must be lower than '{$maxValue}', but '{$tenMillionsMaxValue}' passed.",
64+
),
65+
array(
66+
$intMax,
67+
"Interval overflow, value must be lower than '{$maxValue}', but '{$intMax}' passed.",
68+
),
69+
array(
70+
$oneIntMax,
71+
"Interval overflow, value must be lower than '{$maxValue}', but '{$oneIntMax}' passed.",
72+
),
73+
array(
74+
$tenIntMax,
75+
"Interval overflow, value must be lower than '{$maxValue}', but '{$tenIntMax}' passed.",
76+
),
77+
array(
78+
$oneHundredIntMax,
79+
"Interval overflow, value must be lower than '{$maxValue}', but '{$oneHundredIntMax}' passed.",
80+
),
81+
array(
82+
$oneThousandIntMax,
83+
"Interval overflow, value must be lower than '{$maxValue}', but '{$oneThousandIntMax}' passed.",
84+
),
85+
array(
86+
$tenMillionsIntMax,
87+
"Interval overflow, value must be lower than '{$maxValue}', but '{$tenMillionsIntMax}' passed.",
88+
),
89+
array(
90+
$tenThousandsTimesIntMax,
91+
"Interval overflow, value must be lower than '{$maxValue}', but '{$tenThousandsTimesIntMax}' passed.",
92+
),
93+
);
94+
}
1795
}

tests/Timer/AbstractTimerTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,22 @@ public function testMinimumIntervalOneMicrosecond()
137137

138138
$this->assertEquals(0.000001, $timer->getInterval());
139139
}
140+
141+
public function testTimerIntervalBelowZeroRunsImmediately()
142+
{
143+
$loop = $this->createLoop();
144+
$start = 0;
145+
$loop->addTimer(
146+
-1,
147+
function () use (&$start) {
148+
$start = \microtime(true);
149+
}
150+
);
151+
152+
$loop->run();
153+
$end = \microtime(true);
154+
155+
// 1ms should be enough even on slow machines
156+
$this->assertLessThan(0.001, $end - $start);
157+
}
140158
}

0 commit comments

Comments
 (0)
0