Packet Dispatch¶
PacketDispatchChannel is the runtime bridge between retained transport buffers and
application packet handlers. It owns the background worker loops, wakes workers when
new packets are queued, deserializes packets through the registered packet registry,
and disposes packet and buffer leases after dispatch completes.
Source Mapping¶
src/Nalix.Runtime/Dispatching/PacketDispatchChannel.cssrc/Nalix.Runtime/Internal/Routing/DispatchChannel.cssrc/Nalix.Runtime/Dispatching/Options/PacketDispatchOptions.cssrc/Nalix.Runtime/Dispatching/Options/PacketDispatchOptions.PublicMethods.cs
Runtime Flow¶
flowchart LR
A["Transport IBufferLease"] --> B["HandlePacket"]
B --> C["Retain lease"]
C --> D["DispatchChannel.PushCore"]
D --> E["RequestWake via SemaphoreSlim"]
E --> F["Dispatch worker loop"]
F --> G["IPacketRegistry.TryDeserialize"]
G --> H["MiddlewarePipeline + handler"]
H --> I["Dispose packet and lease"]
HandlePacket Handoff Semantics¶
HandlePacket(IBufferLease packet, IConnection connection) is intentionally small:
- Ignore
nullinputs and empty leases. - Call
packet.Retain()before asynchronous handoff. - Enqueue with
_dispatch.PushCore(connection, packet, noBlock: true). - Dispose the retained lease if enqueue fails.
- Request a worker wake if enqueue succeeds.
This means the transport may dispose its own reference immediately after calling
HandlePacket; the dispatch channel owns a retained reference until execution ends.
Worker Loop Selection¶
Activate() starts worker loops through TaskManager.ScheduleWorker.
The number of loops is resolved as follows:
| Case | Source behavior |
|---|---|
Options.DispatchLoopCount is set |
Use the explicit value. |
Options.DispatchLoopCount is null |
Use Math.Clamp(Environment.ProcessorCount, MinDispatchLoops, MaxDispatchLoops). |
The worker name format is net.dispatch.process.{index} through TaskNaming tags,
and workers are scheduled with WorkerPriority.HIGH.
Wake and Drain Behavior¶
The current implementation uses a SemaphoreSlim wake signal, not a channel-based
wake pipe. RequestWake() coalesces wake requests with _wakeRequested so repeated
packet arrivals do not always release another semaphore count.
Worker loops drain up to _maxDrainPerWake packets before waiting again. The drain
budget is calculated in the constructor:
Math.Clamp(
Environment.ProcessorCount * Options.MaxDrainPerWakeMultiplier,
Options.MinDrainPerWake,
Options.MaxDrainPerWake)
Default option values make this clamp resolve within a bounded range derived from
Environment.ProcessorCount and the configured drain multipliers in PacketDispatchOptions.
DispatchChannel Queue Behavior¶
DispatchChannel<TPacket> is the internal queue behind PacketDispatchChannel.
It maintains per-connection state and priority-ready queues.
| Concern | Source behavior |
|---|---|
| Priority classification | Reads the priority byte at PacketHeaderOffset.Priority; invalid or short buffers use PacketPriority.NONE. |
| Priority selection | Uses weighted round-robin budgets, scanning from PacketPriority.URGENT down to PacketPriority.NONE. |
| Default priority weights | If DispatchOptions.PriorityWeights is absent or short, each missing weight uses 1 << priorityIndex. |
| Per-connection bounds | Enabled when DispatchOptions.MaxPerConnectionQueue > 0. |
| Overflow handling | Uses DropPolicy.DropNewest, DropOldest, Coalesce, or Block; packet dispatch calls PushCore(..., noBlock: true), so block-mode enqueue fails fast from HandlePacket. |
| Cleanup | src/Nalix.Runtime/Internal/Routing/DispatchChannel.cs subscribes to IConnectionHub.ConnectionUnregistered and drains/disposes queued leases for removed connections. |
Execution and Disposal Guarantees¶
When a worker pulls a lease:
IPacketRegistry.TryDeserialize(lease.Span, out IPacket?)is called.- Deserialization failure increments the connection error count and disposes the lease.
- Successful packets are executed through
ExecutePacketHandlerAsync. - Synchronous completions dispose
IDisposablepackets and the lease immediately. - Asynchronous completions are awaited by a helper that disposes both in
finally. - Non-fatal handler exceptions increment the connection error count and are logged.
Diagnostics¶
GenerateReport() and WriteReportData(System.Text.Json.Utf8JsonWriter writer) expose the current runtime snapshot:
| Field | Meaning |
|---|---|
Running |
Whether workers are currently active. |
DispatchLoops |
Number of scheduled dispatch worker loops. |
WakeSignals |
Number of calls that released wake signals. |
WakeReads |
Counter field included in reports. |
WakeRequested |
Current coalesced wake-request flag. |
TotalPackets |
Total queued packets in DispatchChannel. |
TotalConnections |
Active tracked connection states. |
ReadyConnections |
Connections currently marked ready. |
PendingPerPriority |
Ready-entry snapshot per priority level. |
PendingByConnection |
Top pending connections in WriteReportData(System.Text.Json.Utf8JsonWriter writer)(). |