CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches
Flows

Data Structures

struct  NetFlow
 A single ordering domain: one connection, or one datagram peer. More...
 

Macros

#define netflowSetHandlers(self, handlers, ctx)   NetFlow_setHandlers(NetFlow(self), handlers, ctx)
 
#define netflowSetHandlersObj(self, handlers, ctx)   NetFlow_setHandlersObj(NetFlow(self), handlers, ObjInst(ctx))
 
#define netflowAddTimer(self, delay, flags)   NetFlow_addTimer(NetFlow(self), delay, flags)
 
#define netflowCancelTimer(self, id)   NetFlow_cancelTimer(NetFlow(self), id)
 
#define netflowRearmTimer(self, id, delay)   NetFlow_rearmTimer(NetFlow(self), id, delay)
 
#define netflowClose(self)   (self)->_->close(NetFlow(self))
 
#define netflowSend(self, data, len, flags)   (self)->_->send(NetFlow(self), data, len, flags)
 

Typedefs

typedef struct NetFlow NetFlow
 A single ordering domain: one connection, or one datagram peer.
 

Detailed Description

A flow is the unit of event ordering.

For a TCP socket, the obvious ordering domain is the socket itself, and that works fine. It falls apart for a single UDP socket serving many clients: if the socket were the ordering domain, every client's events would serialize behind one another and the thread pool would sit idle. So the ordering domain is the flow instead:

TCP socket -> exactly one flow (ordering domain = the connection)
UDP socket -> N flows (ordering domain = the peer address)

The guarantee is: events belonging to one flow are strictly ordered and never run concurrently. Events on different flows may run at the same time on different workers, and nothing is promised about ordering between flows.

Both socket types dispatch through the same mechanism, so there is one code path rather than two. For a single stream connection, the flow is created automatically during socket setup and shows up only as a field on the event; consumers that only care about one connection can ignore it entirely.

Creation

A datagram flow is created automatically for the first packet from a new peer or the first packet sent to one, or explicitly with netqueuePromoteFlow(). Any of those fires NET_FlowOpen on the new flow, ordered ahead of its first NET_DataReceived – the place to set up flow->user state and register per-flow handlers before any packet is seen. When the queue is at its flow cap and nothing is reclaimable, no flow is created: a received packet goes to the NET_FlowRefused handler instead, which can validate it and admit the peer with netqueuePromoteFlow(), and a send to an unknown peer is refused.

Stream flows do not fire NET_FlowOpen; their session start is NET_Connection or NET_Accepted.

However a flow comes into being, if its socket has filters attached the flow's own filter chain is built before the flow becomes visible, so no data can ever reach the application unfiltered. See Filters.

Application state

flow->user is the place for per-peer application state – strings, buffers, objects, anything with a destructor. Because the flow is a real object, its generated destructor tears that state down automatically, so there's no need to hand-write cleanup for every way a flow can end.

Only flows carry a user array. Per-socket and per-queue state should go in the ctx pointer captured at handler registration instead, because sockets and queues don't have the same one-worker-at-a-time guarantee a flow does – a single datagram socket can have thousands of flows running on every worker at once, so a shared array there would need its own locking.

Timers

A flow is also the unit of timing. netflowAddTimer() arms a deadline and NET_Timer is delivered on the flow when it elapses – on a worker, ordered behind everything already pending, exactly like a packet.

Timers belong to the flow's lifetime as well as its ordering: teardown cancels whatever is still armed, so no NET_Timer follows NET_FlowClosed.

Teardown

NET_FlowClosed is delivered as a terminal event on the flow's own queue, so it arrives after every packet the application has already been handed. It fires exactly once per flow, on every close cause, with the cause given in NetCloseReason.

A flow reclaimed under cap pressure can resurrect: if a packet arrives before its terminal event has been delivered, the flow comes back to life and continues as if nothing happened. This avoids tearing down and immediately recreating a client that never actually left. Only reclaim works this way – every other close reason is final, and a later packet cannot undo it.

Macro Definition Documentation

◆ netflowAddTimer

#define netflowAddTimer (   self,
  delay,
  flags 
)    NetFlow_addTimer(NetFlow(self), delay, flags)

NetTimerId netflowAddTimer(NetFlow* self, int64 delay, flags_t flags);

Arm a timer on this flow

NetEvent.timer.id on the delivered NET_Timer carries the returned id, so one handler can tell several timers apart.

Parameters
delayMicroseconds from now until the timer fires (see timeS() / timeMS())
flagsOptional NetTimerFlags (NTF_Repeat to re-arm automatically)
Returns
The new timer's id, or 0 if the flow is closing or has no queue

Example:

// fail the request if the response has not completed within 30 seconds
NetTimerId deadline = netflowAddTimer(ev->flow, timeS(30), NTF_None);
#define netflowAddTimer(self, delay, flags)
Definition flow.h:260
uint64 NetTimerId
Handle to an armed timer, unique for the lifetime of its queue.
Definition net_shared.h:210
#define timeS(s)
Definition time.h:20

Definition at line 260 of file flow.h.

◆ netflowCancelTimer

#define netflowCancelTimer (   self,
  id 
)    NetFlow_cancelTimer(NetFlow(self), id)

bool netflowCancelTimer(NetFlow* self, NetTimerId id);

Cancel a timer armed on this flow

Parameters
idTimer id from netflowAddTimer()
Returns
true if this call stopped the timer before it fired. false if the timer had already fired or was never armed; if two threads race to cancel the same timer, only one of them gets true.

Definition at line 271 of file flow.h.

◆ netflowClose

#define netflowClose (   self)    (self)->_->close(NetFlow(self))

bool netflowClose(NetFlow* self);

Close this flow

Marks the flow dying and queues a terminal NET_FlowClosed event behind everything already pending for it. The flow is not freed here; it stays alive until its queued packets and its terminal event have been delivered.

A flow closed this way cannot resurrect – an arriving packet will not cancel the teardown, and the peer is treated as a new, unknown source afterwards.

Returns
true if this call marked the flow dying, false if it was already closing

Definition at line 461 of file flow.h.

◆ netflowRearmTimer

#define netflowRearmTimer (   self,
  id,
  delay 
)    NetFlow_rearmTimer(NetFlow(self), id, delay)

bool netflowRearmTimer(NetFlow* self, NetTimerId id, int64 delay);

Move an armed timer's deadline forward, keeping its id

Parameters
idTimer id from netflowAddTimer()
delayMicroseconds from now until the timer fires
Returns
false if the timer is no longer armed

Definition at line 281 of file flow.h.

◆ netflowSend

#define netflowSend (   self,
  data,
  len,
  flags 
)    (self)->_->send(NetFlow(self), data, len, flags)

bool netflowSend(NetFlow* self, const uint8* data, size_t len, flags_t flags);

Send data on this flow

For a datagram flow this is a convenience wrapper that calls netsocketSend() on the owning socket with this flow's peer as the destination – filtering, backpressure, and queueing are exactly netsocketSend()'s. A QUIC stream flow sends on its stream instead, since a stream is named by its id and not by an address.

Parameters
dataPayload to send (copied; need not outlive the call)
lenLength of the payload in bytes
flagsOptional NetSocketOpFlags
Returns
true if the payload was sent or queued, false if it was refused

Definition at line 475 of file flow.h.

◆ netflowSetHandlers

#define netflowSetHandlers (   self,
  handlers,
  ctx 
)    NetFlow_setHandlers(NetFlow(self), handlers, ctx)

void netflowSetHandlers(NetFlow* self, const NetHandlers* handlers, void* ctx);

Register per-flow handler overrides

Fields left NULL fall through to the socket's set, and then to the queue-wide set. The handler struct is not copied – it must outlive the flow, which is why it is usually static const.

Parameters
handlersHandler set, or NULL to clear
ctxContext passed to these handlers on NetEvent.ctx

Definition at line 230 of file flow.h.

◆ netflowSetHandlersObj

#define netflowSetHandlersObj (   self,
  handlers,
  ctx 
)    NetFlow_setHandlersObj(NetFlow(self), handlers, ObjInst(ctx))

void netflowSetHandlersObj(NetFlow* self, const NetHandlers* handlers, ObjInst* ctx);

Register per-flow handler overrides, with an object as the context

Same as setHandlers(), except ctx is held weakly rather than borrowed.

Parameters
handlersHandler set, or NULL to clear
ctxObject passed to these handlers on NetEvent.ctx, held weakly; NULL to clear

Definition at line 241 of file flow.h.