You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Reduce async state machine allocations and kernel transitions on the concurrency gate hot path by adding synchronous TryEnter fast path when the global and per state semaphores are uncontended.
Goal
Lower latency and GC pressure for concurrent mutation execution without changing public API contracts or concurrency semantics.
Problem
Every EnterAsync on MutationExecutionConcurrencyGate always goes through the async state machine (allocating ValueTask box + task), even when both the global gate and the per state gate are immediately available. In common case (low contention, distinct states) this overhead is pure waste.
Additionally, for stateless mutations (stateId is null/empty) the gate still entered the full async ValueTask path, incurring the same overhead for trivial no op state check.
Stateless - skips state gate entirely, uses sync Wait(0) on global gate before falling back to async
Stateful, gate exists - tries sync Wait(0) on global + state gate, returns struct Lease synchronously on success
Stateful, gate missing or contended - falls back to original async ValueTask path with GetOrAdd
Lease struct keeps direct SemaphoreSlim references (no indirection via owner)
Important
Design Expectations
Hot path (uncontended gate) must return completed ValueTask<Lease> without allocating an async state machine.
Sync Wait(0) is non blocking interlocked operation - safe for use from async code paths when the semaphore is available. When contended, it immediately returns false and falls back to the async path.
No changes to concurrency semantics: global gate still limits total concurrent mutations, state gate still serializes per state access.
No public API or interface changes.
Acceptance Criteria
EnterAsync returns synchronously when global gate is uncontended and no stateId is provided
EnterAsync returns synchronously when both global and state gates are uncontended
Contended paths fall back to WaitAsync with equivalent behaviour
All existing unit tests pass without modification
Benchmark regression validated:
Benchmark
Before
After
Δ
BatchScheduling (2 batches × 4 items)
18.12 us / 27.09 KB
13.54 us / 26.16 KB
-25.3%
BatchScheduling (2 × 16)
71.95 us / 105.09 KB
52.21 us / 101.34 KB
-27.4%
BatchScheduling (4 × 4)
36.71 us / 54.09 KB
27.13 us / 52.22 KB
-26.1%
BatchScheduling (4 × 16)
140.05 us / 210.10 KB
104.11 us / 202.59 KB
-25.7%
ParallelExecution (2)
4.324 us / 6.42 KB
3.237 us / 6.19 KB
-25.1%
ParallelExecution (8)
17.557 us / 25.41 KB
12.537 us / 24.47 KB
-28.6%
Non Goals
This issue does not add per-state SemaphoreSlim eviction or dictionary cleanup
This issue does not change ValueTask<T> return types in other engine components
This issue does not touch MutationEngine or InterceptorPipeline
This issue does not change any public API or interface
Note
The sync fast path uses SemaphoreSlim.Wait(0, CancellationToken) which is equivalent to Wait(TimeSpan.Zero) with cancellation check. It returns immediately without blocking the calling thread.
Summary
Reduce async state machine allocations and kernel transitions on the concurrency gate hot path by adding synchronous
TryEnterfast path when the global and per state semaphores are uncontended.Goal
Lower latency and GC pressure for concurrent mutation execution without changing public API contracts or concurrency semantics.
Problem
Every
EnterAsynconMutationExecutionConcurrencyGatealways goes through the async state machine (allocatingValueTaskbox + task), even when both the global gate and the per state gate are immediately available. In common case (low contention, distinct states) this overhead is pure waste.Additionally, for stateless mutations (
stateIdis null/empty) the gate still entered the fullasync ValueTaskpath, incurring the same overhead for trivial no op state check.Scope
MutationExecutionConcurrencyGate (
src/Runtime/Internal/Execution/MutationExecutionConcurrencyGate.cs):EnterAsyncinto three paths:Wait(0)on global gate before falling back to asyncWait(0)on global + state gate, returns structLeasesynchronously on successasync ValueTaskpath withGetOrAddLeasestruct keeps directSemaphoreSlimreferences (no indirection via owner)Important
Design Expectations
ValueTask<Lease>without allocating an async state machine.Wait(0)is non blocking interlocked operation - safe for use from async code paths when the semaphore is available. When contended, it immediately returnsfalseand falls back to the async path.Acceptance Criteria
EnterAsyncreturns synchronously when global gate is uncontended and nostateIdis providedEnterAsyncreturns synchronously when both global and state gates are uncontendedWaitAsyncwith equivalent behaviourBatchScheduling (2 batches × 4 items)BatchScheduling (2 × 16)BatchScheduling (4 × 4)BatchScheduling (4 × 16)ParallelExecution (2)ParallelExecution (8)Non Goals
SemaphoreSlimeviction or dictionary cleanupValueTask<T>return types in other engine componentsMutationEngineorInterceptorPipelineNote
The sync fast path uses
SemaphoreSlim.Wait(0, CancellationToken)which is equivalent toWait(TimeSpan.Zero)with cancellation check. It returns immediately without blocking the calling thread.