Network Callback Options

Read time 4 minutes Package: Nalix.Network
On this page

Namespace: Nalix.Network.Options
Assembly: Nalix.Network

NetworkCallbackOptions configures two independent callback-pressure layers:

  1. Layer 1 (Per-Connection Throttles in SocketConnection): Caps pending receive packets (MaxPerConnectionPendingPackets) and open fragment reassembly streams (MaxPerConnectionOpenFragmentStreams) per connection, applying OverflowPolicy (DropPacket or Disconnect) when exceeded.
  2. Layer 2 (Global & Per-IP Fairness Lanes in AsyncCallback): Enforces separate budgets for inbound processing callbacks (CallbackLane.Process) and outbound post-processing callbacks (CallbackLane.Post) across global counters and a lock-free endpoint-hash fairness array (FairnessMapSize).

Source Mapping

  • src/Nalix.Network/Options/NetworkCallbackOptions.cs
  • src/Nalix.Abstractions/Networking/NetworkOverflowPolicy.cs
  • src/Nalix.Network/Internal/Transport/SocketConnection.cs
  • src/Nalix.Network/Internal/Transport/AsyncCallback.cs
  • src/Nalix.Network/Connections/Connection.cs
  • src/Nalix.Network/Internal/Pooling/PooledConnectEventContext.cs

Defaults and Validation

PropertyTypeDefaultValid RangeRuntime Effect
MaxPerConnectionPendingPacketsint161..1024Layer 1 cap for receive-path packets queued for one connection (MessageProcessing) but not yet completed.
MaxPerConnectionOpenFragmentStreamsint41..256Layer 1 cap for concurrently open fragmented packet streams on a single connection.
OverflowPolicyNetworkOverflowPolicyDropPacket (0)DropPacket (0), Disconnect (1)Action taken when a connection exceeds MaxPerConnectionPendingPackets: drop the incoming packet (DropPacket) or immediately close the connection (Disconnect).
MaxPendingNormalCallbacksint10_000100..1_000_000Layer 2 global cap for inbound processing callbacks (CallbackLane.Process) pending in AsyncCallback.
CallbackWarningThresholdint5_0000..1_000_000Emits backpressure warnings every 1,000 callbacks once pending normal callbacks reach this threshold (0 disables warnings).
MaxPendingPerIpint641..10_000Layer 2 per-endpoint fairness cap for CallbackLane.Process callbacks.
MaxPendingPostCallbacksint5_000100..1_000_000Layer 2 global cap for post-process (MessageProcessed / CallbackLane.Post) callbacks.
MaxPendingPostPerIpint321..10_000Layer 2 per-endpoint fairness cap for CallbackLane.Post callbacks.
MaxCallbackExecutionMsint501..60_000Execution time threshold (ms) for a single callback before a slow-callback warning is logged and SlowCallbacks is incremented.
MaxPooledCallbackStatesint1_00064..100_000Configuration bound for reusable callback state wrappers.
FairnessMapSizeint16_3841_024..65_536Size of the fixed long[] fairness buckets (s_perIpProcessMap and s_perIpPostMap) used for per-endpoint callback accounting.

Validation Rules

Validate() runs DataAnnotations validation followed by three cross-field checks:

  • CallbackWarningThreshold < MaxPendingNormalCallbacks (when CallbackWarningThreshold > 0)
  • MaxPendingPerIp <= MaxPendingNormalCallbacks
  • MaxPendingPostPerIp <= MaxPendingPostCallbacks

Layer 1: Per-Connection Receive Throttles (Connection.OnFrameReceived & SocketConnection)

When a complete frame is assembled in SocketConnection.PROCESS_FRAME_FROM_BUFFER, it calls _connectionOwner?.OnFrameReceived(lease) on the owning Connection. When AsyncCallback.CanRunInline(backing.MessageProcessing) is false, Connection.OnFrameReceived increments backing.PendingProcessCallbacks before queuing the frame:

  • If the incremented counter exceeds MaxPerConnectionPendingPackets:
    • The counter is immediately decremented, IncrementPacketsDropped() is called, and the frame lease is disposed.
    • If OverflowPolicy == NetworkOverflowPolicy.Disconnect, the connection is closed immediately (Disconnect()) to protect the server from slow-consumer / flood abuse.
    • If OverflowPolicy == NetworkOverflowPolicy.DropPacket (default), the packet is dropped and the connection remains open.
  • Otherwise, the frame is wrapped in ConnectionEventArgs and dispatched via:
    AsyncCallback.Invoke(
        s_messageProcessingBridge,
        this,
        args,
        CallbackLane.Process,
        releasePendingPacketOnCompletion: true);
    When the callback finishes executing, PooledConnectEventContext notifies the connection (ReleasePendingPacket()) to decrement backing.PendingProcessCallbacks.

Fragment Stream Cap

When a fragmented frame arrives and opens a new stream in FragmentAssembler, SocketConnection enforces MaxPerConnectionOpenFragmentStreams. If the count exceeds MaxPerConnectionOpenFragmentStreams, the stream is rejected and the frame is dropped. Expired fragment streams are periodically evicted via FragmentAssembler.EvictExpired(...).

Layer 2: AsyncCallback Dual-Lane Backpressure

AsyncCallback separates normal callbacks into two isolated lanes (CallbackLane.Process for MessageProcessing and CallbackLane.Post for MessageProcessed) plus a high-priority cleanup lane (InvokeHighPriority for ConnectionClosed):

Architecture Diagram
flowchart TD
    I["AsyncCallback.Invoke(delegate, sender, args, lane)"] --> G{"Reserve Global Slot?<br/>(Process: MaxPendingNormalCallbacks<br/>Post: MaxPendingPostCallbacks)"}
    G -- No --> D1["Increment Dropped + Return false"]
    G -- Yes --> E{"Reserve Per-IP Fairness Bucket?<br/>(Process: MaxPendingPerIp<br/>Post: MaxPendingPostPerIp)"}
    E -- No --> R["Rollback Global Slot + Increment Dropped"]
    E -- Yes --> Q["Rent PooledConnectEventContext + UnsafeQueueUserWorkItem"]
    Q --> X["Execute Callback -> Measure Elapsed Ms -> Release Slots"]

High-Priority Cleanup Lane

AsyncCallback.InvokeHighPriority(...) is used by Connection when raising ConnectionClosed. It bypasses MaxPendingNormalCallbacks and MaxPendingPerIp so disconnect cleanup and resource release always run even during severe callback saturation.

Diagnostics (AsyncCallbackMetrics)

AsyncCallback.GetStatistics() returns an internal readonly struct AsyncCallbackMetrics:

PropertyTypeDescription
PendingProcessintLive count of queued CallbackLane.Process callbacks.
PendingPostintLive count of queued CallbackLane.Post callbacks.
TotallongCumulative callbacks scheduled across all lanes.
DroppedlongCumulative callbacks rejected by global, per-IP, or ThreadPool backpressure.
SlowCallbackslongCumulative callbacks whose execution time exceeded MaxCallbackExecutionMs.
FairnessCollisionslongCumulative hash-bucket collisions detected in the per-IP fairness maps.
FairnessEvictionslongCumulative fairness bucket evictions when idle slots are reclaimed.

INI Configuration Example

[NetworkCallback]
MaxPerConnectionPendingPackets = 16
MaxPerConnectionOpenFragmentStreams = 4
OverflowPolicy = DropPacket
MaxPendingNormalCallbacks = 10000
CallbackWarningThreshold = 5000
MaxPendingPerIp = 64
MaxPendingPostCallbacks = 5000
MaxPendingPostPerIp = 32
MaxCallbackExecutionMs = 50
MaxPooledCallbackStates = 1000
FairnessMapSize = 16384