CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches
flow.cxh
1#include <cx/net/socket.cxh>
2#include <cx/net/filter.cxh>
3#include <cx/net/pool.cxh>
4#include <cx/net/net_shared.h>
5
6/// @addtogroup net_flow
7/// @{
8/// A flow is the unit of event ordering.
9///
10/// For a TCP socket, the obvious ordering domain is the socket itself, and that works fine. It
11/// falls apart for a single UDP socket serving many clients: if the socket were the ordering
12/// domain, every client's events would serialize behind one another and the thread pool would
13/// sit idle. So the ordering domain is the flow instead:
14///
15/// @code
16/// TCP socket -> exactly one flow (ordering domain = the connection)
17/// UDP socket -> N flows (ordering domain = the peer address)
18/// @endcode
19///
20/// The guarantee is: events belonging to one flow are strictly ordered and never run
21/// concurrently. Events on different flows may run at the same time on different workers, and
22/// nothing is promised about ordering *between* flows.
23///
24/// Both socket types dispatch through the same mechanism, so there is one code path rather than
25/// two. For a single stream connection, the flow is created automatically during socket setup
26/// and shows up only as a field on the event; consumers that only care about one connection can
27/// ignore it entirely.
28///
29/// @section net_flow_creation Creation
30///
31/// A datagram flow is created automatically for the first packet from a new peer or the first
32/// packet sent to one, or explicitly with netqueuePromoteFlow(). Any of those fires NET_FlowOpen
33/// on the new flow, ordered ahead of its first NET_DataReceived -- the place to set up
34/// `flow->user` state and register per-flow handlers before any packet is seen. When the queue is
35/// at its flow cap and nothing is reclaimable, no flow is created: a received packet goes to the
36/// NET_FlowRefused handler instead, which can validate it and admit the peer with
37/// netqueuePromoteFlow(), and a send to an unknown peer is refused.
38///
39/// Stream flows do not fire NET_FlowOpen; their session start is NET_Connection or NET_Accepted.
40///
41/// However a flow comes into being, if its socket has filters attached the flow's own filter chain
42/// is built before the flow becomes visible, so no data can ever reach the application unfiltered.
43/// See @ref net_filter.
44///
45/// @section net_flow_state Application state
46///
47/// `flow->user` is the place for per-peer application state -- strings, buffers, objects,
48/// anything with a destructor. Because the flow is a real object, its generated destructor tears
49/// that state down automatically, so there's no need to hand-write cleanup for every way a flow
50/// can end.
51///
52/// Only flows carry a `user` array. Per-socket and per-queue state should go in the ctx pointer
53/// captured at handler registration instead, because sockets and queues don't have the same
54/// one-worker-at-a-time guarantee a flow does -- a single datagram socket can have thousands of
55/// flows running on every worker at once, so a shared array there would need its own locking.
56///
57/// @section net_flow_timers Timers
58///
59/// A flow is also the unit of timing. netflowAddTimer() arms a deadline and NET_Timer is delivered
60/// on the flow when it elapses -- on a worker, ordered behind everything already pending, exactly
61/// like a packet.
62///
63/// Timers belong to the flow's lifetime as well as its ordering: teardown cancels whatever is still
64/// armed, so no NET_Timer follows NET_FlowClosed.
65///
66/// @section net_flow_teardown Teardown
67///
68/// NET_FlowClosed is delivered as a terminal event on the flow's own queue, so it arrives after
69/// every packet the application has already been handed. It fires exactly once per flow, on
70/// every close cause, with the cause given in NetCloseReason.
71///
72/// A flow reclaimed under cap pressure can resurrect: if a packet arrives before its terminal
73/// event has been delivered, the flow comes back to life and continues as if nothing happened.
74/// This avoids tearing down and immediately recreating a client that never actually left. Only
75/// reclaim works this way -- every other close reason is final, and a later packet cannot undo
76/// it.
77
78/// A single ordering domain: one connection, or one datagram peer
79class NetFlow
80{
81 /// @brief Socket that owns this flow
82 ///
83 /// Weak, to break the socket/flow ownership cycle. Resolve to a strong reference once per
84 /// dispatch batch, not once per packet.
85 weak[NetSocket] socket;
86
87 /// @brief Pool every message on this flow was drawn from, held strongly
88 ///
89 /// Strong where `socket` is weak, and for the opposite reason: a flow can still be holding
90 /// messages -- in its inbox, its ready list, or the datagram staging queue -- at a moment when
91 /// neither its socket nor the queue is reachable any more, and a pooled buffer destroyed
92 /// instead of returned costs the whole queue that much of its ceiling for good. NULL only for a
93 /// stream flow whose socket has not been added to a queue yet; netqueueAddSocket() fills it in.
94 object[NetPool] pool;
95
96 NetAddr peer; ///< Datagram: the peer address this flow is keyed on
97
98 /// @brief QUIC: the stream id this flow is keyed on. Zero on every other kind of flow.
99 ///
100 /// A QUIC socket's flow table is keyed on this rather than on `peer`, because a QUIC
101 /// connection's identity is its Connection ID and its peer address can change underneath it.
102 uint64 key;
103
104 atomic[uint32] claimed; ///< A worker is currently draining this flow
105 atomic[uint32] queued; ///< Present on the queue's runqueue
106 atomic[uint32] dying; ///< Marked for close; blocks further dispatch
107
108 /// @brief Pending messages for this flow, in an intrusive lock-free stack
109 ///
110 /// Any ingest thread can push; only the worker holding the claim drains it.
111 atomic[ptr] inbox;
112
113 NetMessage *ready; ///< Consumer-private FIFO head, refilled from inbox
114 NetMessage *readytail; ///< Consumer-private FIFO tail, for O(1) append
115
116 /// @brief Last time a packet was ingested for this flow, for approximate LRU
117 ///
118 /// Reclaim scans for the oldest entries rather than keeping an ordered list, since updating a
119 /// list on every packet would put a lock on the hottest path in the system. Stored as a
120 /// coarse ~1 second tick rather than a precise timestamp, which keeps it small enough to
121 /// update as a single atomic write.
122 atomic[uint32] lastActive;
123
124 /// @brief Application state for this peer, allocated lazily
125 ///
126 /// Often unused on stream sockets, where per-connection state usually lives in the ctx.
127 [noinit] sarray[stvar] user;
128
129 NetHandlers *handlers; ///< Per-flow handler overrides, optional
130 void *handlerCtx; ///< Context passed to per-flow handlers, set by setHandlers()
131
132 /// @brief Context passed to per-flow handlers, set by setHandlersObj() -- NULL when
133 /// handlerCtx is in use instead
134 weak[ObjInst] handlerWeak;
135
136 /// @brief Guards handlers/handlerCtx/handlerWeak against a concurrent setHandlers()/setHandlersObj()
137 RWLock handlerLock;
138
139 /// @brief Ids of the timers currently armed on this flow, guarded by the queue's timerLock
140 [noinit] sarray[uint64] timers;
141
142 /// @brief This flow's filter chain, ordered application -> wire, or empty for none
143 ///
144 /// Never installed by hand: the socket builds it when the flow is created (and when a filter is
145 /// attached to a socket that already has flows) by walking its own NetFilter list and calling
146 /// createFlow() on each, so the two run in lockstep. See @ref net_filter.
147 [noinit] sarray[object[NetFlowFilter]] filters;
148
149 /// @brief Stream: staging ring the encode chain consumes from, allocated with the chain
150 ///
151 /// netsocketSend() writes the application payload here and the chain's first stage reads it;
152 /// whatever a stage declines to consume stays put and resumes on the next pass. NULL when the
153 /// flow has no filters, and always NULL on a datagram flow, which stages whole messages in
154 /// encInMsgs instead.
155 BufRing *encIn;
156
157 /// @brief Datagram: staging queue the encode chain consumes from, the encIn of the message side
158 ///
159 /// A stage still negotiating declines to consume application messages, and they wait here until
160 /// it will take them, exactly as unconsumed bytes wait in a stream flow's encIn. Bounded by
161 /// NET_FLOW_ENCQ_MAX messages, past which a send is refused rather than buffered without limit.
162 NetMsgQueue encInMsgs;
163
164 /// @brief Serializes filter chain access for this flow
165 ///
166 /// Decode always runs on the worker holding the flow's claim, but encode is driven by whatever
167 /// thread called netsocketSend(), and both touch the same stage objects and boundary buffers.
168 /// Held across an entire driver pass -- but never across an application callback, which could
169 /// re-enter through netsocketSend().
170 Mutex filterLock;
171
172 uint8 closeReason; ///< NetCloseReason once the flow is dying
173
174 factory create(NetSocket *socket, const NetAddr *peer);
175 init(); // needed to init lastActive
176
177 /// Close this flow
178 ///
179 /// Marks the flow dying and queues a terminal NET_FlowClosed event behind everything already
180 /// pending for it. The flow is not freed here; it stays alive until its queued packets and
181 /// its terminal event have been delivered.
182 ///
183 /// A flow closed this way cannot resurrect -- an arriving packet will not cancel the
184 /// teardown, and the peer is treated as a new, unknown source afterwards.
185 ///
186 /// @return true if this call marked the flow dying, false if it was already closing
187 bool close();
188
189 /// Register per-flow handler overrides
190 ///
191 /// Fields left NULL fall through to the socket's set, and then to the queue-wide set. The
192 /// handler struct is not copied -- it must outlive the flow, which is why it is usually
193 /// `static const`.
194 ///
195 /// @param handlers Handler set, or NULL to clear
196 /// @param ctx Context passed to these handlers on NetEvent.ctx
197 unbound void setHandlers([in] [opt] const NetHandlers *handlers, [in] [opt] void *ctx);
198
199 /// Register per-flow handler overrides, with an object as the context
200 ///
201 /// Same as setHandlers(), except ctx is held weakly rather than borrowed.
202 ///
203 /// @param handlers Handler set, or NULL to clear
204 /// @param ctx Object passed to these handlers on NetEvent.ctx, held weakly; NULL to clear
205 unbound void setHandlersObj([in] [opt] const NetHandlers *handlers, [in] [opt] ObjInst *ctx);
206
207 /// Send data on this flow
208 ///
209 /// For a datagram flow this is a convenience wrapper that calls netsocketSend() on the owning
210 /// socket with this flow's peer as the destination -- filtering, backpressure, and queueing
211 /// are exactly netsocketSend()'s. A QUIC stream flow sends on its stream instead, since a
212 /// stream is named by its id and not by an address.
213 ///
214 /// @param data Payload to send (copied; need not outlive the call)
215 /// @param len Length of the payload in bytes
216 /// @param flags Optional NetSocketOpFlags
217 /// @return true if the payload was sent or queued, false if it was refused
218 bool send([in] const uint8 *data, size_t len, flags_t flags);
219
220 /// Arm a timer on this flow
221 ///
222 /// NetEvent.timer.id on the delivered NET_Timer carries the returned id, so one handler can
223 /// tell several timers apart.
224 ///
225 /// @param delay Microseconds from now until the timer fires (see timeS() / timeMS())
226 /// @param flags Optional NetTimerFlags (NTF_Repeat to re-arm automatically)
227 /// @return The new timer's id, or 0 if the flow is closing or has no queue
228 ///
229 /// Example:
230 /// @code
231 /// // fail the request if the response has not completed within 30 seconds
232 /// NetTimerId deadline = netflowAddTimer(ev->flow, timeS(30), NTF_None);
233 /// @endcode
234 unbound NetTimerId addTimer(int64 delay, flags_t flags);
235
236 /// Cancel a timer armed on this flow
237 ///
238 /// @param id Timer id from netflowAddTimer()
239 /// @return true if this call stopped the timer before it fired. false if the timer had
240 /// already fired or was never armed; if two threads race to cancel the same timer, only
241 /// one of them gets true.
242 unbound bool cancelTimer(NetTimerId id);
243
244 /// Move an armed timer's deadline forward, keeping its id
245 ///
246 /// @param id Timer id from netflowAddTimer()
247 /// @param delay Microseconds from now until the timer fires
248 /// @return false if the timer is no longer armed
249 unbound bool rearmTimer(NetTimerId id, int64 delay);
250
251 // -----------------------------------------------------------------------------------------
252 // PRIVATE IMPLEMENTATION DETAILS
253 //
254 // Everything below this point is internal plumbing between the net module's own translation
255 // units. It is not a stable API and carries no compatibility promise -- signatures and
256 // semantics change whenever the implementation needs them to. Nothing outside cx/net/ and the
257 // platform backends should call any of it.
258 // -----------------------------------------------------------------------------------------
259
260 // Inbox (flow.c)
261
262 // Push a message onto the flow's inbox. Returns true if the caller is responsible for putting
263 // the flow on the runqueue, which is the case only when it was not already there.
264 [sal _Check_return_] unbound bool _push([inout] NetMessage *msg);
265
266 // Pop the next message in FIFO order, refilling the private ready list from the inbox when it
267 // runs dry. May ONLY be called by the worker holding the flow's claim.
268 [sal _Ret_maybenull_] unbound NetMessage *_pop();
269
270 // Put a popped message back at the head of the ready list, so the next _pop() returns it
271 // again. For a worker that has to stop draining partway through -- the message has to keep its
272 // place, and everything behind it keeps its place too. Same rule as _pop(): the claim holder
273 // only.
274 unbound void _unpop([inout] NetMessage *msg);
275
276 // Mark the flow dying and queue its terminal NET_FlowClosed event. Returns false if the flow
277 // was already closing. The public close() is this with NCR_AppClosed.
278 unbound bool _close(NetCloseReason reason);
279
280 // Resolve the flow's queue through socket->queue. Returns a strong reference the caller must
281 // release, or NULL if either weak arm has already been broken.
282 [sal _Ret_maybenull_] unbound NetQueue *_queue();
283
284 // Flow table (flow.c)
285
286 // Snapshot a socket's live flows into `out`, each with a reference held, and hand the array to
287 // the caller to destroy. Initializes `out`; it must not already hold an array. Taken under the
288 // socket's flow lock and meant to be used outside it, because everything worth doing to a flow
289 // -- installing or dropping a filter chain, closing it -- allocates, primes, or reaches the
290 // wire, none of which may run with the flow table locked. Declared here rather than on
291 // NetSocket because sa_NetFlow is only visible from this side of the socket/flow include
292 // cycle, and because the rest of the flow table lives here too.
293 standalone void _snapshotFlows([in] NetSocket *sock, [out] sa_NetFlow *out);
294
295 // Filter chain construction (filter.c)
296 //
297 // The socket owns a list of NetFilter factories; every flow it owns carries the parallel list
298 // of NetFlowFilter stages they produce. The chain is always built before a flow is reachable
299 // by anything else, so there is no window in which data could move past a flow that should
300 // have been filtered. Everything here is serialized by the flow's filterLock.
301
302 // Create one socket filter's stage for this flow and append it to the chain, allocating the
303 // staging buffer if this is the flow's first stage. A factory that returns NULL contributes
304 // nothing.
305 [extern] unbound void _addFilter([inout] NetSocket *sock, [in] NetFilter *filter);
306
307 // Build the whole chain by walking the socket's filter list in order. A no-op for a socket
308 // with no filters, which is what keeps an unfiltered flow free of any filter cost at all.
309 [extern] unbound void _buildFilters([inout] NetSocket *sock);
310
311 // Drop the flow's chain, its staging storage, and anything the stages had buffered.
312 [extern] unbound void _clearFilters();
313
314 // Begin an orderly close on every stage (app -> wire). The caller runs one more encode pass
315 // afterwards to put whatever they produced (a TLS close_notify) on the wire.
316 [extern] unbound void _filterShutdown();
317
318 // Deliver every notification the chain has raised, clearing each as it goes. `onWorker`
319 // delivers inline -- correct only from inside the flow's dispatch batch, where it also orders
320 // the notification ahead of data produced by the same pass; otherwise each is queued on the
321 // flow so it still lands on a worker in order. Must NOT be called with the flow's filterLock
322 // held: a handler can call straight back into the send path.
323 [extern] unbound void _filterNotify([in] [opt] NetQueue *q, [in] [opt] NetSocket *sock,
324 bool onWorker);
325
326 // Filter data plane (dataplane.c)
327 //
328 // The four functions where a filtered flow's chain actually runs, all of them on the shared
329 // send/recv path rather than in any backend. A stage never touches the wire itself: these
330 // drivers own the socket's buffers and syscalls on its behalf, which is what stops a stage
331 // from bypassing one above it in the chain. Each takes the flow's filterLock for the duration
332 // of the chain walk and drops it before anything can call back into application code.
333
334 // Stream send: stage `data` (NULL/0 for a priming pass) into the flow's encIn, run the encode
335 // chain repeatedly until no stage has more to produce -- each stage reading the previous
336 // stage's encOut -- and flush what the wire-end stage produced onto the socket's send chain.
337 // Returns false on a fatal transform error, having closed the flow.
338 [extern] unbound bool _filterStreamSend([in] [opt] NetQueue *q, [inout] NetSocket *sock,
339 [sal _In_reads_bytes_opt_(len)] const uint8 *data, size_t len);
340
341 // Stream recv: run the decode chain repeatedly, until no stage has more to produce, over the
342 // raw bytes already in the socket's receive ring -- each stage reading the decOut of the
343 // stage one hop toward the wire -- push out any wire-bound output the pass produced, deliver
344 // the stages' notifications, and deliver NET_DataReceived if the head stage produced
345 // application bytes. Runs on a worker (drainFlow).
346 [extern] unbound void _filterStreamRecv([in] [opt] NetQueue *q, [inout] NetSocket *sock);
347
348 // Encode half of the datagram send: queue `data` (NULL/0 for a priming pass) on the flow's
349 // staging queue, run the encode chain repeatedly until no stage has more to produce, and
350 // collect what the wire-end stage produced into `out` rather than sending it. Split from the
351 // send below so the chain can be driven without a socket. `fatalp` distinguishes a stage
352 // failing (the flow is finished) from a payload merely being refused (the caller backs off);
353 // it may be NULL.
354 [extern] unbound bool _filterDatagramEncode([in] [opt] NetQueue *q,
355 [sal _In_reads_bytes_opt_(len)] const uint8 *data, size_t len,
356 [inout] NetMsgQueue *out, [out] [opt] bool *fatalp);
357
358 // Datagram send: queue `data` (NULL/0 for a priming pass) on the flow's staging queue, run
359 // the encode chain repeatedly until no stage has more to produce, and send every message the
360 // wire-end stage produced to the flow's peer. Returns false if the payload could not be
361 // accepted (staging queue full, pool exhausted) or a stage failed fatally.
362 [extern] unbound bool _filterDatagramSend([in] [opt] NetQueue *q, [inout] NetSocket *sock,
363 [sal _In_reads_bytes_opt_(len)] const uint8 *data, size_t len);
364
365 // Datagram recv: run one received message through the decode chain (consuming it), send any
366 // wire-bound output the pass produced, and append whatever came out the application end to
367 // `out` for the caller to deliver. Runs on a worker (drainFlow). Returns false on a fatal
368 // transform error.
369 [extern] unbound bool _filterDatagramRecv([in] [opt] NetQueue *q, [inout] NetSocket *sock,
370 [inout] NetMessage *msg, [inout] NetMsgQueue *out);
371
372 // Run the encode side once with nothing staged, so a filter that initiates a negotiation gets
373 // to emit its first flight. Called when a chain is built (datagram), when the transport comes
374 // up (stream), and after shutdown() to flush a close record. A no-op for an unfiltered flow,
375 // or a stream flow whose socket is not connected yet.
376 [extern] unbound void _primeFilters([in] [opt] NetQueue *q, [inout] NetSocket *sock);
377}
378
379/// @}