Skip to content

Use custom mutex implementation instead of async-mutex#900

Merged
simolus3 merged 5 commits intomainfrom
vendor-mutex
Mar 18, 2026
Merged

Use custom mutex implementation instead of async-mutex#900
simolus3 merged 5 commits intomainfrom
vendor-mutex

Conversation

@simolus3
Copy link
Contributor

@simolus3 simolus3 commented Mar 18, 2026

To serialize some operations within a JavaScript context, we currently rely on the async-mutex package. That package is great, but we have some use-cases that are tricky to implement with it. In particular, it doesn't support timeouts (well, it does, but only globally on a mutex and we need per-acquire timeouts). Our workaround so far is to either ignore the timeout, or to acquire the mutex and then return it immediately if the timeout has expired. This is not a particularly good timeout implementation since we still need to wait for the mutex once even if the timeout has elapsed. It also doesn't support aborting an individual mutex request.

For the JS worker refactoring (#889) which introduces local mutexes where we've previously used navigator locks (with proper aborts), relying on async-mutex and ignoring timeouts would be a regression. So, considering that async-mutex doesn't do exactly what we need and since writing async mutexes in single-threaded languages is not that hard, this rolls our own mutex implementation. For the most part, it has the exact same API as async-mutex:

  1. Calling and awaiting acquire() locks the mutex, and returns a function that unlocks it when invoked.
  2. The runExclusive helper invokes a callback while them mutex is locked.

Because some of our mutex invocations rely on abort signals and others rely on timeouts, I've added an AbortSignal-based abort strategy to the implementation. In places where we've previously been using timeouts, the new timeoutSignal function is a wrapper around AbortSignal.timeout that should work in all places.

@changeset-bot
Copy link

changeset-bot bot commented Mar 18, 2026

🦋 Changeset detected

Latest commit: 465b193

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

This PR includes changesets to release 10 packages
Name Type
@powersync/react-native Minor
@powersync/common Minor
@powersync/web Minor
@powersync/op-sqlite Patch
@powersync/adapter-sql-js Patch
@powersync/capacitor Patch
@powersync/node Patch
@powersync/nuxt Patch
@powersync/tanstack-react-query 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

@simolus3 simolus3 requested a review from stevensJourney March 18, 2026 12:09
@simolus3 simolus3 marked this pull request as ready for review March 18, 2026 12:09
Copy link
Collaborator

@stevensJourney stevensJourney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy with these changes. Removing a dependency is always nice, removing it and replacing it with even more functionality is even better :)

@simolus3 simolus3 merged commit 8f8ef1c into main Mar 18, 2026
9 checks passed
@simolus3 simolus3 deleted the vendor-mutex branch March 18, 2026 14:54
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.

2 participants