Module sched::io_nb
Non-blocking file/socket helpers for the reactor
Thin wrappers around real OS syscalls (open/socket/accept/connect) that enable non-blocking mode, meant to be paired with std::Reactor/ std::TaskScheduler (see reactor.h): a task calls one of these, gets EAGAIN/EWOULDBLOCK back immediately instead of blocking the whole program, and awaits readiness via TaskScheduler::await_fd(fd, SCHED_READ/SCHED_WRITE) instead.
This header declares only the genuinely portable pieces (AF_INET/ SOCK_STREAM/O_RDONLY/O_WRONLY/O_RDWR share the same values across every backend below — a historical BSD-sockets accident every later API, including Winsock, deliberately preserved for source compatibility) and the function signatures every backend implements identically. Anything that actually differs by platform — sockaddr_in's layout (BSD has an extra sin_len byte Linux/Windows don't), O_NONBLOCK's value (or lack of a direct equivalent at all, on Windows), the non-blocking-mode syscall itself (fcntl vs. ioctlsocket) — lives in the matching backend file instead, picked the same way as reactor.h's own backends:
io_nb_bsd.sc — macOS, iOS, FreeBSD
io_nb_linux.sc — Linux, Android
io_nb_win32.sc — Windows (fd_open_nb is best-effort there — see its
own comment; Windows has no non-blocking-file-open
equivalent to O_NONBLOCK, only non-blocking sockets)
Functions
| sched_htons | |
| sched_htonl | |
| sched_ipv4 | Packs four host-order octets (e.g. 127,0,0,1) into a network-order address suitable for a backend's sockaddr_in sin_addr field — avoids callers needing to hand-compute the byte order themselves for the common "literal IP" case. |
| fd_set_nonblocking | Puts an already-open socket fd into non-blocking mode (fcntl+O_NONBLOCK on BSD/Linux, ioctlsocket+FIONBIO on Windows). Returns 0 on success. |
| fd_set_blocking | Puts an already-open socket fd back into blocking mode — the inverse of fd_set_nonblocking, for callers (e.g. std::http, see std/http/http.h) that want simple synchronous request/response semantics on top of tcp_listen_nb/tcp_accept_nb/tcp_connect_nb's portable socket setup rather than pairing them with std::Reactor/std::TaskScheduler. Returns 0 on success. On a socket from tcp_connect_nb, call tcp_wait_connected first (or connect with tcp_connect_timeout instead): Darwin and Winsock fail send/recv with ENOTCONN while the handshake is still in flight instead of waiting for it. |
| fd_open_nb | open() in non-blocking mode where the platform supports it for regular files (BSD/Linux: O_NONBLOCK; Windows: no equivalent — see io_nb_win32.sc). Returns fd, or -1 on failure. |
| tcp_listen_nb | socket()+bind()+listen(), non-blocking. Returns listening fd, or -1. |
| tcp_accept_nb | Non-blocking accept(): returns a connected client fd (itself set non-blocking), or -1 with errno EAGAIN/EWOULDBLOCK if none is pending yet — the caller should await_fd(listenfd, SCHED_READ) and retry. |
| tcp_connect_nb | socket()+connect(), non-blocking: connect() on a non-blocking socket returns immediately with EINPROGRESS rather than waiting for the handshake — the caller should await_fd(fd, SCHED_WRITE) and treat the fd becoming writable as "connect finished (successfully or not; check SO_ERROR if you need to distinguish)". Returns the socket fd, or -1 on immediate failure. |
| tcp_wait_connected | Blocks until a tcp_connect_nb socket's handshake finishes (poll for writable, then SO_ERROR), with no time limit of its own. Returns 0 if connected, -1 if the connect failed (refused, unreachable, ...). For synchronous callers; a std::Reactor task awaits SCHED_WRITE instead. |
| tcp_connect_timeout | socket()+connect() that waits at most 'timeout_ms' (<= 0: no limit beyond the kernel's own SYN retries, ~2 minutes on Linux) for the TCP handshake: non-blocking connect(), poll()/select() for writability, then SO_ERROR. Returns a connected socket already switched back to blocking mode, or -1 with the socket closed and the error left in errno (BSD/Linux) or WSAGetLastError() (Windows): ETIMEDOUT/ WSAETIMEDOUT on timeout, otherwise the connect error itself (ECONNREFUSED, ENETUNREACH, ...). |
| sock_set_io_timeout | Sets SO_RCVTIMEO and SO_SNDTIMEO on a blocking socket, so that a single read/write that makes no progress for 'timeout_ms' fails (-1, EAGAIN/ EWOULDBLOCK, or WSAETIMEDOUT on Windows) instead of blocking forever. It bounds each call, not a whole request. 'timeout_ms' <= 0 clears both timeouts. Returns 0 on success. |
| sock_would_block | True if the most recent non-blocking socket call (accept/recv/send/ connect/...) failed only because it would have blocked — i.e. "not ready yet, try again after await_fd()", not a real error. Checking this is genuinely platform-specific in a way none of the other functions here are: BSD/Linux report it through errno (EAGAIN/EWOULDBLOCK, and std/errno.h's ERRNO_EAGAIN()/ERRNO_EWOULDBLOCK() only recently started giving the right numbers per platform there — see that header), Windows sockets never touch errno at all and report it through a separate per-thread slot, WSAGetLastError() == WSAEWOULDBLOCK (10035). Call this immediately after the failing call, before anything else that might itself set errno/WSAGetLastError() and clobber it. |
Macros
| SCHED_AF_INET | |
| SCHED_SOCK_STREAM | |
| SCHED_O_RDONLY | |
| SCHED_O_WRONLY | |
| SCHED_O_RDWR |