Skip to content

🐛 allocate v2 exchange generations before async dispatch and make verifier acquisition atomic - #4693

Merged
guiyanakuang merged 2 commits into
mainfrom
fix/issue-4692-queued-warmup-cancel-race
Jul 30, 2026
Merged

🐛 allocate v2 exchange generations before async dispatch and make verifier acquisition atomic#4693
guiyanakuang merged 2 commits into
mainfrom
fix/issue-4692-queued-warmup-cancel-race

Conversation

@guiyanakuang

Copy link
Copy Markdown
Member

Closes #4692. Stacked on #4690 (base is its branch; retargets to main once #4690 merges).

Changes

1. Generation allocation moved ahead of the async event boundary.

  • New SyncManager.exchangeKeysForPairing(appInstanceId): the warm-up's generation is allocated and recorded in the ledger synchronously at the UI call site, before the event enters the resolver queue; SyncEvent.ExchangeKeysForPairing, SyncHandler, and SyncDeviceManager carry it through.
  • SyncDeviceManager.exchangeKeysForPairing sends only while the ledger still holds that exact generation: a dialog dismissed while the warm-up event was still queued now captures the generation, its cancel consumes the ledger entry first, and the late warm-up sends nothing. This closes the last residual window from 🐛 release the responder's pending v2 exchange when the initiator abandons pairing #4690 (previously the cancel found an empty ledger and the orphaned responder entry had to self-heal).

2. Atomic verifier acquisition.

  • AppTokenApi.acquireVerifier(appInstanceId, showToken) replaces the hand-composed addPendingVerifier + startRefresh pair. Acquisition is idempotent per verifier: repeated acquisition (v1 dialog reopened, repeated /sync/showToken) reopens the overlay without incrementing the refresh count twice — fixing the historical v1 accumulation where reopening the dialog n times leaked n−1 counts past the confirm.

3. Lock-free CAS ledger.

  • PendingExchangeLedger now keeps its per-peer map in an atomicfu atomic reference; consume is a true compare-and-set loop, so a stale consume racing a replacement record for the same peer can never drop the newer generation. Covered by a 1000-round concurrent stress test.

4. Extension parity.

  • The core JS buildKeyExchangeRequest emits strictly monotonic timestamps (maxOf(now, prev + 1)), so same-millisecond attempts can no longer collide as generations.
  • exchangeV2 cancels its exact signed generation when the exchange fails after the request may have reached the desktop (lost or invalid response), before surfacing the original failure.

Tests

  • GeneralSyncManagerTest: warm-up generation is recorded synchronously before dispatch.
  • SyncDeviceManagerTest: a warm-up whose generation was already consumed (cancelled) or superseded sends no request; send/skip matrix updated to the carried-generation signature.
  • PendingExchangeLedgerTest: concurrent stale-consume vs. replacement-record stress test (1000 rounds); consume drops the record.
  • AppTokenServiceTest: repeated acquisition by one verifier owns exactly one count; release-after-acquire ordering; hideToken leaves counts owned by others untouched.
  • Web (sync-v2v3.test.ts): exchangeV2 auto-cancels its exact generation on a tampered response and on a lost response; 32 concurrent buildKeyExchangeRequest calls produce 32 distinct generations.
  • Full app:desktopTest, web vitest (102 tests), and tsc --noEmit pass; core JS library rebuilt and consumed by the web tests.

Base automatically changed from fix/issue-4684-pending-exchange-release to main July 30, 2026 07:55
@guiyanakuang
guiyanakuang force-pushed the fix/issue-4692-queued-warmup-cancel-race branch from b48fb0f to 15fd436 Compare July 30, 2026 13:45
@guiyanakuang
guiyanakuang merged commit 25cae3d into main Jul 30, 2026
5 checks passed
@guiyanakuang
guiyanakuang deleted the fix/issue-4692-queued-warmup-cancel-race branch July 30, 2026 13:53
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.

v2 pairing: close the queued warm-up cancellation race and make verifier acquisition atomic

1 participant