Improve default StreamSelectLoop
to report any warnings for invalid streams passed to stream_select()
#245
Merged
<
8000
/span>
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This changeset improves the default
StreamSelectLoop
to report any warnings for invalid streams passed tostream_select()
. The underlying function would report errors if any streams can not be used to watch for any changes.This can be trivially reproduced by using a stream with any filters attached:
Likewise, this will now report a warning if the underlying file descriptor (FD) number exceeds
FD_SETSIZE
(defaults to1024
on many platforms). For example, this can be reproduced by running an HTTP benchmark that creates a large number of concurrent connections to a server that allows more than 1024 connections (ulimit
may need to be adjusted first) and relies onStreamSelectLoop
with the above defaults:The documentation already contains instructions to use a different loop implementation that does not impose these limitations in this case: https://github.com/reactphp/event-loop#streamselectloop (via #127)
The previous behavior suppressed any warnings and may result in a busy loop that consumes 100% CPU usage by continuously jumping to the next tick. This has improved slightly with PHP 8+ as the
stream_select()
call would now throw if no valid streams remain in the loop, but similar behavior can still be observed with more (valid) streams attached.The error suppression operator has originally been introduced via #44 a while back in order to suppress any superfluous warnings that occur when the system call is interrupted by a signal (signal handling support). The same warning still triggers on all PHP versions, so the changeset includes additional checks to ignore only this specific warning and report any other warnings as expected. The test suite confirms this has full test coverage and is supported across all supported PHP versions.
Arguably, the default behavior should be to throw an
Exception
if any invalid streams are passed to the loop. However, this would incur a significant BC break. I consider reporting a warning instead of simply "hanging" to be a much better solution and believe this to be the best compromise at this point. If you want to throw anException
in this case, you may register a global error handler that can catch any warnings as expected.I've also performed a number of benchmarks to check what kind of performance implications this changeset has. It seems the impact for normal operation that has any reasonable stream activity is completely negligible. In a worst-case scenario with a single idle stream and a busy loops constantly using
futureTick()
for the next tick shows a ~6% performance degradation. I'm going to argue that better error reporting outweighs this small impact. In either case, I've started looking into an unrelated performance improvement that shows a 100% performance improvement for this specific case, but more on that in a separate PR.I've kept this at two commits to show how ignoring only a specific warning is relatively easy, yet reporting other errors upstream and providing matching test cases is surprisingly non-trivial. You're looking at 8+ hours of work for this PR, enjoy!