Skip to content

Fix nested transactions failing to commit with opfsLocks - #3871

Closed
hossam1244 wants to merge 2 commits into
simolus3:developfrom
hossam1244:fix/3870-nested-transaction-navigator-locks
Closed

hossam1244 wants to merge 2 commits into
simolus3:developfrom
hossam1244:fix/3870-nested-transaction-navigator-locks

Conversation

@hossam1244

Copy link
Copy Markdown

Problem

On the web with WasmStorageImplementation.opfsLocks, a nested db.transaction() makes the outer transaction fail:

await db.transaction(() async {
  await db.transaction(() async {});
});
// Bad state: Future already completed — and the rollback after it hangs forever.

Root cause: nested transactions are begun through the outer _InterceptedTransactionExecutor, so they flow through the same _AcquireNavigatorLockInterceptor instance and therefore the same _returnNavigatorLocks completer. The nested commit completes it; the outer commit's whenComplete(() => _returnNavigatorLocks.complete()) then throws, and because the worker has already released the transaction, the subsequent rollbackAfterException waits forever in _waitForTurn.

Fixes #3870

Solution

The interceptor now receives the executor that created its scope (ownsLock) and only completes _returnNavigatorLocks for that executor — a nested commit or rollback leaves the lock to the outermost transaction, matching native behavior. ensureOpen was already safe (??= dedupes the lock acquisition). The same guard covers close for the exclusive path.

Tests

Added to the existing @TestOn('browser') suite (drift/test/platforms/web/navigator_locks_interceptor_test.dart):

  • nested transactions complete the outer transaction — the exact repro from the issue; without the fix it never resolves.
  • nested transaction with rollback rolls the outer one back — the nested insert is rolled back with the outer transaction and the future completes with the error instead of hanging.

Full drift VM suite passes locally (927 tests); the browser suite runs in CI.

Checklist

  • I have added tests or the change is covered by existing tests.
  • I have updated the CHANGELOG.md.

Nested transactions are intercepted by the same _AcquireNavigatorLock
instance as their outer transaction, so the nested commit completed the
shared _returnNavigatorLocks completer. The outer commit then failed
with 'Bad state: Future already completed' and, because the worker had
already released the transaction, the following rollback waited forever.

Only the executor that created the interceptor's scope releases the
navigator lock now; nested commits and rollbacks leave it to the
outermost transaction.

Fixes simolus3#3870

@simolus3 simolus3 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for the PR! Instead of fixing this with explicit checks here, I think adding an explicit flag to disable intercepting nested executors is a better option (implemented in bae8660).

@simolus3 simolus3 closed this Oct 7, 2026
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.

opfsLocks: nested transaction completes the shared navigator-lock completer twice; outer commit throws, rollback hangs

2 participants