Router ASP.NET request cancellation

Contract

AspNetRouterRequestDispatcher.DispatchAsync receives the cancellation token owned by the active Router transport request. The dispatcher assigns that token directly to HttpContext.RequestAborted before endpoint matching and keeps it valid for the complete dispatch lifetime.

This preserves the cancellation chain:

  1. The gateway sends a correlated CANCEL frame when the caller abandons a request or its bound expires.
  2. The microservice transport cancels the request-specific token source for that correlation.
  3. AspNetRouterRequestDispatcher exposes that same token as HttpContext.RequestAborted.
  4. ASP.NET endpoint handlers, model binders, filters, and downstream operations observing RequestAborted stop promptly.

Q-8 executed (2026-09-01, SPRINT_20260824_010 P8-15). messaging.cancel-control.v1 is the ONLY cancellation delivery path — the mixed/legacy-fleet compatibility copy and the client-side legacy request-queue CANCEL consumption are deleted. During a rollout, upgrade every messaging microservice to advertise the capability before relying on cancel; a non-advertising replica receives no cancellations, and a fleet-wide missing capability is a deploy-order defect, not a reason to re-add the fallback.

Control-lane delivery

The gateway fans a signed CANCEL to each live healthy replica advertising messaging.cancel-control.v1. Nothing is placed on the shared service request queue: cancellation traffic cannot queue behind ordinary requests, and a replica without the capability (or a gateway without a frame-envelope signer) receives no cancellation at all — the drop is logged.

The dedicated cancellation consumer leases up to CancelControlBatchSize controls per wake (default 128). This setting is intentionally independent from the normal BatchSize and MaxConcurrentRequests bounds. A cancellation burst can therefore drain in one control-loop lease without increasing ordinary request concurrency or response batch size. Non-positive values fail options validation at startup.

Ownership and lifetime

Regression guard

AspNetRouterRequestDispatcherTests.DispatchAsync_CancellationAfterEndpointStarts_CancelsRequestAbortedPromptly starts a dispatched endpoint, cancels only after endpoint execution has begun, and requires RequestAborted plus the dispatch task to settle inside a hard bound. This ordering prevents a pre-cancelled token from masking a lifetime regression.

AspNetRouterRequestDispatcherTests.CompositeDispatch_TransportTokenRemainsRequestAbortedUntilEndpointCompletes exercises the production composition boundary and requires the transport token observed by CompositeRequestDispatcher to remain the exact HttpContext.RequestAborted token until endpoint completion. This prevents a future wrapper or linked-token lifetime from silently detaching the transport cancellation chain.

MessagingTransportCancelFallbackTests proves the complementary queue contract: an all-capable fleet receives only dedicated control delivery, and mixed or legacy-only fleets receive NO shared-queue copy — the legacy replica observably gets nothing (the Q-8 red-proof).

MessagingTransportCancelControlBatchTests records the actual control queue lease request, proves the default operational burst size is 128, and verifies configured-value and non-positive validation behavior independently from the normal request batch.

The live acceptance forcing gate complements the unit test with 48 authenticated Findings UI reads canceled after 35 ms. The gate then requires five sequential Findings summary reads to return without HTTP 504 or client timeout while Router and Findings remain healthy with no restarts or error logs.

Operational diagnosis

If cancellation appears to arrive at the transport but ASP.NET work remains in flight:

Related documents: