Skip to content

perf(middleware): cheap clones + unboxed futures for hot layers - #30

Merged
aarambh-darshan merged 1 commit into
mainfrom
perf/middleware-hot-path
Aug 25, 2026
Merged

aarambh-darshan merged 1 commit into
mainfrom
perf/middleware-hot-path

Conversation

@aarambh-darshan

Copy link
Copy Markdown
Member

Summary

First implementation wave from AUDIT_FINDINGS_MIDDLEWARE.md — the middleware-focused optimization audit that studied tower-http 0.6.11 and actix-web 4.15.0/actix-cors 0.7.1 sources for technique transfer.

Changes

MW1 — cheap service clones (HIGH)

RateLimitLayer's KeyExtractor (contains String) and ProxyTrust (Vec) moved behind a shared Arc<RateLimitConfig>. Cloning the per-request service was copying both on every call through every stack containing this layer.

MW2 — concrete futures (HIGH)

Four hottest layers replaced Pin<Box<dyn Future>> with pin-project concrete generic futures wrapping the inner future directly — one fewer heap allocation + vtable dispatch per request per layer:

Layer Future Notes
TraceLayer TraceFuture<S> gated-span short-circuit + latency records moved into poll
RequestIdService RequestIdFuture<S> shared by Request + Propagate variants
TimeoutLayer TimeoutFuture<S> handler & deadline polled side-by-side
CORS (kept single box) preflight/no-Origin hoisted sync; async tail needs tower-Oneshot-style infra — documented follow-up

Bonus quick wins (O26)

  • JSON/Form content-type fast paths skip full Mime parse for exact constants
  • SSE normalization skips two allocations when no CR present

Verification (rustc 1.98, CI flags)

cargo test  --workspace --all-features                   ✅ 171 passed / 0 failed
cargo clippy --workspace --all-features -- -D warnings   ✅ clean
cargo fmt --all -- --check                               ✅

Benchmark re-run after merge will quantify; audit predicts most of the wrk-mw gap to actix closes via MW2 (per-layer boxes were the structural difference).

MW1 — RateLimitLayer's KeyExtractor and ProxyTrust (String + Vec
fields) moved behind a shared Arc<RateLimitConfig>: cloning the
per-request service is now refcount bumps instead of copying the
extractor and proxy list on every call.

MW2 — concrete generic futures replace Pin<Box<dyn Future>> in the four
hottest layers, eliminating one heap allocation + vtable dispatch per
request per layer:

- TraceFuture: pin-project wrapper around Instrumented<S::Future>;
  gated-span short-circuit, latency records, and error/warn/info events
  moved into poll.
- RequestIdFuture: concrete future shared by RequestIdService and
  PropagateRequestIdService; response-header insertion moved to poll.
- TimeoutFuture: polls handler and deadline side-by-side (handler wins
  ties), replacing tokio::time::timeout's own boxed wrapper.
- Cors: preflight/no-Origin decisions hoisted to synchronous code before
  future construction; the cross-origin tail keeps its single box (its
  async tail depends on response values — full unboxing needs tower's
  Oneshot-style infrastructure, documented as follow-up).

Also: JSON/Form content-type fast paths skip the full Mime parse for
exact constants; SSE data/comment normalization skips two allocations
when no CR is present.

Verified on rustc 1.98.0 with CI flags: clippy --workspace
--all-features -- -D warnings clean; fmt clean; cargo test --workspace
--all-features 171 passed / 0 failed. Full findings in
AUDIT_FINDINGS_MIDDLEWARE.md.

Co-Authored-By: Claude <noreply@anthropic.com>
@aarambh-darshan
aarambh-darshan merged commit 31535ec into main Aug 25, 2026
5 checks passed
@aarambh-darshan
aarambh-darshan deleted the perf/middleware-hot-path branch August 25, 2026 04:35
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