Skip to content

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
protected function sleep($seconds, $nanoseconds = 0)
{
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.

if ($time == PHP_INT_MAX) {
return 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.

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 WyriHaximus, clue and jsor April 7, 2017 19:28
*/
private static function getMicroseconds($time)
{
if ($time === 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) {
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 $read
* @param array $write
* @param array $except
* @param int|null $seconds
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
protected function streamSelect(array &$read, array &$write, $timeout)
{
$seconds = $timeout === null ? null : self::getSeconds($timeout);
$microseconds = $timeout === null ? 0 : self::getMicroseconds($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 😃

* (float)PHP_INT_MAX == PHP_INT_MAX => true
* (int)(float)PHP_INT_MAX == PHP_INT_MAX => false
* (int)(float)PHP_INT_MAX == PHP_INT_MIN => true
*/
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?

*
* @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.
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