Skip to content

fix(shared-internals): clear ConnectionManager's init and disconnect promises when they reject - #1177

Open
shahjahangc wants to merge 1 commit into
powersync-ja:mainfrom
shahjahangc:fix/1097-connection-manager-latches
Open

shahjahangc wants to merge 1 commit into
powersync-ja:mainfrom
shahjahangc:fix/1097-connection-manager-latches

Conversation

@shahjahangc

Copy link
Copy Markdown

Fixes #1097

Two of ConnectionManager's in-flight promise fields, syncStreamInitPromise and disconnectingPromise, were cleared only when the promise resolved.

  • connectInternal() stores the sync implementation's creation in syncStreamInitPromise. When the creation rejects, the field keeps the rejected promise, and performDisconnect() awaits that field first. So after one failed creation, every later disconnect() rejects with the creation's error, and so does the next connect(), which disconnects first. The connect() whose creation failed still resolves.
  • disconnectInternal() stores the in-flight disconnect in disconnectingPromise, and a rejected one stays there for every later caller. The first fix does not close this: a disconnect() that is waiting on a creation when the creation fails rejects too, and its promise is kept here.

Both fields are now cleared in a finally, as discussed in #1097. A disconnect() already waiting on either promise still gets its rejection; the next call starts over.

I left the async promise executor as it is to keep this to the fix. Happy to swap it for an async lambda here or separately.

Tests. Three cases in tests/client/ConnectionManager.test.ts, on the existing harness:

  • After a failed creation, disconnect() resolves, and a reconnect creates and connects a new implementation. On main, that disconnect() rejects with the creation's error.
  • A disconnect() that is waiting on a creation that fails still rejects with that error, and the next disconnect() resolves. On main, the next one rejects with the same error.
  • After a disconnect() whose sync.disconnect() rejects, the next disconnect() resolves and a reconnect works. On main, the next one rejects with the same error.

Reverting connectInternal()'s finally turns the first two red, and reverting disconnectInternal()'s turns the last two red. All 79 shared-internals tests pass, tsc --noEmit is clean for the package's src (its tsconfig does not include tests/), and the changed files are formatted with the repo's prettier settings. There's a patch changeset.

Left out, found while testing. All three exist on main. With this change the manager keeps working after a failure, so they can now happen more than once:

  • If sync.disconnect() throws, performDisconnect() never calls dispose() or the disposer, so that implementation is not disposed.
  • BasePowerSyncDatabase's createSyncImplementation builds the sync and then awaits its waitForReady(). If that rejects, the sync is dropped without being disposed.
  • connect() swallows a failed creation and nothing logs it. On main, the next disconnect() or connect() happened to surface the error by rejecting with it; with this change nothing does.

Happy to take any of these here or in a follow-up.

🤖 Generated with Claude Code

…promises when they reject

connectInternal() kept a rejected syncStreamInitPromise, which
performDisconnect() awaits first, so one failed sync implementation
creation made every later disconnect(), and the next connect(), reject
with that error. disconnectInternal() likewise kept a rejected
disconnectingPromise and handed it to every later caller; a disconnect
waiting on a creation that fails reaches it even with the first fix.
Both are now cleared in a finally.

Fixes powersync-ja#1097

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7fa7831

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

This PR includes changesets to release 7 packages
Name Type
@powersync/shared-internals Patch
@powersync/adapter-sql-js Patch
@powersync/capacitor Patch
@powersync/node Patch
@powersync/react-native Patch
@powersync/web Patch
@powersync/diagnostics-app 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

This branch has not been deployed

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

Labels

None yet

Projects

None yet

2 participants