Skip to content

exp 168: synchronous uncontended writer mutex fast-path (rejected)#161

Open
danReynolds wants to merge 3 commits into
mainfrom
exp-168-uncontended-mutex-fastpath
Open

exp 168: synchronous uncontended writer mutex fast-path (rejected)#161
danReynolds wants to merge 3 commits into
mainfrom
exp-168-uncontended-mutex-fastpath

Conversation

@danReynolds

Copy link
Copy Markdown
Owner

Hypothesis

After exp 159 + 161, the residual writer/request wall on A11c overlap is still the largest bucket (71.8 %; exp 147). Exp 151 already rejected the response-side Completer<T>.sync() request-resolution tweak. This experiment tested the matching request-side variant: Mutex.tryLock() plus a non-async Writer.execute / Writer.executeBatch that drops the uncontended await _mutex.lock() microtask hop and the wrapping async function's implicit reply await. For a serial-await caller (Single Inserts (100 sequential), sequential-awaited), the mutex is always free; both hops are pure scheduling overhead.

Acceptance gate (set before running the candidate): focused sequential-awaited (2000 writes) median moves outside the run-to-run noise band (>5 %), with no regression on concurrent-burst or transaction-guardrail.

Approach

  • lib/src/mutex.dart: add bool tryLock() — sync acquire when _completer == null, never jumps a parked waiter (preserves FIFO fairness).
  • lib/src/writer/writer.dart: make Writer.execute / Writer.executeBatch non-async. Fast path: tryLock() → sync send via existing executeInTransaction / executeBatchInTransactionunlock() → return reply Future directly. Slow path stays async (_executeSlow / _executeBatchSlow).
  • test/transaction_test.dart: rewrite the close() contention test as two scenarios — (a) writes issued sync-before-close all complete in FIFO order through the worker port (new behavior), (b) a long-running transaction holds the lock while a parked writer wakes on close and sees _closed (still guards the original "do not send to a closed receive port" property).

No public API change.

Code reverted before commit since the experiment is rejected — only the writeup, README row, and signal map remain. Cherry-pickable from this PR's git history if a future runner wants the implementation back.

Results

Three paired runs each, baseline / candidate interleaved (per JOURNAL.md "phase-ordered A/B" lesson).

metric baseline median candidate median delta
Single Inserts (100 sequential) — writes.dart 3.099 ms 3.153 ms +1.7 %
Concurrent Single Inserts (100 concurrent) — writes.dart 1.231 ms 1.135 ms −7.8 %
sequential-awaited (2000 writes)writer_pipelining.dart 34.047 ms 34.723 ms +2.0 %
concurrent-burst (10×200)writer_pipelining.dart 27.524 ms 26.981 ms −2.0 %
transaction-guardrail (50 tx ×10)writer_pipelining.dart 4.611 ms 4.770 ms +3.4 %

The primary sequential lanes moved within ±2 % in the wrong direction across paired runs (inside the per-run noise band). The only positive signal (−7.8 % on Concurrent Single Inserts) is a row exp 159 already drives at −58 % to −61 %.

Outcome

Rejected — below current signal on the primary lane. The hypothesis targeted the sequential-awaited shape; the data refutes the premise that the await _mutex.lock() microtask hop is a measurable fraction of the per-write residual on the canonical lane. The marginal concurrent improvement does not justify the extra slow-path branch plus the behavioral change of sync-before-close writes no longer being rejected.

Would reopen if: (a) a future Dart SDK makes await over a resolved Future provably more expensive than today, or (b) a Tracelite profile shows the sequential shape spending materially more wall in main-isolate scheduling spans than in the writer-handle span. Per exp 159 + 161, the next bounded structural candidates for the sequential floor are still cross-call request batching (group commit) or a shared-memory transport.

Test plan

  • dart analyze — no issues
  • dart test test/transaction_test.dart test/database_test.dart test/diagnostics_test.dart — 93 passed (verified on candidate before revert)
  • dart run benchmark/finalize_experiment.dart --experiment=experiments/168-uncontended-mutex-fastpath.md — green
  • dart run benchmark/experiments/writer_pipelining.dart — 3 paired baseline/candidate runs
  • dart run benchmark/suites/writes.dart — 3 paired baseline/candidate runs

🤖 Generated with Claude Code

danReynolds and others added 3 commits June 13, 2026 06:04
Tested Mutex.tryLock + non-`async` Writer.execute / executeBatch as the
request-side counterpart to exp 151's rejected response-side
Completer<T>.sync() attempt. Hypothesis: dropping the uncontended
`await _mutex.lock()` microtask hop plus the async wrapper's implicit
reply await would move the Single Inserts (100 sequential) lane.

Paired focused + release runs (3 alternating pairs each):

- Single Inserts (100 sequential): 3.099 → 3.153 ms median (+1.7%)
- writer_pipelining sequential-awaited (2000 writes):
  34.047 → 34.723 ms median (+2.0%)
- Concurrent Single Inserts (100 concurrent): 1.231 → 1.135 ms (-7.8%)
- writer_pipelining concurrent-burst (10×200): 27.524 → 26.981 ms (-2.0%)
- transaction-guardrail (50 tx × 10): 4.611 → 4.770 ms (+3.4%)

The primary sequential lanes moved within ±2 % in the wrong direction.
The only positive signal (Concurrent Single Inserts) is owned by
exp 159 at -58 % to -61 % already.

Rejected as request-scheduling change below current signal. No runtime
code kept; doc + signals carry the durable evidence so the same idea is
not retried without a Dart-runtime change or a workload where main-isolate
scheduling dominates writer-side execution.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Records the lesson that exp 151 (response-side) and exp 168 (request-side)
produced the same verdict under the same gate; the candidate that follows
a scheduling-shape rejection should change the mechanism, not the side.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant