fix(iocp)!: map remote RST to connection_reset, not cond::canceled (#…#319
Draft
mvandeberg wants to merge 3 commits into
Draft
fix(iocp)!: map remote RST to connection_reset, not cond::canceled (#…#319mvandeberg wants to merge 3 commits into
mvandeberg wants to merge 3 commits into
Conversation
…ppalliance#304) IOCP delivers ERROR_NETNAME_DELETED both when a local closesocket() cancels a pending overlapped op and when the peer hard-closes with a RST. detail::make_err mapped it unconditionally to capy::error::canceled, so a remote reset on Windows surfaced as cond::canceled -- conflating remote termination with deliberate local cancellation. POSIX already mapped RST to errc::connection_reset. The cancelled atomic (set by the stop-token, cancel(), and close() paths) is consulted before the raw error at every decode site, so a surviving ERROR_NETNAME_DELETED is always a genuine remote reset. Remove it from make_err's canceled branch and remap it per operation kind at the IOCP decode sites via a new iocp_make_err helper: connection_reset for stream read/write, connection_aborted for accept (mirroring Asio). make_err stays generic; the winsock-dependent remap lives in the IOCP layer. Add test/unit/error_conditions.cpp: a backend-parameterized audit of the remote/connection error family (FIN->eof, RST->connection_reset, write-to-dead-peer->connection_reset|broken_pipe, connect-refused-> connection_refused), asserting portable conditions on every backend so the cross-platform contract is enforced. The cancellation family is already covered across all backends elsewhere and is not duplicated. normalize_openssl_shutdown_read_error is left unchanged: it already excludes canceled (cppalliance#301), and reset-during-shutdown genuinely means no close_notify was received -> stream_truncated.
|
An automated preview of the documentation is available at https://319.corosio.prtest3.cppalliance.org/index.html If more commits are pushed to the pull request, the docs will rebuild at the same URL. 2026-07-16 23:52:01 UTC |
…iance#304) The kqueue backend stripped a user-set SO_LINGER before close, forcing close() to send FIN instead of RST. A peer parked in read_some then saw a graceful cond::eof instead of errc::connection_reset, diverging from epoll/select/IOCP (the cppalliance#304 cross-backend invariant). The strip was a macOS workaround premised on "RST doesn't reliably trigger EV_EOF, so pending reads hang." A standalone probe on macOS (Darwin 25.5, ARM64) disproves this: an abortive close with SO_LINGER {1,0} reliably wakes the peer's kqueue with EV_EOF, carries ECONNRESET in the kevent fflags, and a subsequent recv() returns ECONNRESET -- 20/20 runs, including the race where the RST lands before EVFILT_READ is registered (level-triggered EV_EOF persists). The scheduler already maps EV_EOF to a read wakeup, so no read hangs, and with l_linger == 0 the abortive close never blocks the caller. Make the kqueue stream_socket_hook a plain option passthrough matching epoll/select. macOS kqueue now reports connection_reset like every other backend; FreeBSD kqueue is expected to follow (BSD kevent fflags carry the socket error identically) and will be confirmed by CI.
mvandeberg
force-pushed
the
pr/304-normalize-errors
branch
from
July 16, 2026 22:55
bfa1e65 to
fee9573
Compare
…ppalliance#304) The remote-connection error family (refused connect, peer RST, write to a dead peer, unreachable host/net, connect timeout) must compare equal to the portable std::errc / capy::cond conditions on every backend. On IOCP it did not, and the failure mode differed by standard library: - MSVC's STL maps the WSA-range codes (WSAECONNRESET 10054, WSAECONNREFUSED 10061, ...) to the std::errc conditions but not their 12xx Win32 counterparts. - libstdc++ (MinGW) does the exact opposite: it maps the Win32 12xx codes (ERROR_CONNECTION_REFUSED 1225, ...) but not the WSA range. So there is NO single native Windows code that std::system_category maps to, say, std::errc::connection_refused on both toolchains. The prior approach of stuffing a WSA code into system_category fixed MSVC but left MinGW CI red (error_conditions.cpp cppalliance#4 reset, cppalliance#7 write-to-dead-peer, cppalliance#9 refused connect), and a Win32 code would fail the reverse way. Fix: in iocp_make_err, return the condition itself via std::make_error_code (generic_category), which compares equal to the matching std::errc on every standard library -- mirroring what the POSIX backends yield from errno. Both the WSA and Win32 forms of each code are accepted as input because the delivered form varies by operation: WSASend/WSARecv completions surface the Winsock code directly (e.g. WSAECONNRESET), while a ConnectEx failure is completed with an NTSTATUS that GetQueuedCompletionStatus reports as the RtlNtStatusToDosError-derived Win32 code (e.g. ERROR_CONNECTION_REFUSED). The normalization stays in the IOCP layer; the platform-neutral make_err is untouched. Trade-off: a generic, less Windows-specific message string. Observed raw codes (this environment): refused connect -> 1225, network unreachable -> 1231, connect timeout -> 121, write to reset peer -> 10054.
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.
…304)
IOCP delivers ERROR_NETNAME_DELETED both when a local closesocket() cancels a pending overlapped op and when the peer hard-closes with a RST. detail::make_err mapped it unconditionally to capy::error::canceled, so a remote reset on Windows surfaced as cond::canceled -- conflating remote termination with deliberate local cancellation. POSIX already mapped RST to errc::connection_reset.
The cancelled atomic (set by the stop-token, cancel(), and close() paths) is consulted before the raw error at every decode site, so a surviving ERROR_NETNAME_DELETED is always a genuine remote reset. Remove it from make_err's canceled branch and remap it per operation kind at the IOCP decode sites via a new iocp_make_err helper: connection_reset for stream read/write, connection_aborted for accept (mirroring Asio). make_err stays generic; the winsock-dependent remap lives in the IOCP layer.
Add test/unit/error_conditions.cpp: a backend-parameterized audit of the remote/connection error family (FIN->eof, RST->connection_reset, write-to-dead-peer->connection_reset|broken_pipe, connect-refused-> connection_refused), asserting portable conditions on every backend so the cross-platform contract is enforced. The cancellation family is already covered across all backends elsewhere and is not duplicated.
normalize_openssl_shutdown_read_error is left unchanged: it already excludes canceled (#301), and reset-during-shutdown genuinely means no close_notify was received -> stream_truncated.