Skip to content

Settle pending SQLite WASM requests before replacing workers - #6994

Merged
tim-smart merged 3 commits into
mainfrom
audit/repro-split-sql-adapters-sw-1
Aug 4, 2026
Merged

Settle pending SQLite WASM requests before replacing workers#6994
tim-smart merged 3 commits into
mainfrom
audit/repro-split-sql-adapters-sw-1

Conversation

@fubhy

@fubhy fubhy commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

Replacing a failed worker leaves existing requests suspended forever and allows replacement request IDs to overwrite abandoned callbacks.

Important

This PR starts with focused failing reproduction tests. Add the implementation fix to this same branch; CI is expected to fail until that fix is included.

Worker replacement abandons in-flight requests

Module: sqlite-wasm/SqliteClient
Audit ID: sql-adapters-sw-1
Severity / confidence: high / high

What happens

Replacing a failed worker leaves existing requests suspended forever and allows replacement request IDs to overwrite abandoned callbacks.

Why it happens

Replacement neither settles nor removes callbacks in the shared pending map, while each new connection restarts its request ID counter at zero.

Expected behavior

A worker-backed client restart must settle all operations owned by the replaced scoped connection.

Relevant implementation

These links and excerpts are pinned to audit base c9b56ab507f224426ee8388dc450da447ec4715f.

View problematic code at packages/sql/sqlite-wasm/src/SqliteClient.ts:309
    const pending = new Map<number, (effect: Exit.Exit<any, SqlError>) => void>()

View exact lines on GitHub

View problematic code at packages/sql/sqlite-wasm/src/SqliteClient.ts:348-353
      function onError() {
        Effect.runFork(ScopedRef.set(connectionRef, makeConnection))
      }
      if ("onerror" in worker) {
        worker.addEventListener("error", onError)
      }

View exact lines on GitHub

View problematic code at packages/sql/sqlite-wasm/src/SqliteClient.ts:369-373
      const send = (id: number, message: OpfsWorkerMessage, transferables?: ReadonlyArray<any>) =>
        Effect.callback<any, SqlError>((resume) => {
          pending.set(id, resume)
          postMessage(message, transferables)
        })

View exact lines on GitHub

Reproduction

pnpm test --run packages/sql/sqlite-wasm/test/Client.test.ts

Observed failure: The in-flight query remained pending after worker replacement.

Implementation handoff

The initial reproduction tests on this branch are the regression specification for the implementation fix that should follow in this PR.

  1. Start with the pinned implementation excerpts and the Why it happens analysis above.
  2. Change the implementation so it satisfies the stated Expected behavior; do not weaken or remove the reproduction assertions.
  3. Run the focused reproduction command(s) and confirm the observed failures become passing tests:
pnpm test --run packages/sql/sqlite-wasm/test/Client.test.ts
  1. Run the affected package's existing tests, then the repository lint and type checks before requesting review.

Audit provenance

  • Audit base: c9b56ab507f224426ee8388dc450da447ec4715f
  • Reproduction base: 8f9499f562729f5f7b08d8bcc4db86b4aeff8a21
  • Findings: sql-adapters-sw-1
  • Initial patch: focused reproduction tests; implementation fix pending

Closes EFF-431

@fubhy fubhy added the audit Findings originating from the Effect runtime correctness audit label Aug 4, 2026
@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 08f0ec3

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

This PR includes changesets to release 30 packages
Name Type
@effect/sql-sqlite-wasm Patch
effect Patch
@effect/ai-anthropic Patch
@effect/ai-openai Patch
@effect/ai-openai-compat Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/openapi-generator Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-deno Patch
@effect/platform-node Patch
@effect/platform-node-shared Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/vitest 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

@effect-slopcop effect-slopcop Bot added 4.0 bug Something isn't working labels Aug 4, 2026
pullfrog[bot]
pullfrog Bot previously requested changes Aug 4, 2026

@pullfrog pullfrog 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.

Important

This PR's title and description promise an implementation fix, but the diff only adds the reproduction test. packages/sql/sqlite-wasm/src/SqliteClient.ts is unchanged, so worker replacement still leaves callbacks in the shared pending map suspended and vulnerable to id collisions. CI fails as expected. The reproduction test also needs a correction before it can serve as the regression spec.

Reviewed changes

  • Added a focused reproduction test in packages/sql/sqlite-wasm/test/Client.test.ts for pending-query settlement on worker error.
  • The implementation fix to settle or remove callbacks in packages/sql/sqlite-wasm/src/SqliteClient.ts is not present in this diff.

⚠️ Implementation fix missing

The PR body and title describe settling pending requests before replacing workers, but SqliteClient.ts was not modified. onError still only calls ScopedRef.set(connectionRef, makeConnection), leaving the shared pending map untouched. The new connection restarts currentId at 0, so replacement request ids can overwrite abandoned callbacks. The fix needs to fail or remove every callback owned by the replaced connection before the replacement proceeds.

Technical details
# Settle pending callbacks on worker error

## Affected sites
- `packages/sql/sqlite-wasm/src/SqliteClient.ts:348``onError` replaces the worker without touching `pending`
- `packages/sql/sqlite-wasm/src/SqliteClient.ts:309``pending` is shared across all connections created by `makeConnection`
- `packages/sql/sqlite-wasm/src/SqliteClient.ts:312` — each new connection resets `currentId` to `0`

## Required outcome
- When a worker errors, every callback currently in `pending` must be settled (e.g., with `Exit.fail(SqlError)`) or removed before the replacement connection starts.
- After the fix, a new connection's id `0` must not invoke a stale callback from the previous connection.

## Suggested approach
- Inside `onError`, before calling `ScopedRef.set`, copy the current entries from `pending`, clear the map, and invoke each resume with `Exit.fail(...)` describing the worker failure.
- Run the focused test from the PR body to confirm the reproduction now passes.

⚠️ Test assertion does not match expected behavior

Effect.timeoutOption preserves failures from the source effect. When the implementation correctly fails the in-flight query with SqlError, Fiber.join(fiber).pipe(Effect.timeoutOption(...)) will fail with SqlError, so Option.isSome(result) is never reached. The test should capture the fiber's Exit and assert both that it settled and that it failed.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Fix all ➔Fix 👍s ➔View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

Comment thread packages/sql/sqlite-wasm/test/Client.test.ts
# Conflicts:
#	packages/sql/sqlite-wasm/test/Client.test.ts

@pullfrog pullfrog 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.

ℹ️ No critical issues — one correctness observation inline.

Reviewed changes

  • Moved pending from the outer make scope into makeConnection so each worker/connection owns its own callback map.
  • Updated the worker error handler to fail every pending request with SqlError before replacing the connection.
  • Corrected the reproduction test to assert both that the in-flight query settles and that it fails.
  • Added a patch changeset for @effect/sql-sqlite-wasm.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Fix all ➔Fix 👍s ➔View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

Comment on lines +347 to +354
function onError(cause: Event) {
const exit = Exit.fail(
new SqlError({ reason: classifyError(cause, "SQLite WASM worker failed", "worker") })
)
const requests = Array.from(pending.values())
pending.clear()
for (const resume of requests) {
resume(exit)

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.

The snapshot-then-clear ordering leaves a small race: if a message response completes a callback between Array.from(pending.values()) and pending.clear(), onError will still call that resume again. Effect.callback resumes are not idempotent, so a double-resume could defect.

Consider iterating entries and deleting each from the map before calling resume, or clearing the map first and then resuming the captured callbacks. Either removes the window.

Suggested change
function onError(cause: Event) {
const exit = Exit.fail(
new SqlError({ reason: classifyError(cause, "SQLite WASM worker failed", "worker") })
)
const requests = Array.from(pending.values())
pending.clear()
for (const resume of requests) {
resume(exit)
function onError(cause: Event) {
const exit = Exit.fail(
new SqlError({ reason: classifyError(cause, "SQLite WASM worker failed", "worker") })
)
const requests = Array.from(pending.values())
pending.clear()
for (const resume of requests) {
resume(exit)
}
Effect.runFork(ScopedRef.set(connectionRef, makeConnection))
}

@tim-smart
tim-smart merged commit 3dd1ddc into main Aug 4, 2026
18 of 19 checks passed
@tim-smart
tim-smart deleted the audit/repro-split-sql-adapters-sw-1 branch August 4, 2026 23:22
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
basic.ts 7.06 KB 7.06 KB 0.00 KB (0.00%)
batching.ts 9.86 KB 9.86 KB 0.00 KB (0.00%)
brand.ts 6.34 KB 6.34 KB 0.00 KB (0.00%)
cache.ts 10.71 KB 10.71 KB -0.00 KB (-0.02%)
config.ts 20.60 KB 20.60 KB 0.00 KB (0.00%)
differ.ts 20.20 KB 20.20 KB 0.00 KB (0.00%)
http-client.ts 21.58 KB 21.54 KB +0.04 KB (+0.19%)
logger.ts 10.84 KB 10.84 KB 0.00 KB (0.00%)
metric.ts 8.98 KB 8.98 KB 0.00 KB (0.00%)
optic.ts 7.18 KB 7.18 KB 0.00 KB (0.00%)
pubsub.ts 14.99 KB 14.99 KB 0.00 KB (0.00%)
queue.ts 11.66 KB 11.66 KB 0.00 KB (0.00%)
schedule.ts 10.83 KB 10.83 KB 0.00 KB (0.00%)
schema-class.ts 19.14 KB 19.14 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 28.96 KB 28.96 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 25.29 KB 25.29 KB 0.00 KB (0.00%)
schema-string-transformation.ts 13.38 KB 13.38 KB 0.00 KB (0.00%)
schema-string.ts 10.94 KB 10.94 KB 0.00 KB (0.00%)
schema-template-literal.ts 15.17 KB 15.17 KB 0.00 KB (0.00%)
schema-toArbitraryLazy.ts 21.94 KB 21.94 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 24.34 KB 24.34 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 19.18 KB 19.18 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 19.01 KB 19.01 KB 0.00 KB (0.00%)
schema-toFormatter.ts 18.87 KB 18.87 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 22.60 KB 22.60 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 19.52 KB 19.52 KB 0.00 KB (0.00%)
schema.ts 18.41 KB 18.41 KB 0.00 KB (0.00%)
stm.ts 12.63 KB 12.63 KB 0.00 KB (0.00%)
stream.ts 9.80 KB 9.80 KB 0.00 KB (0.00%)

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

Labels

4.0 audit Findings originating from the Effect runtime correctness audit bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants