Skip to content

Fix response connection persistence handling - #788

Merged
haga-rak merged 3 commits into
haga-rak:mainfrom
bbartels:copilot/add-header-value-token-method
Sep 8, 2026
Merged

haga-rak merged 3 commits into
haga-rak:mainfrom
bbartels:copilot/add-header-value-token-method

Conversation

@bbartels

@bbartels bbartels commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Problem

HTTP/1.1 connections are persistent unless either peer requests closure. Fluxzy instead gives a response without an explicit Keep-Alive: timeout an implicit one-second protocol idle timeout. The pool subtracts 200 ms from that value, so a healthy connection can be discarded after roughly 800 ms even though the origin never limited its lifetime.

Changing that default without considering HTTP/1.0 would make HTTP/1.0 responses persistent even when they did not negotiate Connection: keep-alive. Connection options are also comma-separated token lists, but the existing whole-value comparison misses values such as Connection: custom, close and Connection: custom, keep-alive.

HTTP/1.0 persistence is safe only when the response has a self-defined message length. Treating an unbounded HTTP/1.0 body as persistent can leave the proxy waiting for EOF while the origin waits for the next request. A non-final-chunked Transfer-Encoding is likewise close-delimited and overrides any accompanying Content-Length; trusting that length can recycle a connection with unread transfer-coded bytes. A bare 205 Reset Content is not self-delimiting without explicit zero-length framing.

Solution

Represent an absent HTTP/1.1 protocol idle limit as -1, matching the pool's existing "no peer-supplied limit" sentinel. Preserve explicit Keep-Alive timeout/max handling and the existing global unused-pool timeout.

Retain the source response version while parsing flat HTTP/1 headers. HTTP/1.0 remains close-by-default and is reusable only when Connection: keep-alive is present and the response is self-delimiting through Content-Length or valid no-body status semantics. Direct field-based responses are H2 and do not inherit HTTP/1.0 behavior.

Parse Connection as a comma-separated, case-insensitive token list with optional whitespace. Use that parser consistently for close, keep-alive, and upgrade decisions.

For a response whose final transfer coding is not chunked, remove Content-Length, reset the parsed length, and force close-delimited handling for both HTTP/1.0 and HTTP/1.1.

Benefits

  • Preserves ordinary HTTP/1.1 connections until the peer or configured global idle policy closes them.
  • Keeps HTTP/1.0 close-by-default and prevents indefinite reads of non-self-delimited HTTP/1.0 bodies.
  • Prevents connection desynchronization when non-final-chunked transfer coding accompanies a misleading content length.
  • Honors compound and repeated Connection fields without substring matching.
  • Retains explicit timeout, maximum-request, close, and protocol-upgrade behavior.
  • Keeps H2/direct-header response semantics unchanged.

The original current-master performance campaign used four request waves separated by 1.25 seconds:

Workload Current master Candidate Paired median effect
Idle-wave H1 empty, c16 1.27k req/s 3.26k req/s +170.3% (pair range +149.9% to +230.5%)
Idle-wave H1 8 KiB, c16 1.09k req/s 3.21k req/s +123.0% (pair range +112.5% to +259.8%)
Idle-wave H1 8 KiB, c56 1.93k req/s 4.35k req/s +164.6% (pair range +104.0% to +184.4%)

New measured upstream TLS connections fell from 64/61/224 to zero. Across all three workloads p99 fell 58-65%, allocation/request fell 53-69%, and CPU/request fell 34-38%. The later HTTP/1.0, token-parsing, and transfer-framing hardening does not alter the measured HTTP/1.1 path, but was not separately benchmarked.

Changes

  • Default ResponseHeader.TimeoutIdleSeconds to the existing unlimited sentinel.
  • Detect HTTP/1.0 response status lines and require explicitly negotiated, self-delimited persistence.
  • Add allocation-free token parsing for comma-separated header values.
  • Apply token parsing to response close, keep-alive, and upgrade options.
  • Normalize non-final-chunked transfer coding as close-delimited and discard conflicting content length.
  • Cover implicit and explicit HTTP/1.1 persistence, HTTP/1.0 framing, compound/repeated connection options, H2 construction, transfer-coding precedence, status 205, and real TLS connection reuse.
  • Allow the in-process HTTPS fixture to force HTTP/1.1.

Verification

The updated branch was built and tested locally with .NET 10.0.101. Existing compiler, analyzer, and obsolete-API warnings remain; no new errors were introduced.

  • Debug focused parser/framing/pool/upgrade/keep-alive set: 66 passed, 0 failed, 0 skipped.
  • Release focused parser/framing/pool/upgrade/keep-alive set: 66 passed, 0 failed, 0 skipped.
  • Debug complete Fluxzy.Tests.UnitTests.Core set: 189 passed, 0 failed, 0 skipped.
  • Release complete Fluxzy.Tests.UnitTests.Core set: 189 passed, 0 failed, 0 skipped.
  • Debug complete Fluxzy.Tests.UnitTests.H11Client set: 10 passed, 0 failed, 0 skipped.
  • Independent final read-only review found no remaining medium-or-higher correctness issues after the transfer-framing and status-205 follow-up.
  • The real TLS test verifies that two HTTP/1.1 requests 1.25 seconds apart reuse one upstream connection.
  • Git whitespace validation passes with the repository's CRLF convention (core.whitespace=cr-at-eol).

The complete repository test suite was not run because it includes environment-dependent external-network, certificate-installation, and raw-capture cases. The test startup reported the expected inability to install its development CA in the unprivileged DevPod; the selected tests still completed successfully.

Line Count

Category Additions Removals Net
Documentation 0 0 0
Configuration 0 0 0
Runtime 84 4 +80
Tests 213 1 +212
Total 297 5 +292

Copilot AI and others added 3 commits August 22, 2026 15:16
Co-authored-by: bbartels <23058572+bbartels@users.noreply.github.com>
Added support for HTTP/2 in InProcessHost configuration.
@haga-rak
haga-rak merged commit 47957ef into haga-rak:main Sep 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants