Middleware Pipeline
On this page
- Source Mapping#
- Middleware Contract (IPacketMiddleware<TPacket>)#
- Registration & Ordering (Use)#
- Execution Flow & Pooled Runner Lifecycle#
- Cancellation Propagation Between Handler and OutboundAlways#
- Error Handling (ConfigureErrorHandling)#
- Telemetry Structs (PipelineMetrics & PerMiddlewareMetrics)#
- Related APIs#
Namespace: Nalix.Runtime.Middleware
Assembly: Nalix.Runtime
MiddlewarePipeline<TPacket> (internal sealed class MiddlewarePipeline<TPacket> where TPacket : IPacket) orchestrates inbound and outbound IPacketMiddleware<TPacket> execution around terminal packet handlers. It uses immutable PipelineSnapshot arrays, a 32-slot bitmask local runner pool (PooledPipelineContext), and direct typed terminal invocation so zero per-packet closures are allocated.
Source Mapping
src/Nalix.Runtime/Middleware/MiddlewarePipeline.cssrc/Nalix.Runtime/Middleware/MiddlewarePipeline.Types.cssrc/Nalix.Abstractions/Middleware/IPacketMiddleware.cssrc/Nalix.Abstractions/Middleware/MiddlewareOrderAttribute.cssrc/Nalix.Abstractions/Middleware/MiddlewareStageAttribute.cs
Middleware Contract (IPacketMiddleware<TPacket>)
namespace Nalix.Abstractions.Middleware;
public interface IPacketMiddleware<TPacket> where TPacket : IPacket
{
ValueTask InvokeAsync(
IPacketContext<TPacket> context,
Func<CancellationToken, ValueTask> next);
}
- Calling
await next(token)advances to the next middleware or transitions to the terminal handler. - Returning without calling
nextshort-circuits the pipeline (the handler and outbound stages do not run).
Registration & Ordering (Use)
Configured via PacketDispatchOptions<TPacket>.WithMiddleware(middleware):
- Throws
ArgumentNullExceptionifmiddlewareisnull. - Throws
InternalErrorExceptionif the exact same middleware instance is registered twice. - Reads
[MiddlewareOrder(int)](default0) and[MiddlewareStage(MiddlewareStage, AlwaysExecute = bool)](defaultMiddlewareStage.Inbound,AlwaysExecute = false), caching the metadata perType. - Sorts stage lists and publishes a new immutable
PipelineSnapshot:- Inbound: sorted by ascending
Order(a.Order.CompareTo(b.Order)). - OutboundAlways: sorted by descending
Order(b.Order.CompareTo(a.Order)). - Outbound: sorted by descending
Order(b.Order.CompareTo(a.Order)).
- Inbound: sorted by ascending
| Built-in Middleware | Stage | Order | Purpose |
|---|---|---|---|
PermissionMiddleware | Inbound | -50 | Fail-closed [PacketPermission] authorization check. |
RateLimitMiddleware | Inbound | 50 | [PacketRateLimit] (PolicyRateLimiter) or endpoint TokenBucketLimiter check. |
TimeoutMiddleware | Inbound | 75 | Per-handler [PacketTimeout] cancellation wrapper. |
Execution Flow & Pooled Runner Lifecycle
flowchart TD
A["ExecuteAsync(context, dispatch, descriptor, ct)"] --> B{"snapshot.IsEmpty?"}
B -->|Yes| C["dispatch.ExecuteTerminalHandler(descriptor, context, ct)"]
B -->|No| D["Acquire PooledPipelineContext (32-slot local bitmask pool or ObjectPoolManager)"]
D --> E["Stage 1: Inbound (ascending Order)"]
E -->|Calls next(inboundCt)| F["CREATE_EXECUTION_TOKEN(inboundCt, rootCt) + ExecuteTerminalHandler"]
E -->|Short-circuits| K["Return PooledPipelineContext"]
F --> G["Stage 2: OutboundAlways (descending Order, runs even if handler threw OperationCanceledException)"]
G --> H{"!context.SkipOutbound && !token.IsCancellationRequested?"}
H -->|Yes| I["Stage 3: Outbound (descending Order)"]
H -->|No| J["Re-throw OperationCanceledException if handler cancelled"]
I --> J
J --> KCancellation Propagation Between Handler and OutboundAlways
When the terminal handler throws OperationCanceledException (for example, when TimeoutMiddleware cancels inboundCt):
EXECUTE_HANDLER_FULL_ASYNCcatches theOperationCanceledExceptiontemporarily.- Runs all
OutboundAlwaysmiddleware so audit/cleanup logic still executes. - Re-throws the
OperationCanceledExceptionafterOutboundAlwayscompletes so upstreamTimeoutMiddlewarecan observe the timeout and sendDirective(TIMEOUT).
Error Handling (ConfigureErrorHandling)
Configured via PacketDispatchOptions<TPacket>.WithErrorHandlingMiddleware(bool continueOnError, Action<Exception, Type>? errorHandler = null):
continueOnError = false(default): Non-fatal middleware exceptions incrementTotalErrorsand propagate to the caller, terminating the pipeline.continueOnError = true: InvokeserrorHandler(ex, middlewareType)and callsnext(token)to continue pipeline execution.
Telemetry Structs (PipelineMetrics & PerMiddlewareMetrics)
Exposed via PacketDispatchOptions<TPacket>.Metrics and PacketDispatchOptions<TPacket>.MiddlewareMetrics:
PipelineMetrics:ActiveExecutions(long),TotalExecutions(long),TotalExecutionTicks(long),MaxExecutionTicks(long),TotalErrors(long),AverageExecutionTime(TimeSpan).PerMiddlewareMetrics:MiddlewareType(Type),TotalExecutions(long),TotalExecutionTicks(long),MaxExecutionTicks(long),TotalErrors(long).