Skip to content

fix(ppr): prioritize document shell captures - #801

Merged
ivogt merged 2 commits into
mainfrom
fix/ppr-document-capture-priority
Jul 18, 2026
Merged

ivogt merged 2 commits into
mainfrom
fix/ppr-document-capture-priority

Conversation

@ivogt

@ivogt ivogt commented Jul 18, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A consumer using PPR with production viewport prefetch can enqueue several slow navigation-only shell captures before a document MISS schedules its document-shell capture. The per-isolate capture queue previously used strict FIFO ordering, so speculative navigation work could consume the full 15-second queue budget first. The requested page remained on repeated x-rango-shell: MISS responses and its background trace ended as skip-queue-timeout, even though the route was eligible for a reusable document shell.

Before And After

The consumer configuration does not change:

export const urlpatterns = urls(({ path, loader, loading }) => [
  path(
    "/products",
    ProductsPage,
    { name: "products", ppr: { ttl: 300, swr: 120 } },
    () => [loader(productsLoader), loading(<ProductsFallback />)],
  ),
]);

export const router = createRouter({
  document: Document,
  defaultPrefetch: "viewport",
});

Before: if viewport prefetch scheduled several slow navigation snapshots first, the /products document capture waited behind all of them and could expire without storing a shell.

After: the active capture still completes, then the queued /products document capture runs before queued navigation-only captures. The route can transition from MISS to HIT within the existing queue budget without any consumer migration.

End-To-End Example

A product index renders a cached document shell around a live loader boundary. When the browser discovers several product links, viewport prefetch starts navigation-only PPR captures. A simultaneous hard request for /products serves its normal live MISS response immediately and schedules a document capture in the same isolate. Once the currently running capture finishes, the document capture overtakes the queued speculative snapshots, stores the shell, and the next document request returns x-rango-shell: HIT; the live products loader still resumes through the captured hole.

Semantics

Case Behavior
Capture already running Completes normally; priority never preempts active work
Queued document vs. queued navigation capture Document runs first
Captures at the same priority Preserve FIFO order
Navigation-only queue without waiting documents Preserves prior FIFO behavior
Queue wait reaches 15 seconds Capture is dropped as skip-queue-timeout; no failure backoff is added
Per-isolate admission reaches 32 captures New work is rejected as skip-capacity
Foreground response Remains independent of background capture completion
App does not use PPR Unchanged

Code Changes

  • Replace the promise-tail FIFO implementation in src/rsc/capture-queue.ts with explicit document and navigation queues while retaining one active capture per isolate, admission accounting, per-item wait timers, rejection isolation, and the 60-second wedge cap.
  • Grant each queued caller a start signal so its capture resumes in its own workerd request context. Queue handoff now completes before the caller waitUntil promise settles; resolving first allowed workerd to retire the context while captureRunning remained locked.
  • Classify captures in src/rsc/shell-capture.ts from the existing descriptor.navigationOnly signal. This keeps priority at the orchestration boundary where document and navigation captures are already distinguished.
  • Add unit regressions that pin non-preemption, document-first dequeue order, and handoff-before-caller-resolution.
  • Add pressure regressions to the router test app and the Cloudflare/workerd consumer app in both dev and production. The workerd case also verifies the document background span reaches stored with queue wait below the 15-second budget.
  • Update the PPR design, execution model, feature map, telemetry design, and PPR diagnostics skill to document the priority and request-context contracts.

Regression proof: strict FIFO makes the priority unit test receive navigation before document, and the pressure scenario leaves the document capture behind speculative captures until the queue timeout path. The first CI run also exposed the workerd handoff bug: two later PPR document tests stayed MISS through all retries. The same 46-test Cloudflare PPR sequence reproduced both failures locally before the handoff fix and passed 46/46 afterward.

Test Plan

  • pnpm run typecheck
  • pnpm run test:unit:all (363 router files; 5,568 passed; 6 todo, plus all consumer unit and RSC suites)
  • pnpm run lint
  • pnpm run format
  • Unit: enqueueSerializedCapture > prioritizes a document capture over queued navigation captures
  • Unit: enqueueSerializedCapture > hands off before the completed capture caller can enqueue more work
  • Cloudflare PPR dev sequence: e2e/ppr-shell.test.ts (46/46 passed)
  • Router e2e dev: shell-capture-timeout (dev) > a document capture overtakes queued navigation-only captures
  • Router e2e production: shell-capture-timeout (production) > a document capture overtakes queued navigation-only captures
  • Cloudflare/workerd e2e dev: ppr capture timeout (dev) > a document capture overtakes queued navigation-only captures
  • Cloudflare/workerd e2e production: ppr capture timeout (production) > a document capture overtakes queued navigation-only captures

Notes

  • This changes only scheduling order and request-context-safe handoff among queued capture jobs. It does not split document and navigation capture architectures or storage formats.
  • Existing timeout, capacity, retry, backoff, tracing, and best-effort capture behavior remains in place.
  • No public API or generated route files change.

@pkg-pr-new

pkg-pr-new Bot commented Jul 18, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@rangojs/router@801

commit: 2e9e679

@ivogt
ivogt merged commit 5017487 into main Jul 18, 2026
36 checks passed
@ivogt
ivogt deleted the fix/ppr-document-capture-priority branch July 18, 2026 12:35
ivogt added a commit that referenced this pull request Jul 18, 2026
…-store guard (#803)

Three operability gaps from the #801 incident: a queue-dropped capture
reported only its wait (now: priority class + backlog at enqueue on the
skip event and the rango.background span); bake cost was invisible (now:
bakeWaitMs names the slowest gate-holding source on stored attempts, with
a once-per-key dev warning past 2s carrying the nest/cache()/loading()
remedy ladder); and a store whose shell family can only no-op (KV-less
CFCacheStore, declared via SegmentCacheStore.shellFamilyInert) still
burned a full background render per MISS (now: skip-inert-store at the
scheduling gate).
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