fix(iocp)!: map remote RST to connection_reset, not cond::canceled (#…#319
Conversation
|
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-17 17:12:05 UTC |
bfa1e65 to
fee9573
Compare
|
GCOVR code coverage report https://319.corosio.prtest3.cppalliance.org/gcovr/index.html Build time: 2026-07-17 17:21:45 UTC |
…ppalliance#304) Make cond::canceled mean only local, deliberate cancellation on every backend, and surface remote/connection failures as the portable std::errc condition consistently across epoll/io_uring/kqueue/select/IOCP. Previously the IOCP and kqueue backends diverged from POSIX: - IOCP mapped ERROR_NETNAME_DELETED unconditionally to canceled, so a remote RST looked identical to a local cancel. It is now disambiguated per operation kind in the new iocp_make_err (connection_reset for stream read/write, connection_aborted for accept); make_err no longer folds it into canceled. The cancelled flag, set before the raw error is consulted, keeps genuine local closes (close()/cancel()/stop-token) as canceled. - IOCP surfaced NTSTATUS-derived Win32 socket codes (e.g. ERROR_CONNECTION_REFUSED 1225) that MSVC's system_category does not map to std::errc. iocp_make_err now normalizes the observed ones to their Winsock equivalents (connection_refused, network_unreachable, timed_out) so they compare equal to the portable conditions like POSIX does. - IOCP close_socket() flagged only the aux-reactor wait op cancelled, relying on make_err's old NETNAME_DELETED->canceled fold for the overlapped read/write/connect/accept ops. With that fold gone, close_socket() now request_cancel()s every op before tearing down the handle (as cancel() does), so a local close stays canceled regardless of the CancelIoEx-vs- closesocket race. Applied across tcp, udp, and local-stream services. - kqueue stripped a user-set SO_LINGER before close (a macOS workaround), turning an abortive close into a graceful FIN so the peer saw eof instead of connection_reset. RST is in fact delivered reliably (EV_EOF carries ECONNRESET in fflags), so the strip is removed and SO_LINGER is passed through, matching the other backends. Add test/unit/error_conditions.cpp: a cross-backend audit (run under every backend) asserting the portable contract for the remote/connection error family -- FIN->eof, remote reset is never canceled, write-to-dead-peer is connection_reset|broken_pipe, refused connect is never canceled.
c54648d to
320dee4
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #319 +/- ##
===========================================
+ Coverage 77.79% 77.80% +0.01%
===========================================
Files 96 96
Lines 6985 6984 -1
Branches 1682 1681 -1
===========================================
Hits 5434 5434
Misses 1055 1055
+ Partials 496 495 -1
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
…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.