Skip to content

Rework Mutex#33

Merged
garethj2 merged 8 commits into
masterfrom
feat/rework-async-mutex
Jun 19, 2026
Merged

Rework Mutex#33
garethj2 merged 8 commits into
masterfrom
feat/rework-async-mutex

Conversation

@garethj2

Copy link
Copy Markdown
Contributor

Fix concurrent watch operations and strengthen async primitives

Motivation

The Mutex implementation had correctness issues under concurrent load, and several ApiSubject methods had TOCTOU (check-then-act) races that could cause double-opens, interleaved state mutations, and stuck locks after network errors. Test coverage for the async behaviour in Mutex, ApiSubject, and BatchSubject was also incomplete.


Changes

src/util/Mutex.ts

Rewrote from a single-promise busy-wait loop to an explicit FIFO waiter queue:

  • The lock is handed directly to the next waiter on release so it never briefly becomes free between queued tasks — this eliminates the race where wait() could return while a queued task was about to start
  • Added a public acquire(): Promise<() => void> primitive that returns a one-shot release function, allowing callers to hold the lock across a multi-step critical section
  • runSequential and wait are now built on top of acquire/release, removing the duplicated wait-loop logic
  • release is idempotent — calling it more than once is a no-op

src/client/watches/ApiSubject.ts

Fixed six TOCTOU races caused by the await mutex.wait() + separate runSequential() pattern, which left a gap where another caller could interleave between a state check and the action taken on that state:

  • add, remove, refresh, checkClose, and poll now call acquire() once and hold the lock across the state check and the work
  • Private helpers addIds, open, close, and reopen are now lock-free inner functions called while the caller already holds the lock
  • A network error in any operation now reliably releases the lock via try/finally

Tests added

File New tests
spec/util/Mutex.spec.ts FIFO ordering under varying durations, error isolation between tasks, acquire/release ordering, double-release no-op, stale handle safety, wait() when idle, concurrent wait() calls
spec/client/watches/ApiSubject.spec.ts No double-open from concurrent add calls, sequential ordering of concurrent add+remove, mutex released after failing open / add / remove network calls
spec/client/watches/BatchSubject.spec.ts display, pollRate, refresh, on, off, get delegation; error propagation to all coalesced callers; subsequent ops continue after a failed op

@garethj2 garethj2 self-assigned this Jun 18, 2026
Comment thread src/util/Mutex.ts
Comment thread src/util/Mutex.ts Outdated
@garethj2 garethj2 merged commit 8d28b17 into master Jun 19, 2026
1 check passed
@garethj2 garethj2 deleted the feat/rework-async-mutex branch June 19, 2026 07:20
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