Repository navigation
Conversation
Under Workerman\Events\Swoole (SWOOLE_HOOK_ALL) stream_socket_client() in
AsyncTcpConnection::connect() and stream_socket_enable_crypto() in
TcpConnection::doSslHandshake() yield to other coroutines: the hooked connect is
blocking (DNS lookup included) and the handshake waits for the peer. The
set_error_handler() installed around them is process-wide, so while the call is
suspended every warning raised by any other coroutine goes to it instead of the
application's handler:
- in connect() those warnings are silently dropped (fn() => false);
- in doSslHandshake() they are swallowed and the last one becomes the reported
handshake failure reason ("SSL handshake with x failed: Undefined array key ...").
The hooked stream_socket_enable_crypto() reports failures to Swoole's own log, not
as PHP warnings, so under Swoole that handler can only ever catch other
coroutines' warnings. Use "@" there instead (Swoole saves the silence state per
coroutine). connect() moves the socket creation into openSocket() so both paths
share it. Every other event loop keeps the exact previous behaviour.
Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Problem
Under
Workerman\Events\Swoole, warnings raised by other coroutines bypass the application's error handler while a connection is insideAsyncTcpConnection::connect()orTcpConnection::doSslHandshake(). With a framework handler that turns warnings intoErrorException(webman, Laravel, ...), code like$data['missing']in an unrelated request silently returnsnullinstead of throwing — but only while some other coroutine happens to be connecting or handshaking, so behaviour changes with concurrency.In
doSslHandshake()it is worse: the foreign warning is captured as$reason, so a failed handshake is reported as e.g.SSL handshake with 127.0.0.1:19443 failed: Undefined array key "missing".We hit the first case in production: warnings from request code showed up as raw PHP warnings only on the Swoole pods, during windows where an
AsyncTcpConnectionwas connecting to an unresponsive upstream (5 s+).Root cause
Workerman\Events\SwooleenablesSWOOLE_HOOK_ALL. With the hook:stream_socket_client(..., STREAM_CLIENT_ASYNC_CONNECT)becomes a blocking connect (DNS lookup included) that yields to other coroutines;stream_socket_enable_crypto()yields while waiting for the peer.The
set_error_handler()installed around both calls is process-wide, not per coroutine, so for the whole suspended period it receives every warning raised by every other coroutine.Additionally, the hooked
stream_socket_enable_crypto()does not raise PHP warnings on failure at all; it writes to Swoole's own log instead (PHP 8.2.16 / Swoole 6.2.2):stream_socket_enable_crypto(): SSL: Handshake timed outssl_connect(...) failed ... unexpected eof while readingstream_socket_enable_crypto(): SSL operation failed with code 1. ...ssl_connect(...) failed ... sslv3 alert handshake failureSo under Swoole the handler in
doSslHandshake()can only ever catch warnings from other coroutines.Fix
Only when the event loop is
Workerman\Events\Swoole, use@instead ofset_error_handler()around those two calls. Swoole saves and restores the@silence state per coroutine, so it does not leak into other coroutines. Every other event loop keeps the exact previous code path (same handler, same reason capture).To avoid duplicating the five
stream_socket_client()branches,connect()moves the socket creation into a privateopenSocket()that both paths call; its body is unchanged apart from early returns.Reproduction
Results on PHP 8.2.16 / Swoole 6.2.2 / workerman
master(8fd5c14):handshake window: converted (ok)
SSL handshake with 127.0.0.1:19443 failedhandshake window: BYPASSED
SSL handshake with 127.0.0.1:19443 failed: Undefined array key "missing"handshake window: converted (ok)
SSL handshake with 127.0.0.1:19443 failedAlso checked on both loops after the change: a refused connect (
tcp://127.0.0.1:1) is still reported throughonErrorand nothing is thrown out ofconnect()by a throwing app handler; a realssl://example.com:443request still returns200 OK.vendor/bin/pest: 247 passed, same as master; phpstan: no errors on both files.Notes
stream_select()inset_error_handler()), but that is outside workerman.connect()could stay inline instead of introducingopenSocket(); I extracted it only to avoid duplicating the fivestream_socket_client()calls.