Skip to content
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

Fix possible accept() race condition when multiple socket servers listen on same socket address #244

Merged
merged 1 commit into from
Aug 24, 2020

Conversation

clue
Copy link
Member

@clue clue commented Aug 23, 2020

Multiple socket servers can listen on the same socket address (using
SO_REUSEPORT or shared file descriptors). Accordingly, when a new
connection is incoming, multiple event-loops could report the socket to
be readable and try to a accept() an incoming connection from the same
socket.

This wouldn't be an issue with the underlying accept() system call.
The first call would succeed and subsequent calls would report EAGAIN
or EWOULDBLOCK for the other servers given the socket resource is in
non-blocking mode.

However, PHP's stream_socket_accept() implementation first runs a
poll() on the socket resource before performing an accept(). This
means multiple instances listening on the same address could end up in a
race condition where some may be "stuck" in the pending poll().
See https://github.com/php/php-src/blob/91fbd12d5736b3cc9fc6bc2545e877dd65be1f6c/main/network.c#L707

We work around this by specifiying a 0 timeout to ensure the poll()
doesn't block in this case. This allows all servers to either complete
successfully or report an error and continue processing right away.

Additionally, this also fixes two test failures for PHP 8. Instead of using an invalid (closed) socket server to test error reporting, we now use a socket that doesn't have a pending connection (refs #243). I'll file another PR address the last test failures on PHP 8, but this is currently blocked by #243.

Among others, this is a prerequisite for #164

Multiple socket servers can listen on the same socket address (using
`SO_REUSEPORT` or shared file descriptors). Accordingly, when a new
connection is incoming, multiple event-loops could report the socket to
be readable and try to a `accept()` an incoming connection from the same
socket.

This wouldn't be an issue with the underlying `accept()` system call.
The first call would succeed and subsequent calls would report `EAGAIN`
or `EWOULDBLOCK` for the other servers given the socket resource is in
non-blocking mode.

However, PHP's `stream_socket_accept()` implementation first runs a
`poll()` on the socket resource before performing an `accept()`. This
means multiple instances listening on the same address could end up in a
race condition where some may be "stuck" in the pending `poll()`.

We work around this by specifiying a `0` timeout to ensure the `poll()`
doesn't block in this case. This allows all servers to either complete
successfully or report an error and continue processing right away.
@clue clue added the bug label Aug 23, 2020
@clue clue added this to the v1.6.0 milestone Aug 23, 2020
@clue clue requested a review from jsor August 23, 2020 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants