Skip to content

fix: dispose serialized execution queue on backend close#73

Merged
pdlug merged 1 commit into
mainfrom
fix/queue-dispose-on-close
Mar 30, 2026
Merged

fix: dispose serialized execution queue on backend close#73
pdlug merged 1 commit into
mainfrom
fix/queue-dispose-on-close

Conversation

@pdlug
Copy link
Copy Markdown
Contributor

@pdlug pdlug commented Mar 30, 2026

Summary

  • The SQLite backend's serialized execution queue produces unhandled promise rejections when the underlying database is destroyed while operations are still queued (e.g., during Cloudflare Workers test teardown with @cloudflare/vitest-pool-workers)
  • Added dispose() to the serialized queue that tracks in-flight promises and attaches .catch() handlers on disposal to prevent unhandled rejections
  • backend.close() now calls queue.dispose(), and createLocalSqliteBackend's close() now correctly calls the underlying backend.close() before closing the native SQLite connection
  • New operations after close() throw BackendDisposedError

Fixes #72

@pdlug pdlug force-pushed the fix/queue-dispose-on-close branch 3 times, most recently from d033f53 to a9280e8 Compare March 30, 2026 20:19
The SQLite backend's serialized execution queue produced unhandled
promise rejections when the underlying database was destroyed while
operations were still queued (e.g., Cloudflare Workers test teardown
resetting Durable Object storage).

The root cause: queued tasks that execute after teardown fail with
"no such table", and the rejection propagates through the 7+ async
wrappers between the queue and the store-level caller. If the caller
abandoned the promise, every wrapper layer becomes an independently-
unhandled rejected promise. JavaScript offers no way to .catch() a
rejection at the bottom of a chain without the wrappers above it
also creating unhandled rejections.

The fix adds dispose() to the serialized queue, called from
backend.close(). Post-dispose tasks return a never-settling promise
(pendingForever) so no rejection propagates through any layer.
New submissions after dispose reject with BackendDisposedError
since the caller actively holds that promise.

- Add dispose() with never-settling semantics for in-flight tasks
- Add BackendDisposedError to the error hierarchy
- Wire backend.close() to call queue.dispose()
- Fix createLocalSqliteBackend.close() to call backend.close()
  before sqlite.close()
- Make runWithSerializedQueue and exec functions non-async to
  eliminate unnecessary promise wrapper allocations

Fixes #72
@pdlug pdlug force-pushed the fix/queue-dispose-on-close branch from a9280e8 to 5503f91 Compare March 30, 2026 20:25
@pdlug pdlug merged commit 1c95d8e into main Mar 30, 2026
10 checks passed
@pdlug pdlug deleted the fix/queue-dispose-on-close branch March 30, 2026 20:33
@github-actions github-actions Bot mentioned this pull request Mar 30, 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.

[Bug]: Unhandled rejection from serialized execution queue when DO SQLite storage is reset during teardown

1 participant