Skip to content

PPR capture pipeline hardening: narrow the write barrier, unify bounded drains, tame the backoff-vs-CI flake #652

Closed

Description

@ivogt

Problems (all in shell-capture territory)

  1. Barrier over-awaits: the pre-render write barrier (feat(router): PPR capture data snapshot — shell/payload parity on a HIT #647) drains ALL tracked waitUntil tasks before the capture's match — a slow consumer task (analytics flush) delays every capture up to the 1.5s deadline even though only cache writes matter for the no-clobber contract.
  2. Two copies of the same drain: settleWrites (shell-snapshot.ts) and settleTrackedBackgroundTasks (shell-capture.ts) are both iterative deadline-bounded drains — unify into one shared helper.
  3. Backoff vs CI warm windows: a cold CI dev worker makes the first capture attempt come back no-shell; the exponential backoff (1s→60s, from fix(router): PPR capture retry-in-place, quiet expected aborts, DSL middleware() attachment #644) then holds the key in MISS longer than e2e warm windows (20s), producing hard CI failures that never reproduce locally (seen on fix(router): PPR resume theme fidelity — replay the capture's initialTheme, re-sync from cookie post-mount #646's cloudflare run: "HIT streams the cached shell" MISS for 20s ×3 retries — backoff persists in the dev server across test retries).

Direction

  1. Tag cache-write promises at their scheduling sites (cacheRoute / setItem / putResponse waitUntil wrappers) and have the barrier await exactly those; untagged tasks stay fire-and-forget.
  2. Extract drainBounded(queue, deadline) used by both call sites.
  3. Options for the flake (pick during implementation): cap dev-mode backoff at ~2s; and/or reset the backoff for a key on a successful axis-1 render of the same route; and/or a test-only reset hook the e2e warm helper calls. Keep production backoff behavior unchanged.

Test plan

Unit: only tagged writes are awaited (slow untagged task does not delay capture); shared drain covers nested scheduling; dev backoff cap. E2e: existing suites are the regression net (mini shell-manifest pins the no-clobber contract); re-run the cloudflare ppr-shell dev suite under constrained CPU if reproducible.

Activity

  1. ivogt commented on Jul 4, 2026

    @ivogt
    CollaboratorAuthor

    Two more CI signatures from PR #657's cloudflare-basic run (2026-07-04, job 85120452654) to fold into this work:

    1. Retry-cascade into the hmr project's 600s suite-wait: the two ppr-shell dev flakes (warm-window MISS class, this issue's core) passed on retry but their 20s+ retries pushed the job long enough that the hmr project's dev-server wait hit "Timed out waiting 600s for the test suite to run" — the hmr warmup then fails at 0ms and the whole HMR suite skips. So one backoff flake can cascade into an unrelated project's hard failure. Fixing the warm-window interaction removes the trigger, but the 600s suite-wait budget on the hmr project is also worth a look (it pays for ALL prior projects' overruns).

    2. Duplicate-element artifact during the soft-nav flake (may be a SEPARATE real bug — tracked here until triaged): renders and hydrates on the live path, and soft-nav to the route works failed with a strict-mode violation — [data-testid="ppr-price"] resolved to TWO elements, both data-seq="3", after goBack to the ppr route on the LIVE (axis-1, MISS) path. Same test flaked on fix(router): PPR resume theme fidelity — replay the capture's initialTheme, re-sync from cookie post-mount #646's run too. A duplicated DOM node with identical seq suggests a soft-nav re-render artifact rather than timing; if it reproduces under investigation, split it into its own issue.

  2. ivogt commented on Aug 30, 2026

    @ivogt
    CollaboratorAuthor

    Item 3 (dev backoff vs CI warm window) shipped: refusedCaptureCeilingMs is ~2s in dev and 60s in production (src/rsc/shell-capture.ts; documented in docs/design/ppr-shell-resume.md as "#652 item 3").

    Items 1–2 (narrow the write barrier to tagged cache writes; unify settleWrites / settleTrackedBackgroundTasks) are internal DRY, not a user-facing defect. Closing rather than keeping a tracker for that cleanup. Reopen if a slow analytics waitUntil is actually delaying captures in production.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions