Connection Events
On this page
Namespace: Nalix.Network.Connections
Assembly: Nalix.Network
Network events on IConnection (MessageProcessing, MessageProcessed, and ConnectionClosed) are carried by pooled ConnectionEventArgs instances and scheduled through the internal AsyncCallback multi-lane dispatcher (CallbackLane.Process, CallbackLane.Post, and InvokeHighPriority).
Source Mapping
src/Nalix.Abstractions/Networking/IConnectionEventArgs.cssrc/Nalix.Network/Connections/Connection.EventArgs.cssrc/Nalix.Network/Connections/Connection.cssrc/Nalix.Network/Internal/Transport/AsyncCallback.cssrc/Nalix.Network/Options/NetworkCallbackOptions.cs
Event Execution Pipeline
sequenceDiagram
participant S as SocketConnection / UdpListenerBase
participant C as Connection (LocalPool)
participant AC as AsyncCallback
participant TP as ThreadPool
participant H as Protocol / Event Handler
S->>C: OnFrameReceived(lease) -> AcquireEventArgs() + Initialize(lease, connection)
alt AsyncCallback.CanRunInline(MessageProcessing) == true
C->>AC: InvokeInline(bridge, sender, args)
AC->>H: Raise MessageProcessing synchronously on I/O thread
AC->>C: Dispose ConnectionEventArgs
else Queued via AsyncCallback.Invoke(CallbackLane.Process / Post)
C->>AC: Invoke(bridge, sender, args, CallbackLane.Process)
alt Process / Post Lane (Under Global & Per-IP Cap)
AC->>TP: UnsafeQueueUserWorkItem(PooledConnectEventContext)
TP->>H: Raise MessageProcessing (or MessageProcessed on OnFrameSent)
H-->>TP: Complete
TP->>C: ReleasePendingPacket() + Dispose ConnectionEventArgs
else Throttled (Over Cap)
AC->>C: ReleasePendingPacket() + Dispose ConnectionEventArgs
end
endConnectionEventArgs (public sealed class)
namespace Nalix.Network.Connections;
public sealed class ConnectionEventArgs : EventArgs, IConnectionEventArgs, IPoolable, IPoolRentable
{
public IBufferLease? Lease { get; }
public IConnection Connection { get; }
public INetworkEndpoint NetworkEndpoint { get; }
public IBufferLease? ExchangeLease([Borrowed] IBufferLease? newLease);
public void OnRent();
public void ResetForPool();
public void Dispose();
}
ExchangeLease(newLease): Atomically swaps the underlyingIBufferLease?(used byTcpListenerBase/WebSocketListenerBaseafterFramePipeline.ProcessInbounddecrypts or decompresses an inbound frame in place) and returns the previous lease so the caller can dispose it.- Two-Tier Pooling:
ConnectionandWebSocketConnectionmaintain an 8-slot lock-freeLocalPoolforConnectionEventArgsandPooledConnectEventContext, falling back toObjectPoolManager.Sharedwhen the per-connection local pool is full.
Three Dispatch Lanes in AsyncCallback
| Event | AsyncCallback Lane | Global Cap | Per-IP Cap | Behavior |
|---|---|---|---|---|
MessageProcessing | CallbackLane.Process (or InvokeInline) | MaxPendingNormalCallbacks (10,000) | MaxPendingPerIp (64) | Decrypts/decompresses frame (FramePipeline.ProcessInbound), verifies sequence, and invokes IProtocol.ProcessMessage. Executes inline via AsyncCallback.CanRunInline / InvokeInline when bound to a single IFrameProcessor { SupportsInlineProcessing: true } subscriber. |
MessageProcessed | CallbackLane.Post | MaxPendingPostCallbacks (5,000) | MaxPendingPostPerIp (32) | Triggered by Connection.OnFrameSent when an outbound frame send completes; invokes IProtocol.PostProcessMessage (metrics & KeepConnectionOpen teardown). |
ConnectionClosed | InvokeHighPriority | Unbounded | Unbounded | Bypasses all normal/post backpressure gates so disconnect cleanup and unregistration never stall under flood conditions. |
Per-IP Fairness Accounting (FairnessMapSize = 16384)
AsyncCallback hashes IConnectionEventArgs.NetworkEndpoint into fixed-size long[FairnessMapSize] arrays (s_perIpProcessMap and s_perIpPostMap, default 16,384 slots), packing ((long)(uint)hash << 32) | count into each 64-bit slot to track pending callbacks per remote IP without heap allocations. Slow callbacks exceeding MaxCallbackExecutionMs (50 ms) are counted in AsyncCallbackMetrics.SlowCallbacks and logged via throttled diagnostics.