Skip to content

Web: option to run sync on the main isolate (disable the shared sync worker) for reload-based crash recovery #459

Open

Description

@kenchennyc

Use case / problem

On Flutter web the sync engine runs inside a shared worker (WebPowerSyncDatabase.connectInternal → SyncWorkerHandle.start → new SharedWorker(workerUri) in lib/src/web/sync_controller.dart). That shared worker is deliberately kept alive across DB close (lib/src/web/worker.dart — "The worker is also used for sync … we shouldn't close it just because a database has been closed") and persists across a full page reload.

The downside: if the sync isolate inside that shared worker ever wedges or crashes, a plain page reload does not recover it — the reloaded page reconnects to the same (still-dead) shared worker, and any other open tab of the origin keeps it alive regardless. The only recoveries are to close every tab of the origin (so the browser GCs the now-zero-client worker) or to terminate the worker via chrome://inspect. For a single-user PWA this is a real papercut: sync silently stops and the obvious user action (reload) doesn't fix it.

Notably, connectInternal already contains a main-isolate fallback — it constructs a StreamingSyncImplementation (no shared worker) in the catch branch — but that branch is only reached if SyncWorkerHandle.start throws, which in practice only happens for a blob: workerUri behind an assert (i.e. debug/test only). There is no supported way to opt into it in a release build.

Proposed solution

Expose an option — e.g. a field on SyncOptions, or a parameter to connect(...) — to run sync on the main isolate instead of the shared sync worker, i.e. deterministically take the existing StreamingSyncImplementation path.

Benefits:

  • A plain page reload (or an app-coordinated all-tabs reload) reliably restarts sync, because nothing survives the reload.
  • Single-user PWAs that don't need cross-tab sync coordination can trade that for crash-recoverability.

The database can keep running in its own (dedicated or shared) worker independently — this request is specifically about the sync worker.

Alternatives considered

  • Overriding WebPowerSyncOpenFactory.connectToWorker — only controls the database worker's AccessMode (via sqlite3_web). The sync worker is spawned by a separate new SharedWorker(workerUri) call that never consults the factory, so moving the DB to a dedicated worker does not move sync off the shared worker (verified empirically — the DB-dedicated build still spawns a shared *_db.worker.js for sync).
  • Forking the SDK to skip SyncWorkerHandle.start — works, but is an ongoing maintenance burden on the most correctness-sensitive layer.

Environment

  • powersync 2.3.1 (also checked latest 2.3.3 — no such option); sqlite3_web 0.9.2; sqlite_async 0.14.3.
  • Flutter web (dart2js), Chrome.

Activity

  1. simolus3 commented on Aug 18, 2026

    @simolus3
    Contributor

    : if the sync isolate inside that shared worker ever wedges or crashes, a plain page reload does not recover it

    If only a single tab of your app is open, reloading that tab will also restart the shared worker on all major browsers. You mention this could be useful for "Single-user PWAs that don't need cross-tab sync coordination", which is a fair point but then you already get the behavior you're interested in.

    I'm not opposed to that option, but if there's more than one tab then one of them attempting to connect would wait for sync locks all the time which is also not great if wedges/crashes are what you're trying to avoid.

    It would be good to understand the full motivation behind this request: Are you seeing broken shared workers? If so, that's something we should fix! If you have ways to reproduce that, we would love to take a look.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions