Skip to content

StreamSelectLoop: Int overflow for big timer intervals #96

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

ivkalita
Copy link
Contributor

@ivkalita ivkalita commented Apr 5, 2017

The problem

As described here, current StreamSelectLoop implementation restricts using time intervals more than PHP_INT_MAX microseconds. For 32-bit systems it's only 2148 seconds.

Suggested solution

  • Change StreamSelectLoop $timeout type from int (in microseconds) to float (in seconds);
  • Replace usleep with time_nanosleep, cause usleep takes int argument in microseconds, and we can't keep more than PHP_INT_MAX microseconds. But time_nanosleep takes 2 arguments: int amount of seconds and int amount of nanoseconds, that allows StreamSelectLoop to handle PHP_INT_MAX seconds intervals;
  • Extract stream_select and time_nanosleep methods calls from StreamSelectLoop::streamSelect(...) to test timeout values.

@ivkalita ivkalita force-pushed the stream-select-loop-int-overflow branch from 31a613a to a4bf806 Compare April 5, 2017 11:27
{
if ($seconds > 0 || $nanoseconds > 0) {
time_nanosleep($seconds, $nanoseconds);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see explicit handling of $secondsor $nanosecondsbeing null here.

return PHP_INT_MAX;
}

return $time === null ? null : intval(floor($time));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpicking: for consistency with the other 2 methods, i'd like to an early return for $time === null:

if ($time === null) {
    return null;
}

// ...

return intval(floor($time));

@ivkalita ivkalita force-pushed the stream-select-loop-int-overflow branch from a4bf806 to 08c709b Compare April 5, 2017 21:07
@WyriHaximus WyriHaximus requested review from jsor, WyriHaximus and clue April 7, 2017 19:28
private static function getMicroseconds($time)
{
if ($time === null) {
return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably return 0. stream_select accepts null only for the $seconds parameter.

private static function getNanoseconds($time)
{
if ($time === null) {
return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably return 0, see above.

* @param array $write
* @param array $except
* @param int|null $seconds
* @param int|null $microseconds
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should only accept int, see also above.

@ivkalita
Copy link
Contributor Author

ivkalita commented Apr 10, 2017

@jsor, thank you for the review. I think that previous implementation was not explicit, cause $timeout checking for null value was duplicated in 4 places (getSeconds, getMicroseconds, getNanoseconds and sleep). I changed this behavior in 4347fa3: I removed all unnecessary null checks and moved them inside streamSelect method. Also, I fixed the things you pointed ;)

@ivkalita ivkalita force-pushed the stream-select-loop-int-overflow branch from c78624d to 4347fa3 Compare April 10, 2017 18:59
*
* @return integer|false The total number of streams that are ready for read/write.
* Can return false if stream_select() is interrupted by a signal.
*/
protected function streamSelect(array &$read, array &$write, $timeout)
{
$seconds = $timeout === null ? null : self::getSeconds($timeout);
$microseconds = $timeout === null ? 0 : self::getMicroseconds($timeout);
$nanoseconds = $timeout === null ? 0 : self::getNanoseconds($timeout);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calculation of $microseconds and $nanoseconds can probably be moved into the ifs so they are only calculated when needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsor, done in bb81383 😃

* (int)(float)PHP_INT_MAX == PHP_INT_MAX => false
* (int)(float)PHP_INT_MAX == PHP_INT_MIN => true
*/
if ($time == PHP_INT_MAX) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strict comparision (===)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsor, $time is float, so strict comparison will always return false.

(float) PHP_INT_MAX === PHP_INT_MAX; //is false
(float) PHP_INT_MAX == PHP_INT_MAX; //is true

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 (maybe add a comment that loose comparision is intentional)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsor I added more description about this tricky if in 3c8a5d6 😄

Copy link
Member

@jsor jsor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :shipit:

Copy link
Member

@WyriHaximus WyriHaximus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :shipit:

@jsor
Copy link
Member

jsor commented Apr 25, 2017

ping @clue

@ivkalita
Copy link
Contributor Author

@clue, can you please describe, why did you mark this PR as BC Break? Imo, only internal representation of time changed and no public interfaces modified...

Copy link
Member

@clue clue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for filing this PR! These are some high quality changes and I'd actually like to get this in to solve this rare issue 👍

The issue only affects 32 bit systems, yet this PR proposes some major changes to the whole StreamSelectLoop, which contains some very performance-critical code.

Does it make sense to perform some benchmarks to check its impact?

* Emulate a stream_select() implementation that does not break when passed
* empty stream arrays.
*
* @param array &$read An array of read streams to select upon.
* @param array &$write An array of write streams to select upon.
* @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
* @param float|null $timeout Activity timeout in seconds, or null to wait forever.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a rather subtle BC break as consumers could possibly rely on this protected API. Not saying this is an issue with your proposed change, it's more that we screwed up could have done better with defining our external API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
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