Skip to content

Commit cda1f94

Browse files
Copilotdavidfowl
andcommitted
Merge section locks and versions into single ConcurrentDictionary
Co-authored-by: davidfowl <[email protected]>
1 parent c113c9c commit cda1f94

File tree

1 file changed

+27
-13
lines changed

1 file changed

+27
-13
lines changed

src/Aspire.Hosting/Publishing/Internal/DeploymentStateManagerBase.cs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ namespace Aspire.Hosting.Publishing.Internal;
2121
/// <param name="logger">The logger instance.</param>
2222
public 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

Comments
 (0)