11using System . Collections . Concurrent ;
2+ using System . Runtime . CompilerServices ;
23
34namespace ModularityKit . Mutator . Runtime . Internal . Execution ;
45
56/// <summary>
6- /// Coordinates core mutation execution concurrency across the engine.
7+ /// Coordinates concurrent mutation execution using global concurrency limit
8+ /// and per state serialization.
79/// </summary>
8- internal sealed class MutationExecutionConcurrencyGate ( int maxConcurrentMutations )
10+ /// <remarks>
11+ /// When state identifier is provided, an additional per state gate ensures that
12+ /// mutations targeting the same state are executed sequentially, while mutations
13+ /// targeting different states may execute concurrently up to the global limit.
14+ /// </remarks>
15+ /// <param name="maxConcurrentMutations">The maximum number of mutations that may execute concurrently across all states. </param>
16+ internal sealed class MutationExecutionConcurrencyGate (
17+ int maxConcurrentMutations )
918{
10- private readonly SemaphoreSlim _globalGate = new ( maxConcurrentMutations , maxConcurrentMutations ) ;
11- private readonly ConcurrentDictionary < string , SemaphoreSlim > _stateGates = new ( StringComparer . Ordinal ) ;
19+ private readonly SemaphoreSlim _globalGate =
20+ new ( maxConcurrentMutations , maxConcurrentMutations ) ;
1221
13- public async ValueTask < Lease > EnterAsync ( string ? stateId , CancellationToken cancellationToken )
22+ private readonly ConcurrentDictionary < string , SemaphoreSlim > _stateGates =
23+ new ( StringComparer . Ordinal ) ;
24+
25+ /// <summary>
26+ /// Enters the concurrency gate for mutation execution.
27+ /// </summary>
28+ /// <param name="stateId">The identifier of the state being mutated.</param>
29+ /// <param name="cancellationToken">Token that can be used to cancel waiting for the required concurrency gates. </param>
30+ /// <returns>An asynchronous lease that releases the acquired concurrency gates when disposed. </returns>
31+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
32+ public ValueTask < Lease > EnterAsync (
33+ string ? stateId ,
34+ CancellationToken cancellationToken )
1435 {
15- await _globalGate . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
36+ if ( stateId is null )
37+ return EnterGlobalOnlyAsync ( cancellationToken ) ;
1638
17- var stateGate = default ( SemaphoreSlim ) ;
39+ return EnterWithStateAsync ( stateId , cancellationToken ) ;
40+ }
1841
19- try
42+ /// <summary>
43+ /// Attempts to acquire the global concurrency gate without asynchronous waiting
44+ /// when capacity is immediately available.
45+ /// </summary>
46+ private ValueTask < Lease > EnterGlobalOnlyAsync (
47+ CancellationToken cancellationToken )
48+ {
49+ if ( _globalGate . Wait ( 0 , cancellationToken ) )
50+ {
51+ return new ValueTask < Lease > (
52+ new Lease ( _globalGate , null ) ) ;
53+ }
54+
55+ return SlowEnterGlobalOnlyAsync ( cancellationToken ) ;
56+ }
57+
58+ /// <summary>
59+ /// Asynchronously waits for the global concurrency gate when it could not be
60+ /// acquired immediately.
61+ /// </summary>
62+ private async ValueTask < Lease > SlowEnterGlobalOnlyAsync (
63+ CancellationToken cancellationToken )
64+ {
65+ await _globalGate
66+ . WaitAsync ( cancellationToken )
67+ . ConfigureAwait ( false ) ;
68+
69+ return new Lease ( _globalGate , null ) ;
70+ }
71+
72+ /// <summary>
73+ /// Attempts to acquire both the global concurrency gate and the gate associated
74+ /// with the specified state.
75+ /// </summary>
76+ /// <remarks>
77+ /// The global gate is always acquired before the per state gate to maintain
78+ /// consistent acquisition order and avoid lock order inversion.
79+ /// </remarks>
80+ private ValueTask < Lease > EnterWithStateAsync (
81+ string stateId ,
82+ CancellationToken cancellationToken )
83+ {
84+ if ( _stateGates . TryGetValue ( stateId , out var existing ) )
2085 {
21- if ( ! string . IsNullOrWhiteSpace ( stateId ) )
86+ if ( _globalGate . Wait ( 0 , cancellationToken ) )
2287 {
23- stateGate = _stateGates . GetOrAdd ( stateId , static _ => new SemaphoreSlim ( 1 , 1 ) ) ;
24- await stateGate . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
88+ if ( existing . Wait ( 0 , cancellationToken ) )
89+ {
90+ return new ValueTask < Lease > (
91+ new Lease ( _globalGate , existing ) ) ;
92+ }
93+
94+ _globalGate . Release ( ) ;
2595 }
96+ }
97+
98+ return SlowEnterWithStateAsync (
99+ stateId ,
100+ cancellationToken ) ;
101+ }
102+
103+ /// <summary>
104+ /// Asynchronously acquires the global and per state gates when the fast path
105+ /// could not acquire them immediately.
106+ /// </summary>
107+ private async ValueTask < Lease > SlowEnterWithStateAsync (
108+ string stateId ,
109+ CancellationToken cancellationToken )
110+ {
111+ await _globalGate
112+ . WaitAsync ( cancellationToken )
113+ . ConfigureAwait ( false ) ;
26114
27- return new Lease ( _globalGate , stateGate ) ;
115+ try
116+ {
117+ var stateGate = _stateGates . GetOrAdd (
118+ stateId ,
119+ static _ => new SemaphoreSlim ( 1 , 1 ) ) ;
120+
121+ await stateGate
122+ . WaitAsync ( cancellationToken )
123+ . ConfigureAwait ( false ) ;
124+
125+ return new Lease (
126+ _globalGate ,
127+ stateGate ) ;
28128 }
29129 catch
30130 {
@@ -34,24 +134,23 @@ public async ValueTask<Lease> EnterAsync(string? stateId, CancellationToken canc
34134 }
35135
36136 /// <summary>
37- /// Represents an acquired execution slot.
137+ /// Represents an acquired concurrency lease that releases the associated gates
138+ /// when disposed.
38139 /// </summary>
39- internal readonly struct Lease : IAsyncDisposable
140+ internal readonly struct Lease (
141+ SemaphoreSlim globalGate ,
142+ SemaphoreSlim ? stateGate ) : IAsyncDisposable
40143 {
41- private readonly SemaphoreSlim _globalGate ;
42- private readonly SemaphoreSlim ? _stateGate ;
43-
44- public Lease ( SemaphoreSlim globalGate , SemaphoreSlim ? stateGate )
45- {
46- _globalGate = globalGate ;
47- _stateGate = stateGate ;
48- }
49-
144+ /// <summary>
145+ /// Releases the per state gate, when present, followed by the global gate.
146+ /// </summary>
147+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
50148 public ValueTask DisposeAsync ( )
51149 {
52- _stateGate ? . Release ( ) ;
53- _globalGate . Release ( ) ;
150+ stateGate ? . Release ( ) ;
151+ globalGate . Release ( ) ;
152+
54153 return ValueTask . CompletedTask ;
55154 }
56155 }
57- }
156+ }
0 commit comments