feat(wait): add wait() for socket and acceptor readiness without I/O#251
Conversation
Exposes an asio-style wait(wait_type) awaitable on tcp_socket, udp_socket, local_stream_socket, local_datagram_socket, tcp_acceptor, and local_stream_acceptor. The operation suspends until the descriptor is ready in the chosen direction (read / write / error) without transferring any bytes; useful for wrapping nonblocking C libraries (libssh, libpq async, etc.) that own their own recv/send and only need readiness notification. POSIX backends share a new reactor_wait_op<Base> template that parks in three new wait_*_op slots on reactor_descriptor_state. Dispatch in invoke_deferred_io completes the matching slot on each event bit without performing any I/O syscall. wait_type::write completes immediately on a connected socket on every backend, matching asio's IOCP contract. Corosio's reactor backends use edge-triggered EPOLLOUT/EVFILT_WRITE; parking would never fire on an already-writable socket, and backpressure is surfaced through write_some()'s return value as usual. On IOCP, stream-socket wait_read uses a zero-byte WSARecv. All other waits (datagram wait_read, acceptor wait_read, every wait_error) route through a new win_wait_reactor: a dedicated WSAPoll thread woken via a loopback Winsock socket pair, bridging readiness into the IOCP queue via PostQueuedCompletionStatus. The reactor is lazily constructed via std::call_once and stopped early in win_scheduler::shutdown() so parked ops do not block work-counter drain. socket.cancel() / close_socket() / shutdown all route through the same cancel_wait_if_constructed path so reactor-parked ops are cleaned up without forcing reactor construction on hot paths that never used it. Doxygen on every new public symbol; new Antora guide page (4r.wait.adoc) covers the readiness pattern, acceptor semantics, cancellation, and the wait_type::write immediate-ready contract. Tests parameterised across backends cover the no-consume promise on TCP, immediate write completion, local_stream wait_read (POSIX), UDP wait_read (exercises the aux reactor on IOCP), cancellation through the standard reactor (wait_read on TCP), cancellation while parked in the aux reactor (UDP wait_read), and acceptor wait_read. wait_type::error is plumbed but unexercised by tests; kernel error semantics are non-portable and the contract is documented as best-effort.
|
An automated preview of the documentation is available at https://251.corosio.prtest3.cppalliance.org/index.html If more commits are pushed to the pull request, the docs will rebuild at the same URL. 2026-05-19 19:38:37 UTC |
|
GCOVR code coverage report https://251.corosio.prtest3.cppalliance.org/gcovr/index.html Build time: 2026-05-19 19:46:53 UTC |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #251 +/- ##
===========================================
+ Coverage 77.70% 77.76% +0.06%
===========================================
Files 96 96
Lines 7292 7264 -28
Branches 1787 1775 -12
===========================================
- Hits 5666 5649 -17
+ Misses 1108 1104 -4
+ Partials 518 511 -7
Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
Exposes an asio-style wait(wait_type) awaitable on tcp_socket, udp_socket, local_stream_socket, local_datagram_socket, tcp_acceptor, and local_stream_acceptor. The operation suspends until the descriptor is ready in the chosen direction (read / write / error) without transferring any bytes; useful for wrapping nonblocking C libraries (libssh, libpq async, etc.) that own their own recv/send and only need readiness notification.
POSIX backends share a new reactor_wait_op template that parks in three new wait_*_op slots on reactor_descriptor_state. Dispatch in invoke_deferred_io completes the matching slot on each event bit without performing any I/O syscall.
wait_type::write completes immediately on a connected socket on every backend, matching asio's IOCP contract. Corosio's reactor backends use edge-triggered EPOLLOUT/EVFILT_WRITE; parking would never fire on an already-writable socket, and backpressure is surfaced through write_some()'s return value as usual.
On IOCP, stream-socket wait_read uses a zero-byte WSARecv. All other waits (datagram wait_read, acceptor wait_read, every wait_error) route through a new win_wait_reactor: a dedicated WSAPoll thread woken via a loopback Winsock socket pair, bridging readiness into the IOCP queue via PostQueuedCompletionStatus. The reactor is lazily constructed via std::call_once and stopped early in win_scheduler::shutdown() so parked ops do not block work-counter drain. socket.cancel() / close_socket() / shutdown all route through the same cancel_wait_if_constructed path so reactor-parked ops are cleaned up without forcing reactor construction on hot paths that never used it.
Doxygen on every new public symbol; new Antora guide page (4r.wait.adoc) covers the readiness pattern, acceptor semantics, cancellation, and the wait_type::write immediate-ready contract.
Tests parameterised across backends cover the no-consume promise on TCP, immediate write completion, local_stream wait_read (POSIX), UDP wait_read (exercises the aux reactor on IOCP), cancellation through the standard reactor (wait_read on TCP), cancellation while parked in the aux reactor (UDP wait_read), and acceptor wait_read. wait_type::error is plumbed but unexercised by tests; kernel error semantics are non-portable and the contract is documented as best-effort.