Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Worker: init() rejection is never propagated to clients

Open
#1,122 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
typescript
Domain
api

Research direction

Start in worker/index.ts: the postMessage({ type: "ready", id }) that precedes dbPromise = init(options), the tab-here retry handler that re-awaits dbPromise, and connectTab. Trace PGliteWorkerClientApi.create() and waitReady on the main thread to see why only the connected message settles them. Done means a rejected init() rejects create() and pending/future waitReady with the original error, the 16 ms retry loop stops, and repeated tab-here retries no longer produce unhandled rejections — verify with the issue's fault-injection repro (no OPFS needed).

Written by the indexing model from the issue text.

Description

Version

@electric-sql/pglite 0.5.8 (@electric-sql/pglite/worker, Multi-Tab Worker)

Summary

When the worker-side init() callback rejects, the failure is never propagated to the main-thread client. Three consequences, all observed with fault injection (a plain init: async () => { throw new DOMException(..., "InvalidStateError") } — no OPFS required):

  1. PGliteWorkerClientApi.create() resolves anyway — it only awaits the ready ack, which worker() posts before running init (worker/index.ts: postMessage({ type: "ready", id }) precedes dbPromise = init(options)).
  2. waitReady pends forever — it resolves only on the connected message, which connectTab posts only after await dbPromise succeeds. With a rejected init it neither resolves nor rejects: any application awaiting waitReady before its first query hangs silently, with zero errors and no teardown signal.
  3. The failure is amplified into an unhandled-rejection storm — the client retries tab-here every 16 ms until connected; the leader-side handler (case "tab-here": connectTab(id, await dbPromise, …)) re-awaits the same rejected promise on every retry, so each retry is an unhandled rejection in the worker. Measured in a consumer application's fault-injection run: 4,500 uncaught InvalidStateError in 40 s (~110/s), with thousands more dropped by the console buffer.
Minimal repro
// worker
await worker({
  init: async (options) => {
    throw new DOMException("boom", "InvalidStateError");
  },
});

// main thread
const db = await PGliteWorker.create(worker, { dataDir: "opfs-ahp://demo" }); // resolves!
await db.waitReady; // pends forever; meanwhile the worker console floods
Expected

A rejected init() should reject create() (and any pending or future waitReady) with the original error, stop the tab-here retry loop, and fail later tabs' handshakes with the stored init error instead of re-raising it per retry.

Also observed (same code path, adjacent)

The main-thread retry timer keeps firing after client close() (it doesn't observe closure), so closing the BroadcastChannels turns the silent loop into InvalidStateError: postMessage on closed Channel thrown inside a timer callback. Either the timer should observe closed, or close() docs should warn that channels must remain open until unload.

Related
  • #1064 — async-executor never-settle in opfs-ahp pool init (the other half of "broken startup wedges silently")
  • #1109 — rpc with no leader hangs forever
  • #1116 — lock leak on tab close
  • #1112 — teardown BroadcastChannel race
Suggested fix shape

Attach a handler to dbPromise that records the terminal init failure (name/message/stack); reject waitReady and settled create() callers from that state; stop/catch the retry loop on failure and on closed; in connectTab, reply to tab-here with a failure message so connecting tabs reject immediately.

Happy to send a PR if maintainers agree on the shape.

Dominant language
TypeScript
Stars
16.1k
Forks
447
PR merge metrics
No merged PRs in 30d

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from electric-sql/pglite

All issues in electric-sql/pglite

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.