@@ -21,6 +21,15 @@ namespace Aspire.Hosting.Publishing.Internal;
2121/// <param name="logger">The logger instance.</param>
2222public abstract class DeploymentStateManagerBase < T > ( ILogger < T > logger ) : IDeploymentStateManager where T : class
2323{
24+ /// <summary>
25+ /// Holds section metadata including lock and version information.
26+ /// </summary>
27+ private sealed class SectionMetadata
28+ {
29+ public SemaphoreSlim Lock { get ; } = new ( 1 , 1 ) ;
30+ public long Version { get ; set ; }
31+ }
32+
2433 /// <summary>
2534 /// JSON serializer options used for writing deployment state files.
2635 /// </summary>
@@ -35,8 +44,7 @@ public abstract class DeploymentStateManagerBase<T>(ILogger<T> logger) : IDeploy
3544 protected readonly ILogger < T > logger = logger ;
3645 private readonly SemaphoreSlim _loadLock = new ( 1 , 1 ) ;
3746 private readonly SemaphoreSlim _saveLock = new ( 1 , 1 ) ;
38- private readonly ConcurrentDictionary < string , SemaphoreSlim > _sectionLocks = new ( ) ;
39- private readonly ConcurrentDictionary < string , long > _sectionVersions = new ( ) ;
47+ private readonly ConcurrentDictionary < string , SectionMetadata > _sections = new ( ) ;
4048 private JsonObject ? _state ;
4149 private bool _isStateLoaded ;
4250
@@ -193,31 +201,30 @@ public async Task SaveStateAsync(JsonObject state, CancellationToken cancellatio
193201 }
194202 }
195203
196- private SemaphoreSlim GetSectionLock ( string sectionName )
204+ private SectionMetadata GetSectionMetadata ( string sectionName )
197205 {
198- return _sectionLocks . GetOrAdd ( sectionName , _ => new SemaphoreSlim ( 1 , 1 ) ) ;
206+ return _sections . GetOrAdd ( sectionName , _ => new SectionMetadata ( ) ) ;
199207 }
200208
201209 /// <inheritdoc/>
202210 public async Task < DeploymentStateSection > AcquireSectionAsync ( string sectionName , CancellationToken cancellationToken = default )
203211 {
204212 await LoadStateAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
205213
206- var sectionLock = GetSectionLock ( sectionName ) ;
207- await sectionLock . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
214+ var metadata = GetSectionMetadata ( sectionName ) ;
215+ await metadata . Lock . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
208216
209217 try
210218 {
211- var version = _sectionVersions . GetOrAdd ( sectionName , 0 ) ;
212219 var sectionData = _state ? . TryGetPropertyValue ( sectionName , out var sectionNode ) == true && sectionNode is JsonObject obj
213220 ? obj
214221 : null ;
215222
216- return new DeploymentStateSection ( sectionName , sectionData , version , ( ) => sectionLock . Release ( ) ) ;
223+ return new DeploymentStateSection ( sectionName , sectionData , metadata . Version , ( ) => metadata . Lock . Release ( ) ) ;
217224 }
218225 catch
219226 {
220- sectionLock . Release ( ) ;
227+ metadata . Lock . Release ( ) ;
221228 throw ;
222229 }
223230 }
@@ -232,12 +239,12 @@ public async Task SaveSectionAsync(DeploymentStateSection section, CancellationT
232239 throw new InvalidOperationException ( "State has not been loaded." ) ;
233240 }
234241
235- var currentVersion = _sectionVersions . GetOrAdd ( section . SectionName , 0 ) ;
236- if ( currentVersion != section . Version )
242+ var metadata = GetSectionMetadata ( section . SectionName ) ;
243+ if ( metadata . Version != section . Version )
237244 {
238245 throw new InvalidOperationException (
239246 $ "Concurrency conflict detected in section '{ section . SectionName } '. " +
240- $ "Expected version { section . Version } , but current version is { currentVersion } . " +
247+ $ "Expected version { section . Version } , but current version is { metadata . Version } . " +
241248 $ "This typically indicates the section was modified after it was acquired. " +
242249 $ "Ensure the section is saved before disposing or being modified by another operation.") ;
243250 }
@@ -246,7 +253,14 @@ public async Task SaveSectionAsync(DeploymentStateSection section, CancellationT
246253 try
247254 {
248255 _state [ section . SectionName ] = section . Data ;
249- _sectionVersions [ section . SectionName ] = section . Version + 1 ;
256+ _sections . AddOrUpdate (
257+ section . SectionName ,
258+ _ => new SectionMetadata { Version = section . Version + 1 } ,
259+ ( _ , existing ) =>
260+ {
261+ existing . Version = section . Version + 1 ;
262+ return existing ;
263+ } ) ;
250264 await SaveStateToStorageAsync ( _state , cancellationToken ) . ConfigureAwait ( false ) ;
251265 }
252266 finally
0 commit comments