Skip to content

Avoid request transform context allocation - #3041

Draft
artl93 wants to merge 2 commits into
dotnet:mainfrom
artl93:artl93-optimize-transform-header-flow
Draft

Avoid request transform context allocation#3041
artl93 wants to merge 2 commits into
dotnet:mainfrom
artl93:artl93-optimize-transform-header-flow

Conversation

@artl93

@artl93 artl93 commented Jul 13, 2026

Copy link
Copy Markdown
Member

Motivation

StructuredTransformer.TransformRequestAsync allocates a RequestTransformContext for every structured request-transform pipeline. That general context is necessary when transforms can mutate destination/path/query state, observe cancellation or request bodies, run asynchronously, or generate custom behavior. It is unnecessary when every request transform is an exact built-in header-only transform.

The removed object is 72 bytes per eligible request on the measured .NET 9 runtime.

Change

At transformer construction, compute whether all request transforms are exact known built-in header transforms. Eligible pipelines execute the same transform array in the same order through an internal synchronous path using only:

  • HttpContext
  • HttpRequestMessage
  • request-local headersCopied state

Exact type checks preserve derived-transform overrides. If any transform is path/query/body/context/custom/async/derived, the entire pipeline uses the existing RequestTransformContext path; there is no mixed-mode execution.

The eligible boundary covers original host, X-Forwarded For/Host/Proto/Prefix, RFC Forwarded, header remove/value/route-value, and header allow-list transforms. Tests now assert eligibility/fallback selection, RequestHeadersAllowed ordering and HeadersCopied transitions, original-host and route-value behavior, derived fallback, and that every allowlisted type overrides the internal fast-path method.

BenchmarkDotNet

EgorBot does not support YARP. Its official README describes a service for dotnet/runtime, and the runtime performance skill lists only dotnet/runtime and EgorBot/runtime-utils. A portable BenchmarkDotNet harness is therefore the pending campaign-policy substitute.

Exact comparison:

  • Parent: 49b23da6d8b0e9df47abf45d7755173f84670941
  • Final: 43911dfd95adf7bd45e2d7cbcb768a24c8c14a18
  • BenchmarkDotNet 0.15.8 / .NET 9.0.2
  • macOS 26.5, Apple M5 Pro, Arm64
  • Job.ShortRun, MemoryDiagnoser, 3 measured iterations, 3 warmups, one launch
  • two isolated copies of the same harness, each directly referencing one exact-SHA DLL
  • 0 and 50 extra request headers

Allocation is the primary metric. The two isolated runs are sequential, so timing is retained only as supporting evidence and no latency claim is made.

Capability Scenarios Cells Allocation delta
Eligible built-ins default, original-host, X-Forwarded, Forwarded, header remove/value/route-value/allow-list, representative multi-header pipeline, response-header pipeline with built-in request handling 20 -72 B/op in every cell
General fallback/control direct, path/query, mixed built-in + path/query, custom sync, custom async, custom context mutation, custom body observation, derived built-in 16 0 B/op in every cell

Representative exact values:

Scenario Headers Parent Final Delta
Default X-Forwarded 0 2,464 B 2,392 B -72 B
Default X-Forwarded 50 7,528 B 7,456 B -72 B
Request header set/append/remove 0 3,360 B 3,288 B -72 B
Header allow-list 50 1,304 B 1,232 B -72 B
Mixed headers + path/query 0 5,840 B 5,840 B 0 B
Custom async 50 8,160 B 8,160 B 0 B
Derived built-in 0 3,112 B 3,112 B 0 B

A separate exact-SHA Kestrel campaign ran 96 interleaved parent/final processes across default, mixed path/query, custom async, and body-observing pipelines; H1/H2; 0/50 extra headers; 10,000 requests after 1,000 warmups; concurrency 64. All requests completed successfully. Process-wide allocation/throughput includes client, proxy, backend, and thread-pool noise, so it is realistic functional evidence rather than a precise performance claim.

Evidence manifests:

  • BDN: /Users/artl/.copilot/session-state/294ad512-dbe2-42ad-8a82-c528f296fdb3/files/readiness-bdn-runs (SHA256SUMS hash 1f18efc3277968b55f6178830a1c6a3d39a5b555c6b03bba2d85325123132150)
  • Kestrel: /Users/artl/.copilot/session-state/294ad512-dbe2-42ad-8a82-c528f296fdb3/files/readiness-e2e (SHA256SUMS hash 4aebaf19d23a4a843898486a6e2ba500315481b3604931b07d29fb9a2bd467e9)

Validation

  • Fast-path differential tests: 30 passed across net8.0/net9.0
  • All transform tests: 1,080 passed
  • Full solution build: 0 warnings, 0 errors
  • Full solution tests: 4,729 total; 4,657 passed; 72 skipped; 0 failed
  • CI: green on Ubuntu, Windows, macOS, Docker build, markdown lint, and CLA
  • Review threads: none
  • Skeptical capability-boundary review: no correctness issue; the identified eligibility/allow-list/override guard gaps were added as tests

Risks and limits

  • The benefit is deliberately narrow: one non-eligible transform sends the whole pipeline through the existing general path.
  • Exact-type allowlisting is maintenance-sensitive; tests now prove eligible pipelines select the fast path and all allowlisted types implement it.
  • The fast path relies on eligible transforms remaining synchronous, header-only, unable to generate responses, and unable to mutate path/query/destination or observe cancellation/body state.
  • The gain is deterministic but modest: 72 B/request. BDN timing and Kestrel throughput are noisy; no CPU/throughput improvement is claimed.
  • EgorBot cannot provide YARP lab coverage. The portable BDN harness is ready for a future cross-platform campaign, but the reported execution is local macOS Arm64.

AI disclosure

Implementation, tests, benchmark harnesses, evidence analysis, and this PR text were prepared with GitHub Copilot assistance and manually validated.

Art Leonard added 2 commits July 12, 2026 16:31
Use a synchronous fast path when the request pipeline contains only exact built-in header transforms. Derived, custom, path, query, and asynchronous transforms retain the existing context-based pipeline.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a5fc9838-21b3-4a7a-b73a-c505ecf64ca1
Assert fast-path eligibility and fallback selection, cover allowed-header ordering, original host and route values, and guard built-in fast-path overrides.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a5fc9838-21b3-4a7a-b73a-c505ecf64ca1
@artl93 artl93 changed the title WIP: DO NOT REVIEW avoid request transform context allocation Avoid request transform context allocation Jul 20, 2026
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.

1 participant