File: Model\BrowserStorage\LocalStorageLocks.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Concurrent;
 
namespace Aspire.Dashboard.Model.BrowserStorage;
 
/// <summary>
/// Makes saves of a setting in the browser's local storage run one at a time across circuits.
/// </summary>
/// <remarks>
/// Each tab of the dashboard has its own circuit, and the tabs of a browser share its local storage. A save reads the
/// stored value, applies changes to it and writes it back, so two tabs saving at the same time could both read the
/// same value, and the second write would replace the first tab's change. Every circuit runs in this dashboard
/// process, so holding a lock for the storage key during the whole save makes the saves of a setting run one after
/// another. Saves from different browsers, which don't share storage, also wait for each other: saves are rare and
/// short, so telling browsers apart isn't worth it.
/// </remarks>
public sealed class LocalStorageLocks
{
    private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new(StringComparer.Ordinal);
 
    // Keys are a fixed set of settings per application name, so the locks aren't removed.
    internal SemaphoreSlim GetLock(string key) => _locks.GetOrAdd(key, static _ => new SemaphoreSlim(1, 1));
}