Skip to content

fix(core): keep Hyperdrive pool alive through layout prefetch - #2409

Merged
ascorbic merged 2 commits into
mainfrom
fix/issue-2133
Aug 10, 2026
Merged

ascorbic merged 2 commits into
mainfrom
fix/issue-2133

Conversation

@ascorbic

Copy link
Copy Markdown
Collaborator

What does this PR do?

Keeps the anonymous request-scoped database alive until both the response body and the concurrent layout prefetch have settled. This prevents Hyperdrive pool shutdown from racing queries that still own pool clients, which left successful Worker invocations pending until waitUntil() cancellation.

Closes #2133

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

The i18n and Discussion checklist items are not applicable: this changes no admin UI strings and fixes an existing bug.

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: GPT-5 Codex

Screenshots / test output

Non-visual change. Verified with:

  • pnpm lint
  • pnpm typecheck
  • pnpm --dir packages/core exec vitest run tests/unit/middleware/scoped-db.test.ts (18 passed)
  • Focused middleware and Hyperdrive tests (28 passed)
  • pnpm build
  • pnpm format

Copilot AI lite review requested due to automatic review settings August 10, 2026 12:57
@changeset-bot

changeset-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 84fe141

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added review/needs-review No maintainer or bot review yet area/core size/M labels Aug 10, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Aug 10, 2026
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 84fe141 Aug 10 2026, 01:00 PM

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 84fe141 Aug 10 2026, 01:00 PM

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do 84fe141 Aug 10 2026, 01:00 PM

@emdashbot emdashbot Bot 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.

This PR correctly targets the root cause of #2133: on connection-backed adapters, the request-scoped pool was being closed as soon as the response stream finished, while the concurrently-running layout prefetch still held pool clients. The fix introduces a small lifecycle wrapper that keeps the real adapter close() deferred under after()/waitUntil until both the response stream and the prefetch promise have settled. The approach is idiomatic for EmDash's middleware lifecycle and reuses the existing after() utility, so no new platform concepts or query patterns are introduced.

I read the full changed files and traced the call flow through finishScoped, wrapResponseForScopedClose, and prefetchLayoutData. The helper correctly handles adapters with and without a close, preserves the commit-before-response / close-after-stream invariant, and the error path still resolves the response-settlement signal so the pool isn't torn down underneath in-flight prefetch work. The added unit tests cover bodyless responses, streaming responses, and the no-close adapter path. No new queries are added to the logged-out hot path, no admin UI strings are touched, and a changeset is present. I found no issues to fix.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 10, 2026

@emdashbot emdashbot Bot 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.

This is the right fix for the described race.

The old code deferred prefetchLayoutData() inside after(() => ...), so the prefetch still owned pool clients while the response stream was flushing. For Hyperdrive-backed requests, finishScoped() closed the request-scoped pool at stream-end, which could race with the still-running prefetch and leave the Worker invocation stuck until waitUntil cancellation. The new helper makes the prefetch start immediately, then hands a single after() task that waits for both the response stream to finish and the prefetch to settle before calling the real adapter close(). That matches the existing scoped-db lifecycle model and doesn’t change query counts for the logged-out hot path.

I read the diff, the full changed files, after.ts, prefetch.ts, and the AGENTS.md conventions. The changeset is present, user-facing, and targeted. The tests add focused coverage for the new helper. No new admin UI strings, SQL, or locale-filtered queries are involved. The code is clean, and I have no blocking or suggested changes.

@github-actions github-actions Bot added cla: signed review/approved Approved; no new commits since and removed review/needs-review No maintainer or bot review yet labels Aug 10, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2409

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2409

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2409

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2409

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2409

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2409

emdash

npm i https://pkg.pr.new/emdash@2409

create-emdash

npm i https://pkg.pr.new/create-emdash@2409

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2409

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2409

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2409

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2409

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2409

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2409

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2409

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2409

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2409

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2409

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2409

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2409

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2409

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2409

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2409

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2409

commit: 84fe141

Copilot AI 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.

Pull request overview

Fixes a Cloudflare Workers + Hyperdrive lifecycle race where request-scoped pg.Pool teardown could start while anonymous layout prefetch was still using pool clients (especially visible on bodyless responses like redirects), leading to waitUntil() cancellation after ~30s.

Changes:

  • Add deferScopedCloseUntilSettled() to defer adapter teardown until both the response has settled and background prefetch work has completed.
  • Wire anonymous HTML layout prefetch to keep the request-scoped DB lifecycle alive through both response completion and prefetch completion.
  • Add unit tests covering bodyless and streaming responses for the new lifecycle deferral behavior, plus a changeset for a patch release.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
packages/core/src/astro/middleware/scoped-db.ts Introduces lifecycle deferral helper and refactors lifecycle typing used by finishScoped.
packages/core/src/astro/middleware.ts Uses the new deferral helper when anonymous HTML layout prefetch is enabled.
packages/core/tests/unit/middleware/scoped-db.test.ts Adds unit tests verifying teardown deferral behavior for bodyless/streaming responses.
.changeset/fix-hyperdrive-prefetch-teardown.md Adds a patch changeset describing the Hyperdrive teardown fix.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +47 to +54
const close = scoped.close;
defer(async () => {
await Promise.allSettled([pending, responseSettled]);
close();
});

return { commit: scoped.commit, close: settleResponse };
}
@ascorbic
ascorbic merged commit 0c3af01 into main Aug 10, 2026
53 checks passed
@ascorbic
ascorbic deleted the fix/issue-2133 branch August 10, 2026 14:34
@emdashbot emdashbot Bot mentioned this pull request Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hyperdrive request-scoped pool teardown remains pending and triggers waitUntil() cancellation

2 participants