[PR #391] [MERGED] fix: register background KV ops and ISR regen with ctx.waitUntil on Workers #535

Closed
opened 2026-05-06 13:08:37 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/391
Author: @james-elicx
Created: 3/9/2026
Status: Merged
Merged: 3/9/2026
Merged by: @james-elicx

Base: mainHead: fix/workers-waituntil-background-ops


📝 Commits (2)

  • d5f67cd fix: register background KV ops and ISR regen with ctx.waitUntil on Workers
  • 5700a50 regen snaps

📊 Changes

7 files changed (+230 additions, -17 deletions)

View changed files

📝 packages/vinext/src/cloudflare/kv-cache-handler.ts (+53 -8)
📝 packages/vinext/src/deploy.ts (+2 -2)
📝 packages/vinext/src/entries/pages-server-entry.ts (+6 -3)
📝 packages/vinext/src/server/isr-cache.ts (+23 -1)
📝 tests/__snapshots__/entry-templates.test.ts.snap (+6 -3)
📝 tests/isr-cache.test.ts (+27 -0)
📝 tests/kv-cache-handler.test.ts (+113 -0)

📄 Description

Problem

On Cloudflare Workers, any async work started after a `Response` is returned is killed immediately. Three places in vinext were silently losing work as a result:

  1. ISR background regeneration dropped — `triggerBackgroundRegeneration` starts a revalidation promise but never calls `ctx.waitUntil()`, so every stale-while-revalidate revalidation on Workers is silently discarded. Pages appear perpetually stale.

  2. Corrupt-entry deletes blocking the response — `KVCacheHandler.get()` used `await this.kv.delete(kvKey)` for cleanup of corrupt/invalid/tag-invalidated entries. This is pure background cleanup that was unnecessarily holding up the response.

  3. Cache writes on the critical path — `KVCacheHandler.set()` awaited the KV write before returning. The client already has the rendered content at that point; the write can safely happen after the response is sent.

Solution

  • `server/isr-cache.ts`: Added exported `ExecutionContext` interface; updated `triggerBackgroundRegeneration(key, renderFn, ctx?)` to call `ctx.waitUntil(promise)` when `ctx` is present.
  • `entries/pages-server-entry.ts`: Updated the inlined copy of `triggerBackgroundRegeneration` (generated code) and `renderPage` to thread `ctx` through; updated the stale-hit callsite to pass `ctx`.
  • `deploy.ts`: Both `renderPage(request, resolvedUrl, null)` calls updated to pass `ctx` as fourth argument.
  • `cloudflare/kv-cache-handler.ts`:
    • Added private `_deleteInBackground(kvKey)` and `_putInBackground(kvKey, value, opts)` helpers: use `ctx.waitUntil()` when `ctx` is present, fire-and-forget on Node.js.
    • All three `kv.delete` callsites in `get()` (corrupt JSON, invalid shape, tag-invalidation) converted to `_deleteInBackground`.
    • `set()` KV write converted to `_putInBackground`.

All changes are backward-compatible — `ctx` is optional everywhere; Node.js dev/prod behaviour is unchanged.

Tests

  • `tests/kv-cache-handler.test.ts`: New `ctx.waitUntil registration` suite (6 tests) verifies that `ctx.waitUntil` is called for corrupt-entry deletes, invalid-shape deletes, tag-invalidation deletes, and `set()` writes; also verifies the fire-and-forget fallback when no `ctx` is provided.
  • `tests/isr-cache.test.ts`: Two new tests verify that `ctx.waitUntil` is called with the regen promise when `ctx` is provided, and that the function works correctly without `ctx`.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/391 **Author:** [@james-elicx](https://github.com/james-elicx) **Created:** 3/9/2026 **Status:** ✅ Merged **Merged:** 3/9/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `fix/workers-waituntil-background-ops` --- ### 📝 Commits (2) - [`d5f67cd`](https://github.com/cloudflare/vinext/commit/d5f67cd6a6e1b4c6bda3c67330d28142a13a1cd2) fix: register background KV ops and ISR regen with ctx.waitUntil on Workers - [`5700a50`](https://github.com/cloudflare/vinext/commit/5700a50a8a56c986d2a26e4b748e35e0fecf99e4) regen snaps ### 📊 Changes **7 files changed** (+230 additions, -17 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/cloudflare/kv-cache-handler.ts` (+53 -8) 📝 `packages/vinext/src/deploy.ts` (+2 -2) 📝 `packages/vinext/src/entries/pages-server-entry.ts` (+6 -3) 📝 `packages/vinext/src/server/isr-cache.ts` (+23 -1) 📝 `tests/__snapshots__/entry-templates.test.ts.snap` (+6 -3) 📝 `tests/isr-cache.test.ts` (+27 -0) 📝 `tests/kv-cache-handler.test.ts` (+113 -0) </details> ### 📄 Description ## Problem On Cloudflare Workers, any async work started after a \`Response\` is returned is killed immediately. Three places in vinext were silently losing work as a result: 1. **ISR background regeneration dropped** — \`triggerBackgroundRegeneration\` starts a revalidation promise but never calls \`ctx.waitUntil()\`, so every stale-while-revalidate revalidation on Workers is silently discarded. Pages appear perpetually stale. 2. **Corrupt-entry deletes blocking the response** — \`KVCacheHandler.get()\` used \`await this.kv.delete(kvKey)\` for cleanup of corrupt/invalid/tag-invalidated entries. This is pure background cleanup that was unnecessarily holding up the response. 3. **Cache writes on the critical path** — \`KVCacheHandler.set()\` awaited the KV write before returning. The client already has the rendered content at that point; the write can safely happen after the response is sent. ## Solution - **\`server/isr-cache.ts\`**: Added exported \`ExecutionContext\` interface; updated \`triggerBackgroundRegeneration(key, renderFn, ctx?)\` to call \`ctx.waitUntil(promise)\` when \`ctx\` is present. - **\`entries/pages-server-entry.ts\`**: Updated the inlined copy of \`triggerBackgroundRegeneration\` (generated code) and \`renderPage\` to thread \`ctx\` through; updated the stale-hit callsite to pass \`ctx\`. - **\`deploy.ts\`**: Both \`renderPage(request, resolvedUrl, null)\` calls updated to pass \`ctx\` as fourth argument. - **\`cloudflare/kv-cache-handler.ts\`**: - Added private \`_deleteInBackground(kvKey)\` and \`_putInBackground(kvKey, value, opts)\` helpers: use \`ctx.waitUntil()\` when \`ctx\` is present, fire-and-forget on Node.js. - All three \`kv.delete\` callsites in \`get()\` (corrupt JSON, invalid shape, tag-invalidation) converted to \`_deleteInBackground\`. - \`set()\` KV write converted to \`_putInBackground\`. All changes are backward-compatible — \`ctx\` is optional everywhere; Node.js dev/prod behaviour is unchanged. ## Tests - **\`tests/kv-cache-handler.test.ts\`**: New \`ctx.waitUntil registration\` suite (6 tests) verifies that \`ctx.waitUntil\` is called for corrupt-entry deletes, invalid-shape deletes, tag-invalidation deletes, and \`set()\` writes; also verifies the fire-and-forget fallback when no \`ctx\` is provided. - **\`tests/isr-cache.test.ts\`**: Two new tests verify that \`ctx.waitUntil\` is called with the regen promise when \`ctx\` is provided, and that the function works correctly without \`ctx\`. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:08:37 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#535
No description provided.