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

PGliteWorker can post to a closed BroadcastChannel during teardown

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

Maintainers usually reply within 1 day

@zzoraemon043 is already working on this.

Since Oct 19, 2026.

  • #1147 by @zzoraemon043 — open

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
72/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
typescript

Research direction

Start with the PGliteWorker teardown path, especially #leaderNotifyLoop(), close(), and the leader-here handler shown in the issue. Reproduce the startup and leader-change cases, then add regression coverage that verifies close() leaves no timer accessing a closed BroadcastChannel and produces no unhandled rejection.

Written by the indexing model from the issue text.

Description

Reproduction

Reproduced with @electric-sql/pglite 0.5.0, 0.5.6, 0.5.7, and 0.5.8 in Chromium.

pglite.worker.ts:

import { PGlite } from "@electric-sql/pglite";
import { worker } from "@electric-sql/pglite/worker";

worker({
    async init(options) {
        return new PGlite({ dataDir: options.dataDir });
    },
});

main.ts:

import { PGliteWorker } from "@electric-sql/pglite/worker";
import PGliteWorkerScript from "./pglite.worker?worker";

window.addEventListener("unhandledrejection", (event) => {
    console.error("Unhandled rejection:", event.reason);
});

const db = await PGliteWorker.create(
    new PGliteWorkerScript({ type: "module" }),
    { id: "broadcast-channel-close-repro" },
);

console.log("ready at close:", db.ready); // Usually false
await db.close();
await new Promise((resolve) => setTimeout(resolve, 100));

db.ready is commonly false when close() runs. The error is asynchronous, so wait briefly after closing.

Expected

close() should not produce an uncaught exception.

Actual

Shortly after closing:

InvalidStateError: Failed to execute 'postMessage' on 'BroadcastChannel': Channel is closed

In a multi-tab handoff this may also coincide with LeaderChangedError.

Cause

The notification loop schedules a timer while disconnected:

async #leaderNotifyLoop() {
    if (!this.#connected) {
        this.#broadcastChannel!.postMessage({
            type: "tab-here",
            id: this.#tabId,
        });
        setTimeout(() => this.#leaderNotifyLoop(), 16);
    }
}

close() closes the channel but does not cancel or guard the timer:

async close() {
    if (this.#closed) return;
    this.#closed = true;
    this.#broadcastChannel?.close();
    this.#tabChannel?.close();
    this.#releaseTabCloseLock?.();
    this.#workerProcess.terminate();
}

The timer callback discards the async loop's promise, so postMessage() throwing becomes an unhandled rejection. The leader-here handler can re-arm the same loop during handoff.

Request

Please ensure that after close() resolves no internal timer accesses a closed channel or produces an unhandled rejection, with a regression test covering startup and leader-change teardown.

This is distinct from PR #1109, which addresses RPCs hanging across leader changes.

Dominant language
TypeScript
Stars
16.2k
Forks
461
Avg merge
1d 4h
Merged PRs (30d)
23

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.