Skip to content

feat(core): add shared request timing collector and middleware - #389

Open
ChristianPavilonis wants to merge 2 commits into
mainfrom
feat/shared-request-timing
Open

ChristianPavilonis wants to merge 2 commits into
mainfrom
feat/shared-request-timing

Conversation

@ChristianPavilonis

@ChristianPavilonis ChristianPavilonis commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Move generic request-timing ownership into EdgeZero: one portable origin, bounded phase slots, lifecycle marks, and application-owned payload under one shared mutex.
  • Add opt-in, insert-if-absent attachment middleware without moving application rendering or privacy policy upstream.
  • Coordinate adoption with Trusted Server #1121. TS migration and independent joint verification are complete against this exact candidate.

Changes

Crate / File Change
edgezero-core/src/request_timing.rs, src/lib.rs Generic RequestTimings<N, D>, consistent snapshots, compound updates, saturating accumulation, drop-time phase spans and explicit lifecycle marks
edgezero-core/src/middleware.rs Preserve an existing concrete handle; otherwise install request-local state unless the exact path is excluded
Core tests and shared adapter contract fixture Exercise typed application facade, one extension identity/origin, contention, invalid slots, poison recovery, cancellation, and portable runtime clock/attachment
Cloudflare/Fastly/Spin contract tests Run the shared timing scenario on each WASM runtime
Middleware guide and design/plan Document ownership, lifecycle limits, approved API contracts and coordinated delivery

Applications retain phase enums, domain facts, header names/order, serialization and exposure policy. In particular, Trusted Server retains Server-Timing rendering; no generic renderer is introduced. Callbacks run synchronously under one nonblocking mutex; panics propagate and poison recovery is best-effort, without rollback or payload validation. Attachment does not finalize requests, measure streamed-body completion, or cover unmatched routes.

Closes

No standalone EdgeZero issue. Related consumer work: IABTechLab/trusted-server#1121.

Test plan

Publication-stage reruns against this tree:

  • cargo test -p edgezero-core — 484 unit tests, two integration tests and one compile-fail doctest passed; 13 existing doctests ignored
  • cargo test --workspace --all-targets
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo fmt --all -- --check
  • cargo check --workspace --all-targets --features "fastly cloudflare spin"
  • cargo check -p edgezero-adapter-spin --target wasm32-wasip2 --features spin
  • git diff --check

Earlier implementation-stage validation, recorded in the committed plan (not rerun during publication):

  • WASM check/clippy matrix: Fastly wasm32-wasip1, Spin wasm32-wasip2, Cloudflare wasm32-unknown-unknown
  • Runtime contract suites: Cloudflare 8 tests in headless Chromium, Fastly 7 in Viceroy 0.17.0, Spin 13 in Wasmtime 44.0.1; Fastly library suite 88 tests
  • Generated-app build and excluded examples/app-demo gates
  • Docs lint/format and VitePress build
  • Deployed-provider smoke testing (local runtime harnesses are not deployed-provider evidence)
  • Trusted Server migration and joint validation at TS commit 2791ceb46908e6af04ccaae0c10b29d35336a9f4

Independent final verification

  • All 22 reported GitHub checks succeeded. Test/format CI used a synthetic merge whose tree was verified identical to candidate 9c03cc59300363ae5339fc970dded08ede563e98.
  • Fresh local core tests, documentation checks/build, and actual Cloudflare/Fastly/Spin WASM contract runs passed. The shared runtime test covers both middleware-created and preinstalled handles.
  • TS pins this exact commit without local overrides. All four TS adapter suites, CLI/parity, native/WASM lints, local integration/EC lifecycle, 899 Vitest tests, focused GPT diagnostics, and complete Next.js/WordPress browser suites passed.
  • Independent API/correctness/simplicity and joint consumer reviews found no remaining issues. The Axum regression demonstrates the original collector-reset failure and its fix. All 14 Trusted Server checks also passed at 2791ceb46908e6af04ccaae0c10b29d35336a9f4 before requesting review.

Coordinated delivery / merge gate

Trusted Server temporarily pins all six EdgeZero dependencies to 9c03cc59300363ae5339fc970dded08ede563e98 for integration and joint PR verification, with no local path override and one resolved EdgeZero core identity. That temporary commit pin is not the final delivery pin.

Trusted Server #1121 needs an actual approved EdgeZero release tag before merging to main. Replace all temporary pins with that release, regenerate and inspect its lockfile, and revalidate. Review readiness does not waive the release requirement. No merge or tag/release has been performed.

Checklist

  • Changes follow CLAUDE.md conventions
  • No Tokio or UUID dependencies added to core or adapter crates
  • Route params use {id} syntax where applicable; routing unchanged
  • Consumer types imported from edgezero_core, not directly from http
  • Store wiring unchanged (registry requirement not applicable)
  • New code has tests
  • No secrets or credentials committed
  • Independent EdgeZero reviews cleared

@aram356 aram356 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review

Summary

Adds a generic per-request timing collector (RequestTimings<N, D>) and opt-in, insert-if-absent attachment middleware to edgezero-core, with a shared runtime scenario in the three WASM adapter contract suites. I found no correctness bugs. I'm requesting changes on two API-shape items that are cheap now but become breaking changes once Trusted Server pins a release tag. Neither touches Trusted Server's call sites at 2791ceb.

Findings

Blocking

  • 🔧 TimingError not #[non_exhaustive]: inconsistent with EdgeError/KvError/ConfigStoreError, and adding it after the tag is breaking (request_timing.rs:17)
  • 🔧 Hoist t0 out of the mutex: makes elapsed() infallible and the origin structurally immutable; elapsed() currently fails under cross-thread contention (request_timing.rs:100)

Non-blocking

  • 🤔 Generic lifecycle names: headers_ready_total / request_elapsed / resp_bytes mirror TS internals (request_timing.rs:51)
  • 🤔 Cloudflare clock semantics: workerd's performance.now() advances only across I/O, and the contract test runs in Chromium (docs/guide/middleware.md:169)
  • ♻️ Shared fixture via cross-crate #[path], never run natively: Cloudflare/Fastly/Spin tests/contract.rs
  • ⛏ RequestTimings lacks Debug; must_use message wording; with_excluded_paths replace semantics undocumented
  • ⛏ Guide: show paired type aliases (a generic mismatch silently yields no handle); drop the reference to an internal test file
  • ⛏ Spec/plan status lines, unchecked tasks, local paths and "keep the PR draft" text are stale relative to the PR
  • 🌱 Contention drop counter for native runtimes; config-driven exclusions; exposing the middleware-created handle to outer layers

Behavioral claims above were checked with throwaway probe tests against 9c03cc59: generic-mismatch silence, elapsed() failing under cross-thread contention, exclusion-list replacement, and no handle on the response.

📌 Out of Scope

  • Deployed-provider smoke testing is still unchecked in the test plan. It matters most for Cloudflare, where the local harness doesn't use workerd's clock.
  • The Axum adapter has no tests/contract.rs, so the shared timing scenario doesn't run there.

CI Status

  • fmt: PASS
  • clippy (--all-features -D warnings): PASS
  • tests (--workspace --all-targets): PASS (core: 484 unit tests, plus the compile_fail doctest)
  • feature check (fastly cloudflare spin): PASS
  • Spin wasm32-wasip2 check: PASS
  • GitHub: all 22 checks green, including Cloudflare/Fastly/Spin WASM tests

Comment thread crates/edgezero-core/src/request_timing.rs
Comment thread crates/edgezero-core/src/request_timing.rs Outdated
Comment thread crates/edgezero-core/src/request_timing.rs Outdated
Comment thread crates/edgezero-core/src/request_timing.rs
Comment thread crates/edgezero-core/src/request_timing.rs Outdated
Comment thread docs/guide/middleware.md Outdated
Comment thread docs/guide/middleware.md Outdated
Comment thread docs/guide/middleware.md Outdated
Comment thread docs/superpowers/plans/2026-09-28-request-timing.md Outdated
Comment thread docs/superpowers/specs/2026-09-28-request-timing-design.md Outdated

@aram356 aram356 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review

Summary

Second pass at 9c03cc59. It covers areas the earlier review didn't reach: registering the middleware from the manifest, how the handle interacts with State<T>, CI coverage of the compile-fail test, how strong the runtime test is, and documentation consistency. I checked each finding against the code or with a throwaway probe. I'm requesting changes so these land together with the two 🔧 items from the earlier review, before the release tag.

Findings

Inline:

  • 🤔 edgezero.toml can't register the middleware: a const fn constructor fixes it (middleware.rs:86)
  • 🤔 State<RequestTimings<…>> returns a 500 that points at with_state (docs/guide/middleware.md:226)
  • 🤔 CI never runs the compile-fail test (request_timing.rs:40)
  • 🤔 Overlapping spans on one slot add up past wall-clock time (request_timing.rs:129)
  • 🤔 The runtime test can't catch a stopped clock (tests/support/request_timing.rs:37)
  • ⛏ span(slot)? in the guide doesn't compile in a handler (docs/guide/middleware.md:233)
  • ⛏ The web_time example needs a web-time dependency (docs/guide/middleware.md:27)
  • ⛏ Assertions that can't fail (middleware.rs:360, plus four more listed there)
  • ⛏ TimingSnapshot lacks the D = () default (request_timing.rs:45)
  • 🌱 PhaseSpan has no finish/cancel (request_timing.rs:260)

Not attached to a diff line:

  • ⛏ configuration.md now contradicts the corrected middleware guide: docs/guide/configuration.md:49 still says "Manifest-driven middleware are applied in order before routes". This PR corrected the same claim in middleware.md:61-62. Suggest reusing that sentence: "Routes are matched first. Middleware then run in registration order for a matched route; unmatched 404/405 requests bypass the middleware chain."

  • 🌱 The middleware fits only two of Trusted Server's four adapters: at 2791ceb, only Cloudflare and Spin register it.

    • Fastly creates its collector before the app is built, so it can time AppBuild (main.rs:128-131).
    • Axum reimplements insert-if-absent plus the /health exclusion as a tower service outside RouterService (timing.rs:78-99). That also covers 404/405 and gives it a final point to render headers.

    This is the gap behind the earlier 🌱 about reaching the middleware-created handle after the router returns. An EdgeZero Service wrapper around RouterService would cover unmatched routes and give every adapter one place to finish timing.

CI Status

  • fmt: PASS
  • clippy (--all-features -D warnings): PASS
  • tests (--workspace --all-targets): PASS
  • doctests (--workspace --doc): PASS (1 compile-fail test, 13 ignored)
  • feature check (fastly cloudflare spin): PASS
  • Spin wasm32-wasip2 check: PASS
  • GitHub: 21/21 checks green

Comment thread crates/edgezero-core/src/middleware.rs
Comment thread docs/guide/middleware.md Outdated
Comment thread crates/edgezero-core/src/request_timing.rs
Comment thread crates/edgezero-core/src/request_timing.rs
Comment thread crates/edgezero-core/tests/support/request_timing.rs Outdated
Comment thread docs/guide/middleware.md Outdated
Comment thread docs/guide/middleware.md
Comment thread crates/edgezero-core/src/middleware.rs Outdated
Comment thread crates/edgezero-core/src/request_timing.rs Outdated
Comment thread crates/edgezero-core/src/request_timing.rs

@prk-Jr prk-Jr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😃 Reviewed all 11 changed files at 9c03cc5, including focused collector/API and middleware/adapter reviews. No actionable defects found.

The implementation preserves existing collectors, keeps phase and payload updates under one mutex, and follows the documented contention, cancellation, and explicit lifecycle contracts.

Local verification passed:

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace --all-targets
  • cargo test -p edgezero-core: 484 unit tests, two integration tests, and the compile-fail doctest passed; 13 existing doctests ignored
  • cargo check --workspace --all-targets --features "fastly cloudflare spin"
  • cargo check -p edgezero-adapter-spin --target wasm32-wasip2 --features spin
  • Fastly WASM contract suite under Viceroy: all seven tests passed
  • Docs lint and formatting

Initial sandbox socket-binding and certificate-store failures cleared on rerun outside the sandbox. All 22 GitHub checks were green at the reviewed commit. Cloudflare/Spin runtime suites and Trusted Server joint validation were not rerun as part of this review.

Comment thread crates/edgezero-core/tests/request_timing_consumer.rs

This branch has not been deployed

No deployments
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