Skip to content

Give each request its own execution stack build - #1802

Merged
RhysSullivan merged 1 commit into
mainfrom
test/dbprovider-request-scoping
Aug 28, 2026
Merged

Give each request its own execution stack build#1802
RhysSullivan merged 1 commit into
mainfrom
test/dbprovider-request-scoping

Conversation

@RhysSullivan

Copy link
Copy Markdown
Collaborator

Problem

Concurrent API requests could share one database provider build, so a request could use — and lose — a connection owned by another request.

Every request gets its own database connection: opened when the request fiber's scope opens, closed when it closes. But makeExecutionStackMiddleware captures the boot fiber's context once at layer-construction time and re-applies it to every request with Effect.provideContext. A captured context also carries Effect's CurrentMemoMap, and the merge lets the boot map overwrite the fresh per-request map the host installed.

The per-request Effect.provide(stackLayer) then memoized its build in that boot map, which every in-flight request in the isolate shares. Sequential requests still rebuilt — the memo entry is refcounted by observer and drops to zero when the request scope closes — so only overlapping requests were affected. Three concurrent requests produced one stack build, leaving two of them operating a database handle owned by a third.

Two consequences follow. On Workers, the borrowing request performs I/O on a socket belonging to a different request. And when the owning request finishes, its scope finalizer ends the connection, so the borrower's next query — typically the read after a slow outbound call — fails on a connection that is already gone.

Fix

The stack is built with a request-local memoization scope (Effect.provide(stackLayer, { local: true })) at both branches, so overlapping requests each build their own stack over their own connection. The captured context keeps doing the one job it exists for: carrying the boot-scoped services the per-request body needs.

{ local: true } rather than stripping the memo map out of the captured context: it states the invariant at the call site that depends on it, it is the documented meaning of the option, and it does not depend on which memoization map happens to be ambient — so it holds for both cloud (which combines a request-scoped DB layer) and self-host (which does not).

Two other per-request provider builds run under a captured context for the same stated reason — closing over the per-request socket — and had the same defect: the account provider and the admin-users provider. Both are fixed the same way. The fixed-executor middleware captures a context too, but provides only pre-built values and never builds a layer per request, so it is unaffected.

Testing

apps/cloud/src/api.request-scope.node.test.ts gains three regressions that stand the real middleware up over authenticated requests and per-request in-memory databases, so the stack layer actually builds:

  • three concurrent requests produce three stack builds
  • every request's stack is built over its own database handle, never another's
  • read, slow await, read again: the request that is still running holds an open connection of its own while the faster overlapping request has already ended and released its own

All three fail against the unfixed code — one build for three requests, a single handle backing every request, and an inverted release order where the slow request's own connection is released while it keeps working on the fast request's. All pass after the change, over five consecutive runs of the file.

bun run typecheck is green. Cloud e2e storage-error-report-shape and connections-credentials pass unchanged.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 28, 2026

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 Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing a2ed2ab Commit Preview URL

Branch Preview URL
Aug 28 2026, 06:09 AM

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Torn down — the PR is closed.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 28, 2026

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
executor-cloud a2ed2ab Aug 28 2026, 06:12 AM

@pkg-pr-new

pkg-pr-new Bot commented Aug 28, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1802

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1802

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1802

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1802

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1802

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1802

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1802

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1802

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1802

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1802

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1802

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1802

executor

npm i https://pkg.pr.new/executor@1802

commit: acee781

Concurrent requests shared one database provider build, so a request could
use a connection owned by another request and lose it when that request
finished.
@RhysSullivan
RhysSullivan force-pushed the test/dbprovider-request-scoping branch from acee781 to a2ed2ab Compare August 28, 2026 06:08
@RhysSullivan
RhysSullivan marked this pull request as ready for review August 28, 2026 06:12
@RhysSullivan
RhysSullivan merged commit 2afb6e8 into main Aug 28, 2026
43 checks passed
@RhysSullivan RhysSullivan mentioned this pull request Aug 28, 2026
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