Skip to content

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

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

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

Conversation

@ChristianPavilonis

@ChristianPavilonis ChristianPavilonis commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Move generic request-timing collection into EdgeZero with one portable origin, bounded phase slots, lifecycle marks and application-owned payload.
  • Add opt-in, insert-if-absent attachment middleware. Applications retain rendering, header exposure and privacy policy.
  • Coordinate adoption with Trusted Server #1261, which supersedes #1121. Renewed joint validation against the current candidate is still pending.

Changes

Crate / file Change
crates/edgezero-core/src/request_timing.rs, src/lib.rs Generic RequestTimings<N, D>, immutable lock-free origin, snapshots, compound updates, saturating accumulation, drop-time spans and explicit lifecycle marks
crates/edgezero-core/src/middleware.rs Const-configurable middleware that preserves an existing concrete handle, otherwise installs request-local state unless the exact path is excluded
Core tests and shared adapter fixture Cover typed facade integration, extension identity/origin, contention, invalid slots, poison recovery, cancellation, clock advancement and attachment
crates/edgezero-core/tests/request_timing_boundary.rs Three regression tests for the guide's outer middleware pattern, including success/error results, preinstalled handles, exclusions, unmatched routes and streaming limits
Cloudflare/Fastly/Spin contract tests Run shared timing coverage in the adapter runtime harnesses
crates/edgezero-macros/tests/app_macro.rs Exercise named-const middleware registration from the manifest
.github/workflows/test.yml, scripts/run_tests.sh, .claude/commands/check-ci.md, CLAUDE.md Run/document the core doctest gate in CI and local checks
Middleware/configuration guides and design/plan Document API contracts, adapter-specific boundaries, deferred hooks and coordinated release checkpoints

elapsed() is infallible and does not take the state mutex. TimingError and TimingSnapshot are non-exhaustive. Lifecycle methods are mark_headers_ready(), mark_request_complete() and set_response_bytes().

Callbacks run synchronously under one nonblocking mutex. Panics propagate; poison recovery is best-effort, without rollback or payload validation. Attachment does not finalize requests, measure streamed-body completion or cover unmatched routes. Trusted Server retains its phase enum, domain facts, wire names and Server-Timing rendering.

Fastly offers request-extension hooks; Axum can wrap the router in a tower service. Cloudflare and Spin currently require application middleware registered before the attachment middleware. The guide shows that pattern and its error-rendering, exclusion, 404/405 and streaming limits. Request-extension hooks for those adapters remain deferred.

Verification

Current pushed candidate: 018529795767effa1bf0e5761367f4559aa47b30.

  • All 28 reported GitHub checks passed at this candidate, including workspace tests, formatting, static checks, adapter WASM tests and clippy, docs formatting and demo/smoke jobs.
  • Core compile-fail doctest included in the CI test workflow and local gate commands.
  • Review follow-up in 4dd6bf82: boundary example and three regression tests, synchronized doctest gates, refreshed spec/plan evidence and removal of session-only process text.
  • Clippy compatibility follow-up in 01852979 for Rust 1.95 and 1.98.1, without changing behavior.
  • Deployed-provider clock smoke testing. Local browser/Viceroy/Wasmtime tests are not evidence of deployed Cloudflare clock semantics.
  • Renewed Trusted Server integration and joint validation against this candidate.

Historical local and post-merge verification is recorded separately in the implementation plan. The original joint verification at EdgeZero 9c03cc59 / Trusted Server 2791ceb does not validate the revised API or current candidate. Cloudflare CI uses headless Firefox, not the Chromium harness used for earlier local runs.

Coordinated delivery / merge gate

Trusted Server #1261 still pins all six EdgeZero dependencies to ab444946fcacb1d44c020c6079abb6ba29231402. Its current head, 686826eaf2bc6b35a4f52f0731ac8ffee9bc78c5, has failing CI. That pin predates the merged Fastly lifecycle API needed by the consumer.

Before merging EdgeZero, update the consumer facade and all six pins to one pushed candidate containing the merged lifecycle API, confirm one resolved EdgeZero core identity and rerun joint validation. This remains an unmet merge gate, not completed verification.

Trusted Server must then replace temporary revision pins with an approved EdgeZero release tag, regenerate and inspect its lockfile, and pass final tag-backed validation before merging. No merge or release is performed by this review follow-up.

Related work

@aram356 aram356 left a comment

Copy link
Copy Markdown
Collaborator

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
Collaborator

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
Collaborator

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
Keep core doctests in the workspace host suite alongside main's CLI test matrix.

@aram356 aram356 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

PR Review

Summary

Re-review at 878be472, which is ab444946 plus the merge of main. Both 🔧 items from the first review are fixed correctly. t0 now sits outside the mutex, so elapsed() is lock-free, and a cross-thread test covers it. TimingError and TimingSnapshot are #[non_exhaustive]. I checked every other claimed fix against the code and each one holds. The deferrals recorded in the spec's "Deferred follow-ups" are reasonable.

I'm requesting changes for the items below. The main ones are the Cloudflare/Spin gap in the guide's advice on where to measure, and the release gate on Trusted Server #1261.

Findings

Inline:

  • 🤔 Cloudflare and Spin have no adapter hook for preinstalling a handle (docs/guide/middleware.md:288)
  • ⛏ The doctest gate is missing from the local gate scripts and CLAUDE.md (.github/workflows/test.yml:97)
  • ⛏ The spec and plan have stale consumer references, the wrong browser name and old evidence counts (spec:8)
  • ⛏ Some process and authorization text remains (spec:478)

Not attached to a diff line:

  • ⛏ The PR description is stale: it still says Trusted Server migration and joint verification are "complete against this exact candidate". The test plan's counts (484 unit tests; Cloudflare 8, Fastly 7, Spin 13) and the "22 checks … identical to 9c03cc59" note describe the original commit, and the public API has been renamed since then. The Changes table also leaves out .github/workflows/test.yml and the edgezero-macros fixture. Suggest refreshing it and marking joint validation as pending, as the spec's "Remaining release checkpoints" already does.
  • 📝 The release gate isn't met yet: Trusted Server #1261 pins all six crates to ab444946. Its Fastly adapter uses edgezero_adapter_fastly::lifecycle::{Sandbox, run_custom}, which comes from #379 and exists on this branch only after the merge. As a result its CI fails to compile (E0433 at trusted-server-adapter-fastly/src/main.rs:125). The spec's checkpoints put renewed joint validation before the EdgeZero merge, and that validation hasn't happened yet. #1261 needs to re-pin to 878be472 or later and pass.

CI Status

  • fmt: PASS
  • clippy (--all-features -D warnings): PASS
  • tests (--workspace --all-targets): PASS
  • core doctests (-p edgezero-core --doc): PASS (1 compile-fail test, 13 ignored)
  • feature check (fastly cloudflare spin): PASS
  • Spin wasm32-wasip2 check: PASS
  • GitHub: 28/28 checks green, including Cloudflare (10 tests, headless Firefox), Fastly/Viceroy (9) and Spin/Wasmtime (14)

Comment thread docs/guide/middleware.md Outdated
Comment thread .github/workflows/test.yml
Comment thread docs/superpowers/specs/2026-09-28-request-timing-design.md
Comment thread docs/superpowers/specs/2026-09-28-request-timing-design.md Outdated
…ression tests, synchronize local doctest gates, refresh verification records, and remove session-only narrative. Production behavior unchanged.
@ChristianPavilonis

Copy link
Copy Markdown
Collaborator Author

@aram356 ready for another pass on the EdgeZero changes at 018529795767effa1bf0e5761367f4559aa47b30.

Replied to and resolved the four inline threads. 4dd6bf82 adds the adapter-specific boundary guidance and your outer-middleware pattern with three regression tests, synchronizes the local doctest gates, refreshes the spec/plan records and removes the process narrative. 01852979 fixes Clippy compatibility without changing behavior. All 28 reported GitHub checks are green at the current head.

I also refreshed the PR description, including the missing workflow/macro fixture changes and the verification status. It no longer claims that the original joint validation covers this candidate.

Your non-inline release-gate finding remains open. Trusted Server #1261 still pins all six crates to ab444946, and its current CI has failures. It needs the facade/pins updated to the merged candidate and renewed joint validation before EdgeZero merges. This re-review request does not mark that gate complete. Deployed-provider clock smoke testing and final tag-backed consumer validation also remain pending.

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