Summary
- The core blocker for wiring Http into the Web resource. WebApplicationServer.StartAsync queues ONE async-void ThreadPool work item whose loop accepts a connection and then inline-awaits its entire ReceiveAsync enumerable: a single idle HTTP/1.1 keep-alive client starves every other connection indefinitely (they pile up in the listener's 512-slot backlog, never served). There is no try/catch (an application exception escapes the async-void body and CRASHES THE PROCESS via thread-pool escalation — and even graceful StopAsync triggers this via OperationCanceledException), connections/contexts are never disposed (violating the transport DESIGN.md disposal contract), and no connection-count limit exists.
- The server owns application-exception isolation, per-connection dispatch, disposal, in-flight tracking for StopAsync drain, and an optional MaxConcurrentConnections cap; wire-level failure isolation already lives in Http.Connections and must not be duplicated. Web.Hosting has no docs/DESIGN.md — create it in this change (server dispatch model, stop semantics).
Captured 2026-07-03 during the Http/Web requirements audit; every claim below was adversarially verified against the codebase before filing.
Acceptance Criteria
- With connection A held open as an idle HTTP/1.1 keep-alive connection (one request already served), a request sent on a second connection B receives a complete response within a bounded time (e.g. 2s) — proving accepted connections are served concurrently, not serially.
- An exception thrown from the middleware pipeline while serving one connection tears down and disposes only that connection; the server loop keeps running, a subsequent new connection is accepted and served, and the process does not crash from an unhandled async-void/thread-pool exception.
- Every accepted IHttpConnection and opened IHttpConnectionContext is disposed when its per-connection loop ends, whether it ends normally, by client disconnect, or by pipeline fault (verified with an instrumented fake listener/connection).
- StopAsync cancels the accept loop, awaits in-flight connection tasks (graceful drain), and completes without any unobserved OperationCanceledException or other escaped exception.
- A MaxConcurrentConnections option on the server (default documented — unlimited or a stated default) is enforced: when the limit is reached, additional accepted connections are not opened/served until an active connection completes, and this is covered by a test.
- All new behavior is covered by tests in resources/Web/Assimalign.Cohesion.Web.Hosting/tests using xUnit + Shouldly per repo conventions, and the WebApplicationServer no longer uses ThreadPool.UnsafeQueueUserWorkItem with an async lambda.
Standards and Compliance
- Runtime-contract concern — no wire-protocol conformance applies. AOT-safe (SemaphoreSlim gate, stored Tasks, no reflection); no Microsoft.Extensions.*; per-service execution model per libraries/Hosting docs/DESIGN.md
Implementation notes (from the audit)
Change lands entirely in resources/Web/Assimalign.Cohesion.Web.Hosting (project Assimalign.Cohesion.Web.Hosting.csproj): rewrite WebApplicationServer.StartAsync to accept in a dedicated loop and dispatch each accepted connection to its own tracked Task (per-connection loop = OpenAsync + await foreach ReceiveAsync + pipeline execute/send/dispose, wrapped in try/catch and await using). If a MaxConcurrentConnections knob is exposed, it goes on WebApplicationServerOptions/WebApplicationServerBuilder (SemaphoreSlim gate around accept — AOT-safe, no reflection). Layering boundary: wire-level failure isolation and per-stream RST/GOAWAY handling already live in Assimalign.Cohesion.Http.Connections (its docs/DESIGN.md lines 303-332) and must NOT be duplicated; the server owns only application-exception isolation, per-connection dispatch, connection disposal, in-flight tracking for StopAsync drain, and the optional cap. No transport prerequisites: HttpConnectionListener already buffers accepted connections in a channel and HttpConnectionListenerTests.cs line 40 (AcceptOrListenAsync_OnSequentialAccepts_ShouldBacklogConnections) proves backlogged accepts survive until drained. While there, replace the async-void ThreadPool.UnsafeQueueUserWorkItem(WaitCallback) pattern with a stored Task (process-crash hazard) and make StopAsync await the accept loop + in-flight connections, swallowing only OperationCanceledException. The stale Web.ApplicationModel HttpServer.cs (references nonexistent ITransport) shows the previously intended dispatch shape but is dead code — do not resurrect it; consider deleting separately. Tests go in resources/Web/Assimalign.Cohesion.Web.Hosting/tests (currently empty csproj); use xUnit v2 + Shouldly with the 'Cohesion Test [Web Hosting] - ...' display-name convention. AGENTS.md rules apply: file-scoped namespaces, implementations internal, AOT-compatible, no Microsoft.Extensions.*.
Summary
Acceptance Criteria
Standards and Compliance
Implementation notes (from the audit)
Change lands entirely in resources/Web/Assimalign.Cohesion.Web.Hosting (project Assimalign.Cohesion.Web.Hosting.csproj): rewrite WebApplicationServer.StartAsync to accept in a dedicated loop and dispatch each accepted connection to its own tracked Task (per-connection loop = OpenAsync + await foreach ReceiveAsync + pipeline execute/send/dispose, wrapped in try/catch and
await using). If a MaxConcurrentConnections knob is exposed, it goes on WebApplicationServerOptions/WebApplicationServerBuilder (SemaphoreSlim gate around accept — AOT-safe, no reflection). Layering boundary: wire-level failure isolation and per-stream RST/GOAWAY handling already live in Assimalign.Cohesion.Http.Connections (its docs/DESIGN.md lines 303-332) and must NOT be duplicated; the server owns only application-exception isolation, per-connection dispatch, connection disposal, in-flight tracking for StopAsync drain, and the optional cap. No transport prerequisites: HttpConnectionListener already buffers accepted connections in a channel and HttpConnectionListenerTests.cs line 40 (AcceptOrListenAsync_OnSequentialAccepts_ShouldBacklogConnections) proves backlogged accepts survive until drained. While there, replace the async-void ThreadPool.UnsafeQueueUserWorkItem(WaitCallback) pattern with a stored Task (process-crash hazard) and make StopAsync await the accept loop + in-flight connections, swallowing only OperationCanceledException. The stale Web.ApplicationModel HttpServer.cs (references nonexistent ITransport) shows the previously intended dispatch shape but is dead code — do not resurrect it; consider deleting separately. Tests go in resources/Web/Assimalign.Cohesion.Web.Hosting/tests (currently empty csproj); use xUnit v2 + Shouldly with the 'Cohesion Test [Web Hosting] - ...' display-name convention. AGENTS.md rules apply: file-scoped namespaces, implementations internal, AOT-compatible, no Microsoft.Extensions.*.