Skip to content

Fix GH-24171: Slow stream_socket_client() connect on Windows - #24172

Open
vibbow wants to merge 1 commit into
php:PHP-8.4from
vibbow:fix-gh24171-win-connect-so-error
Open

vibbow wants to merge 1 commit into
php:PHP-8.4from
vibbow:fix-gh24171-win-connect-so-error

Conversation

@vibbow

@vibbow vibbow commented Oct 7, 2026

Copy link
Copy Markdown

Fixes #24171.

On Windows, php_network_connect_socket() calls getsockopt(SO_ERROR) right after select() reports that the non-blocking connect has completed. In that window, mswsock's SockIsSocketConnected() can race with mswsock's own async thread and wait on an empty internal completion port with a 10 ms timeout. As a result, a synchronous stream_socket_client()/fsockopen() connect usually takes 10–25 ms instead of ~0.2 ms. The issue has the full analysis.

On Windows we already poll for POLLOUT|POLLPRI, because Winsock reports a successful non-blocking connect in writefds and a failed one in exceptfds (see the existing comment). So when the poll result has POLLOUT and no POLLPRI, the connect has succeeded and getsockopt(SO_ERROR) isn't needed. The failure path (POLLPRI) is unchanged.

Tested on Windows 11 with a minimal NTS x64 build of this branch and of 8.5.11:

  • Sync stream_socket_client() to a LAN host: median 13.8 ms → 0.31 ms.
  • Sync stream_socket_client() on loopback: p95 22.4 ms → 0.43 ms.
  • Refused and timed-out connects report the same errno, with the same timing, as before.
  • Reading and writing on the resulting stream are unaffected.

No .phpt is included: the bug only shows up as latency, and a timing-based test would be flaky on CI.

🤖 Generated with Claude Code

On Windows, php_network_connect_socket() calls getsockopt(SO_ERROR)
right after select() reports that the non-blocking connect completed.
Inside mswsock, SockIsSocketConnected() can then race with mswsock's
own async thread and wait on an internal completion port with a 10 ms
timeout, so the call often blocks for 10-25 ms.

Winsock reports a successful non-blocking connect in writefds and a
failed one in exceptfds, and we already poll for POLLOUT|POLLPRI. So
when only POLLOUT is set, the connect has succeeded and getsockopt()
can be skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant