Uh oh!
There was an error while loading. Please reload this page.
Prevent "interval" overflow in ExtUvLoop - #196
Conversation
WyriHaximus
commented
May 20, 2019
@PabloKowalczyk looking into why Travis lost track of the required UV dependencies |
PabloKowalczyk
commented
May 20, 2019
@WyriHaximus command |
WyriHaximus
commented
May 20, 2019
@PabloKowalczyk yeah most likely, but ensure why that is an issue all of the sudden |
WyriHaximus
commented
May 23, 2019
@PabloKowalczyk just filed #197 to fix this issue. |
PabloKowalczyk
commented
May 23, 2019
@WyriHaximus good job, after your merge #197 i will rebase my branch and push changes again. |
WyriHaximus
commented
May 23, 2019
@PabloKowalczyk FYI #197 has been merged |
754fae7 to
4e32262ComparePabloKowalczyk
commented
May 24, 2019
Rebase done, but there is other issue: IMO it is not caused by my change, i can't reproduce it locally and seems "random" to me. What do you think @WyriHaximus? |
WyriHaximus
commented
May 24, 2019
@PabloKowalczyk sometimes we have to kick the build to fix that error ;) |
PabloKowalczyk
commented
May 24, 2019
Yep, now better :) |
clue
left a comment
There was a problem hiding this comment.
@PabloKowalczyk Thank you for looking into this and keeping this updated!
I've added a couple of remarks below, what do you think about this? 👍
Uh oh!
There was an error while loading. Please reload this page.
| } | ||
| // Subtract 2 because 1 doesn't seems to work and may give wrong results | ||
| $maxValue = ((int) (\PHP_INT_MAX / 1000)) - 2; |
There was a problem hiding this comment.
This calculation looks really odd, i.e. it doesn't explain why or what "wrong results" could be. As an alternative, what do you think about something like this:
$result = $interval * 1000;
if ($result > PHP_INT_MAX) {
// nope
}
return (int)$result;There was a problem hiding this comment.
Your code will work only if $result will be about ~1025 more than PHP_INT_MAX,
check this example: https://3v4l.org/OH3T7
There was a problem hiding this comment.
You're right. PHP will automatically convert an int on overflow to a float, but PHP_INT_MAX + 1 > PHP_INT_MAX === false. If you cast this float value back to int, you'll get an integer overflow and a negative value in return. Here's the gist:
$ms = (int)($interval * 1000);
if ($ms < 0 || $interval > \PHP_INT_MAX) {
// nope
}
return$ms;There was a problem hiding this comment.
Look at this code: https://3v4l.org/WcQBR - should it output "Ok"?
There was a problem hiding this comment.
Are we talking about the boundary value? I don't really mind tbqh. We're discussing integer overflows that happen for timer intervals of round ~25 days on 32 bit platforms and millions of years on 64 bit platforms, so I don't think it's really relevant if it's +/-1 ms. What I do care about is that the outcome is consistent and reliable.
I'm not suggesting this solution is perfect, I was mostly approaching it from a different perspective because the original implementation contained an apparently arbitrary -2 offset.
Either solution is fine for me as long as it works reliably 👍
There was a problem hiding this comment.
I see you point of view, we could move -2-responsibility to user and do not bother with it in implementation - https://3v4l.org/AADvB, but we still need to do (PHP_INT_MAX / 1000) - 2 in tests (\React\Tests\EventLoop\AbstractLoopTest::testTimerIntervalCanBeFarInFuture) - maybe with extensive comment about -2-issue.
On the other hand i'm completely fine with leaving current implementation as is, so it is up to you to choose the best for you/project/users/etc.
There was a problem hiding this comment.
The maximum working interval is (int) (\PHP_INT_MAX / 1000) - 1. If the value is higher, you can simply reject it before doing any conversion.
| private function convertFloatSecondsToMilliseconds($interval, $allowZero = false) | ||
| { | ||
| if ($interval < 0) { | ||
| $interval = 0; |
There was a problem hiding this comment.
Might as well do a return here instead of going through all these calculations?
There was a problem hiding this comment.
It could return early only if i duplicate $allowZero-related logic from last line:
return$result === 0 && !$allowZero
? 1
: $result
;So new code will look like:
if ($interval < 0) {
return$allowZero
? 0
: 1
;
}IMO it is better to leave it as is.
There was a problem hiding this comment.
I'm not sure for what it even is? Libuv allows 0, so differentiating between 0 and 1 is imo unnecessary.
There was a problem hiding this comment.
I must admit i don't know, but my guess is addPeriodicTimer with 0 will run code only once, also adding 1 was part of old code: https://github.com/reactphp/event-loop/blob/v1.1.0/src/ExtUvLoop.php#L130 and https://github.com/reactphp/event-loop/blob/v1.1.0/src/ExtUvLoop.php#L153-L154
There was a problem hiding this comment.
We only need a non-zero repeat (periodic timer). And then it'd be better to do an inline check there, so it only affects the repeat value and not the timeout value.
There was a problem hiding this comment.
Good catch, actually it makes conversion easier. Please check new code.
| $maxValue = (int) (\PHP_INT_MAX / 1000); | ||
| $intInterval = (int) $interval; | ||
| if ($intInterval >= $maxValue) { |
There was a problem hiding this comment.
I've just tested this and unfortunately there's an edge case, which makes the comparison fail, even though the interval is significantly larger (as float) than the PHP integer max value.
Based on https://3v4l.org/TIkG3, the suggestion should work around that. You should probably add test cases for these.
| if ($intInterval >= $maxValue) { | |
| if ($interval > \PHP_INT_MAX || ($intInterval <= 0 && $interval > 0)) { |
There was a problem hiding this comment.
You are right, fixed and tested.
ghost
commented
Jul 10, 2019
@WyriHaximus Can you kick the build again please? :) https://travis-ci.org/reactphp/event-loop/jobs/543631451 |
WyriHaximus
commented
Jul 10, 2019
@CharlotteDunois done, thanks for the heads up 👍 |
ghost
commented
Jul 10, 2019
@clue Could you please review the PR again? From my side it looks from a quick glance ok. |
clue
left a comment
There was a problem hiding this comment.
Thank you for the update, the changes LGTM! Can you squash this to a reasonable number of commits before we can merge this? ![]()
PabloKowalczyk
commented
Jul 11, 2019
@clue should i do the squash? You can also do |
WyriHaximus
commented
Jul 26, 2019
@PabloKowalczyk yes please. |
730109f to
5d2e219ComparePabloKowalczyk
commented
Jul 27, 2019
Done. |
| $this->loop->addTimer(-1, function () {}); | ||
| $this->assertRunFasterThan(0.002); | ||
| } |
There was a problem hiding this comment.
We currently have this odd split in our tests suite, can you move this to the similar tests in AbstractTimerTests?
Other than that, this test looks good to me, but we've seen similar tests fail sporadically due to timer inaccuracies, see also
event-loop/tests/Timer/AbstractTimerTest.php
Lines 28 to 45 in babf91e
Perhaps you can update this to use a similar logic to make sure an inaccurate platform does not cause this test to fail?
7ec5707 to
a032124Compare…t to AbstractTimerTest
a032124 to
a3165daCompare
clue
left a comment
There was a problem hiding this comment.
Changes LGTM, thanks for keeping up with this! ![]()
WyriHaximus
commented
Aug 12, 2019
Thanks 👍 ! |
This code introduced in reactphp#196 relied on behavior that has been changed in PHP8.5 by: https://wiki.php.net/rfc/warnings-php-8-5#casting_out_of_range_floats_to_int
This code introduced in reactphp#196 relied on behavior that has been changed in PHP8.5 by: https://wiki.php.net/rfc/warnings-php-8-5#casting_out_of_range_floats_to_int
This PR fixes#194.