1#include <cx/net/net_shared.h>
3#include <cx/net/pool.cxh>
5/// @addtogroup net_queue
7/// NetQueue is an abstraction for servicing one or more sockets using platform-specific
8/// backends. Sockets can be added to a queue to handle I/O operations efficiently. The
9/// queue can be operated in single-threaded polled mode or with a pool of worker threads
10/// for asynchronous operation.
12/// NetQueue manages one or more sockets and a thread pool of workers
13abstract class NetQueue
15 NetQueueConfig conf; ///< Configuration settings for this queue
17 /// @brief Queue-wide fallback handlers
19 /// The last level of the per-field fallthrough described in @ref net_handlers, and generally
20 /// the right place for logging and error handling while sockets and flows override the
21 /// events they actually care about.
23 void *handlerCtx; ///< Context passed to queue-wide handlers, set by setHandlers()
25 /// @brief Context passed to queue-wide handlers, set by setHandlersObj() -- NULL when
26 /// handlerCtx is in use instead
27 weak[ObjInst] handlerWeak;
29 /// @brief Guards handlers/handlerCtx/handlerWeak against a concurrent setHandlers()/setHandlersObj()
32 /// @brief Sockets this queue is managing, keyed by pointer identity
33 hashtable[ptr,object] sockets;
35 RWLock lock; ///< Protects the socket hash
37 /// @brief Flows with work pending, waiting for a worker to claim them
39 /// One instance per queue, safe for many ingest threads to push to and many workers to pop
40 /// from at once. Holds its own strong reference to a queued flow, so closing a socket can't
41 /// free a flow out from under a worker that's mid-batch on it.
44 /// @brief Shared, capped storage every NetMessage on this queue is drawn from
46 /// A separate object rather than a member so that flows and filter stages -- which can be torn
47 /// down holding messages at a point where the queue is no longer reachable from them -- can
48 /// keep their own reference and always have somewhere to return a buffer. See @ref net_pool.
51 /// @brief Timestamp of the last opportunistic GC pass, used to rate-limit it
53 /// The runqueue and receive pool grow under load and only give that memory back when
54 /// prqCollect() runs; netqueue_maint() runs it periodically from whichever thread just
55 /// drained the runqueue and is about to go idle.
56 atomic[uint32] gcLastLo;
58 /// @brief Datagrams dropped for lack of a receive buffer
60 /// A silent drop looks just like a flaky network. A nonzero counter turns "the network seems
61 /// unreliable" into "the buffer pool is too small or a callback is too slow" right away.
62 atomic[uint32] droppedNoBuf;
64 atomic[uint32] nflows; ///< Flows currently live across every socket on this queue
66 /// @brief Armed timers across every flow on this queue, kept as a min-heap ordered by deadline
68 /// Every entry holds a strong reference to its flow.
69 NetTimerEntry *timers;
71 uint32 ntimers; ///< Entries in use in `timers`
72 uint32 timersCap; ///< Entries allocated in `timers`
73 uint64 timerSerial; ///< Source of NetTimerId values; never reused, never 0
75 /// @brief Maps a timer id to its current index in `timers`
76 hashtable[uint64,uint32] timerIdx;
78 /// @brief Guards the timer heap and every flow's list of armed timer ids
80 /// Lock order is connectLock -> timerLock, never the reverse. Never held during a callback.
83 /// @brief Number of armed timers, readable without taking `timerLock`
84 atomic[uint32] timersLive;
86 /// @brief Dispatch worker threads draining the runqueue (empty in polled mode)
88 /// Each worker blocks on runqSema and drains the runqueue through netqueue_dispatch(). A
89 /// backend that merges ingest and dispatch into one wait call (IOCP) manages its own threads
90 /// instead and leaves this empty. A nonzero count means threaded mode.
91 [noinit] sarray[Thread] workers;
93 /// @brief Posted once per flow enqueued; dispatch workers wait on it when the runqueue is dry
94 [noinit] Semaphore runqSema;
96 /// @brief Set when shutdown begins, so ingest stops producing and addSocket is refused
97 atomic[uint32] shutdownReq;
99 /// @brief Backend hook invoked when the send path leaves outbound data queued on a socket
101 /// netsocketSend() calls this after its own flush when data couldn't all go out at once, so
102 /// the backend can arrange for the rest to drain -- a readiness backend (select) wakes its
103 /// ingest thread to add write interest, a completion backend (IOCP) posts an overlapped send.
104 /// NULL in polled mode, where tick() rebuilds the watch set every call. See NetSendPumpFn.
105 NetSendPumpFn sendPump;
106 void *sendCtx; ///< Context passed to sendPump, normally the derived queue
108 /// @brief Backend hook that wakes a parked wait so it can recompute its timeout
110 /// Called when a timer is armed with a deadline nearer than the one the backend is currently
111 /// waiting on. Set even in polled mode. NULL if the backend has no wait to interrupt.
113 void *wakeCtx; ///< Context passed to wake, normally the derived queue
115 /// Add a socket to be managed by the queue
117 /// The socket must be compatible with the NetQueue implementation. The netqueueSocket()
118 /// factory can be used to create compatible sockets.
120 /// @param socket Socket to add to the queue
121 /// @return true if the socket was successfully added, false otherwise
122 bool addSocket(NetSocket *socket);
124 /// Remove a socket from the queue, usually when the socket is closed
126 /// For some implementations this will also attempt to cancel pending I/O requests that
127 /// reference the socket's owned buffers.
129 /// @param socket Socket to remove from the queue
130 /// @return true if the socket was successfully removed, false otherwise
131 bool removeSocket(NetSocket *socket);
133 /// Factory for creating sockets
135 /// This does NOT add the socket to the queue.
137 /// @param type Type of socket to create (connection-oriented or connectionless)
138 /// @return Newly created socket, or NULL on failure
139 [abstract] NetSocket *socket(NetSocketType type);
141 // Begin one outbound connect attempt against a single resolved address
143 // The one backend-specific piece of connect. By the time this is called, the portable state
144 // machine has already picked the address and armed the deadline; this just gets a fresh OS
145 // handle of the address's family and starts the attempt. A readiness backend resets the
146 // handle, issues a non-blocking connect(), and watches it for writability (and the except
147 // set, where Windows signals a failed connect). A completion backend resets and binds the
148 // handle and posts an overlapped ConnectEx.
150 // The outcome is reported back through netsocket_connectResult() -- immediately for a
151 // synchronous result, or later from readiness, a completion, or the timeout sweep.
153 // @param sock Socket to connect (a stream socket in NS_Connecting)
154 // @param addr Address to attempt
155 // @return true if the attempt was initiated or resolved, false if it could not be started
156 [abstract] bool connectBegin(NetSocket* sock, const NetAddr* addr);
158 // React to a socket having just become connected
160 // Called once, after a successful connect has transitioned the socket to NS_Connected and
161 // its NET_Connection event has been queued. The backend starts servicing the socket for
162 // receive here -- select rebuilds its watch set to read-watch it, IOCP posts the first
163 // WSARecv -- the same thing addSocket() does for a socket that was already connected when it
166 // @param sock Socket that just connected
167 [abstract] void connectArm(NetSocket* sock);
169 // Begin (or resume) accepting connections on a now-listening socket
171 // The accept-side counterpart of connectArm. Called once the socket enters NS_Listening --
172 // from the platform listen(), or from addSocket() if the socket was already listening when
173 // it joined the queue. A readiness backend wakes its loop so the next rebuild read-watches
174 // the listener; a completion backend posts its initial batch of overlapped AcceptEx
175 // operations. Each accepted connection is wrapped and handed to netsocket_accepted().
177 // @param sock Listening socket (a stream socket in NS_Listening)
178 [abstract] void acceptArm(NetSocket* sock);
180 /// Shut down the queue
182 /// Removes all sockets from the queue and shuts down worker threads. Will shut down
183 /// the queue in the background even if false is returned.
185 /// @param timeout How long to wait for workers to terminate, in microseconds (see timeS() /
186 /// timeMS()), or 0 or less to wait forever
187 /// @return true if the queue is fully shut down
188 bool shutdown(int64 timeout);
190 /// Process events in polled mode
192 /// Only for a queue created with nthreads set to 0. A queue with worker threads already does
193 /// this work on its own threads, and calling tick() on one puts two threads in the same place.
195 /// @param wait How long to wait for an event, in microseconds (see timeS() / timeMS()), 0 to
196 /// return immediately, or timeForever to wait until something happens. A wait of under a
197 /// millisecond is rounded up to one on some platforms, so treat it as a lower bound.
198 /// @return true if any events were processed, false if the wait timeout was reached
199 [abstract] bool tick(int64 wait);
201 /// Fill in a configuration suited to a client or a single connection
203 /// Polled mode, a small buffer pool, and few flows. Nothing here starts a thread, so a
204 /// program that drives its own loop with netqueueTick() never spawns one.
206 /// @param conf Configuration structure to populate
207 standalone void presetClient([out] NetQueueConfig *conf);
209 /// Fill in a configuration suited to a server
211 /// A worker pool sized to the machine, a large buffer pool, and a high flow cap. Suitable
212 /// as-is for a process multiplexing many peers over a single datagram socket.
214 /// @param conf Configuration structure to populate
215 standalone void presetServer([out] NetQueueConfig *conf);
217 /// Create a stream socket on the queue and start connecting it, in one call
219 /// Composes the whole client setup -- netqueueSocket(), netqueueAddSocket(), handler
220 /// registration, and netsocketConnect() -- so a single outbound connection is one call
221 /// instead of four. Handlers are registered before the connect begins, so the
222 /// NET_Connection event cannot slip past. Purely a convenience over the public API; use
223 /// the individual calls when the socket needs configuration between the steps.
225 /// @param host Hostname or literal address to connect to (NULL/empty means loopback)
226 /// @param port Port number, host byte order
227 /// @param handlers Per-socket handler overrides, or NULL for none. Not copied -- must
228 /// outlive the socket, so it is usually `static const`
229 /// @param ctx Context passed to these handlers on NetEvent.ctx
230 /// @return The connecting socket (a reference the caller must release), or NULL if any
231 /// step failed -- nothing is left registered on the queue in that case
235 /// static const NetHandlers handlers = { .connection = onConn, .recv = onRecv };
236 /// NetSocket* s = netqueueConnect(q, _SL("example.com"), 443, &handlers, &state);
238 [sal _Ret_maybenull_] unbound NetSocket *connect(strref host, uint16 port,
239 [in] [opt] const NetHandlers *handlers, [in] [opt] void *ctx);
241 /// Create a stream socket, hand it over, and start connecting it, in one call
243 /// netqueueConnect() with one addition: `prep` is called with the finished socket immediately
244 /// before the connect begins. Use it when the socket has to be reachable from somewhere else
245 /// -- a request, a session, a cancel path -- before its first event can arrive, which the
246 /// return value is too late for: the connect can complete on another thread while this call is
249 /// A NULL return means nothing was started. Anything `prep` stored is the caller's to clean up.
251 /// @param host Hostname or literal address to connect to (NULL/empty means loopback)
252 /// @param port Port number, host byte order
253 /// @param handlers Per-socket handler overrides, or NULL for none. Not copied -- must
254 /// outlive the socket, so it is usually `static const`
255 /// @param ctx Context passed to these handlers on NetEvent.ctx
256 /// @param prep Called with the socket just before it connects, or NULL for none
257 /// @param prepctx Context passed to `prep`
258 /// @return The connecting socket (a reference the caller must release), or NULL if any
259 /// step failed -- nothing is left registered on the queue in that case
263 /// NetSocket* s = netqueueConnectPrep(q, _SL("example.com"), 443, &handlers, &state,
264 /// publishSock, &state);
266 [sal _Ret_maybenull_] unbound NetSocket *connectPrep(strref host, uint16 port,
267 [in] [opt] const NetHandlers *handlers, [in] [opt] void *ctx,
268 [opt] NetConnectPrepCB prep, [in] [opt] void *prepctx);
270 /// Create a stream socket on the queue and start it listening, in one call
272 /// The accept-side counterpart of netqueueConnect(): composes netqueueSocket(),
273 /// netqueueAddSocket(), handler registration, netsocketBind(), and netsocketListen().
274 /// Incoming connections arrive as NET_Accepted events on the listener; with NQ_AutoAccept
275 /// set on the queue they are registered with it automatically.
277 /// @param addr Local address to bind (port 0 lets the OS pick one)
278 /// @param backlog Listen backlog, or 0 for a platform-specific default
279 /// @param handlers Per-socket handler overrides, or NULL for none. Not copied -- must
280 /// outlive the socket, so it is usually `static const`
281 /// @param ctx Context passed to these handlers on NetEvent.ctx
282 /// @return The listening socket (a reference the caller must release), or NULL if any
283 /// step failed -- nothing is left registered on the queue in that case
287 /// static const NetHandlers handlers = { .accepted = onAccept };
289 /// netAddrFromStr(&la, _SL("0.0.0.0"));
291 /// NetSocket* s = netqueueListen(q, &la, 0, &handlers, &state);
293 [sal _Ret_maybenull_] unbound NetSocket *listen([in] const NetAddr *addr, int backlog,
294 [in] [opt] const NetHandlers *handlers, [in] [opt] void *ctx);
296 /// Register the queue-wide fallback handlers
298 /// This is the last level of the per-field fallthrough described in @ref net_handlers, and
299 /// is generally the right place for logging and error handling. The handler struct is not
300 /// copied -- it must outlive the queue, which is why it is usually `static const`.
302 /// @param handlers Handler set, or NULL to clear
303 /// @param ctx Context passed to these handlers on NetEvent.ctx
304 unbound void setHandlers([in] [opt] const NetHandlers *handlers, [in] [opt] void *ctx);
306 /// Register the queue-wide fallback handlers, with an object as the context
308 /// Same as setHandlers(), except ctx is held weakly rather than borrowed.
310 /// @param handlers Handler set, or NULL to clear
311 /// @param ctx Object passed to these handlers on NetEvent.ctx, held weakly; NULL to clear
312 unbound void setHandlersObj([in] [opt] const NetHandlers *handlers, [in] [opt] ObjInst *ctx);
314 /// Admit a peer that was refused by the flow cap
316 /// Called from a NET_FlowRefused handler once the application has validated the packet --
317 /// completed a handshake, checked a crypto negotiation, whatever its protocol requires.
318 /// This is what makes a public datagram port safe: past the cap the queue stops allocating
319 /// on its own and hands raw packets to code that already knows how to tell a real client
320 /// from a flood. The admitted flow fires NET_FlowOpen like any other, ordered ahead of its
323 /// @param sock Socket the packet arrived on
324 /// @param peer Source address to admit
325 /// @return The new flow (a reference the caller must release), or NULL if there is still
327 [sal _Ret_maybenull_] unbound NetFlow *promoteFlow([inout] NetSocket *sock,
328 [in] const NetAddr *peer);
330 /// Datagrams dropped for lack of a receive buffer
332 /// A silent drop is indistinguishable from a network problem. A counter that is nonzero
333 /// turns "the network is flaky" into "the pool is too small or a callback is too slow"
334 /// immediately, so this is worth logging at whatever cadence the application already has.
336 /// @return Monotonic count of dropped datagrams since the queue was created
337 unbound uint32 droppedNoBuf();
339 // -----------------------------------------------------------------------------------------
340 // PRIVATE IMPLEMENTATION DETAILS
342 // Everything below this point is internal plumbing between the net module's own translation
343 // units and the platform backends. It is not a stable API and carries no compatibility
344 // promise -- signatures and semantics change whenever the implementation needs them to.
345 // -----------------------------------------------------------------------------------------
347 // Construction (queue.c)
349 // Apply a configuration to a queue under construction. Called by a backend's factory before
350 // objInstInit(), so that the sizes everything else is built from are already in place when the
351 // base class initializes. `conf` is NULL for the client preset.
352 unbound void _applyConfig([in] [opt] const NetQueueConfig *conf);
354 // Worker pool (queue.c)
356 // The generic dispatch pool the base class owns: N threads that drain the runqueue and block
357 // on runqSema when it is empty. A backend's factory starts them in threaded mode; polled
358 // queues never call these and run everything on the caller's tick() thread.
360 // Start n dispatch worker threads. Fewer may end up running if thread creation fails; the
361 // actual count lands in self->workers.
362 unbound void _startWorkers(int32 n);
364 // Ask every dispatch worker to exit and join it. Each worker drains the runqueue one last time
365 // on its way out, so terminal events queued during shutdown are still delivered. `timeout`
366 // <= 0 waits indefinitely. Idempotent -- safe to call when no workers are running.
367 unbound void _stopWorkers(int64 timeout);
371 // Where a packet enters the core. _ingestDatagram is the datagram front door and belongs to the
372 // queue; _submit is the runqueue's producer half and lives beside its consumer in dispatch.c.
374 // Hand a received datagram to the core for demultiplexing and dispatch. Takes ownership of
375 // `buf`, which must have come from the queue's own buffer pool. Resolves the flow for the
376 // source address (creating one, reclaiming under cap pressure, or falling back to the
377 // flowRefused handler), pushes the packet onto that flow's inbox, and enqueues the flow if it
378 // was idle. Returns true if the packet was queued to a flow, false if it was dropped or handed
379 // to a NET_FlowRefused handler instead.
380 unbound bool _ingestDatagram([inout] NetSocket *sock, [in] NetAddr *peer,
381 [in] [opt] const NetPktInfo *info, [inout] Buffer *buf);
383 // Hand a message to a flow and make sure something will come along to run it. Takes ownership
384 // of the message. `self` may be NULL, in which case the message is simply released.
385 [extern] unbound void _submit([inout] NetFlow *flow, [inout] NetMessage *msg);
387 // Dispatch (dispatch.c)
389 // The runqueue and the event delivery it feeds. _submit above is the producer half of the same
390 // claim protocol _dispatch consumes.
392 // Claim one flow from the runqueue and drain it. This is the body of a worker thread, and it
393 // is also what tick() runs inline in polled mode -- the same code either way, just without the
394 // threads. Returns false if the runqueue was empty.
395 [extern] unbound bool _dispatch();
397 // Resolve and invoke the handler for an event, filling in the event's queue/socket/flow/ctx
398 // fields; a no-op when no level supplies a handler. In dev builds the callback is timed, and
399 // one that holds its worker past the warning threshold gets logged with the socket and event
400 // type -- handlers are expected to return quickly, and this is how we notice when one doesn't.
401 // Every callback invocation should go through here so timing stays consistent. `self` may be
402 // NULL, in which case only the socket and flow levels are consulted.
403 [extern] unbound void _deliver([in] [opt] NetSocket *sock, [in] [opt] NetFlow *flow,
404 [inout] NetEvent *ev);
406 // Maintenance (queue.c)
408 // Opportunistic GC of the queue's dynamic PrQueues (runqueue and receive pool). Call it from a
409 // thread that has just drained the runqueue and is about to idle -- the moment the PrQueue
410 // docs recommend for collection. Time-gated and single-runner, so a whole worker pool or a
411 // tight poll loop costs one GC attempt per interval; safe to call as often as convenient.
412 unbound void _maint();
416 // The queue-wide deadline heap behind netflowAddTimer(). Backends drive it with two calls per
417 // wait: _nextDeadline() to bound the sleep, and _timerSweep() to fire whatever came due.
419 // Arm a timer on a flow, `delay` microseconds from now. `fn` is NULL for an application timer
420 // (delivered as NET_Timer through the flow's inbox) or a framework hook fired inline on the
421 // sweeping thread. Returns the new id, or 0 if the flow is already dying or has no queue.
422 [extern] unbound NetTimerId _addTimer([inout] NetFlow *flow, int64 delay, flags_t flags,
423 NetTimerFn fn, [in] [opt] void *ctx);
425 // Cancel an armed timer. Returns true only if this call removed it from the heap -- a timer
426 // already popped for delivery answers false. A successful cancel means the caller is the one
427 // that gets to act on it. Cancelling an unknown or already-fired id is a harmless false.
428 [extern] unbound bool _cancelTimer(NetTimerId id);
430 // Move an armed timer's deadline to `delay` microseconds from now. Returns false if the timer
431 // is not armed (cancelled, or already popped for delivery).
432 [extern] unbound bool _rearmTimer(NetTimerId id, int64 delay);
434 // Cancel every timer armed on a flow. Called on the flow's terminal path so a long deadline on
435 // a dead connection cannot keep the flow object alive until it expires.
436 [extern] unbound void _cancelFlowTimers([inout] NetFlow *flow);
438 // Fire every timer whose deadline has passed. A no-op on one relaxed load while nothing is
439 // armed. Entries are popped under timerLock and fired outside it, so a callback is free to arm,
440 // cancel, or send. Called by every backend right after its wait returns, beside _maint().
441 [extern] unbound void _timerSweep();
443 // The nearest armed deadline in absolute clockTimer() microseconds, or 0 if nothing is armed.
444 // Backends cap their wait to this so a timer fires close to when it is due rather than on the
445 // next unrelated wakeup.
446 [extern] unbound int64 _nextDeadline();
448 // Flow table and lifecycle (flow.c)
450 // Find the flow for a peer address, creating one if there is room under the cap. Returns a
451 // strong reference the caller must release, or NULL if the queue is at its cap and nothing
452 // could be reclaimed -- in which case the caller should fall back to the flowRefused handler.
453 // A stream or QUIC socket has no address-keyed table, so `peer` is ignored there and the
454 // socket's single control flow is returned.
455 [extern] [sal _Ret_maybenull_] unbound NetFlow *_findFlow([inout] NetSocket *sock,
456 [in] NetAddr *peer, bool create);
458 // Create a flow for a peer and register it with the socket, bypassing the cap check. Returns a
459 // strong reference the caller must release. A thin wrapper over _admitFlowObj below.
460 [extern] [sal _Ret_maybenull_] unbound NetFlow *_admitFlow([inout] NetSocket *sock,
463 // Register an already-constructed flow with its socket: build its filter chain, publish it in
464 // the socket's table (keyed on `peer` for a datagram socket, `key` for a QUIC one), count it
465 // against the queue, queue its NET_FlowOpen ahead of anything else that could land in its
466 // inbox, and prime the chain. Split out of _admitFlow so cxquic can register a NetFlow
467 // subclass of its own for a QUIC stream.
469 // Consumes the caller's reference and returns the one to use, which is normally the same flow
470 // -- but is the winner's flow when another thread inserted the same key first, and NULL if the
471 // table lost it. Do not use `flow` after the call; use the return value.
472 [extern] [sal _Ret_maybenull_] unbound NetFlow *_admitFlowObj([inout] NetSocket *sock,
473 [inout] NetFlow *flow);
475 // Mark up to reclaimBatch least-recently-active flows on this socket for teardown, skipping
476 // flows active more recently than reclaimMinIdle allows. Runs inline on the ingest path at the
477 // moment of a cap hit; there is no timer thread.
478 [extern] unbound uint32 _reclaimFlows([inout] NetSocket *sock);