| File: Ats\CapabilityInvocationGate.cs | Web Access |
| Project: src\src\Aspire.Hosting.RemoteHost\Aspire.Hosting.RemoteHost.csproj (Aspire.Hosting.RemoteHost) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Aspire.Hosting.RemoteHost.Ats; /// <summary> /// Serializes the synchronous portion of capability invocations for a guest connection. /// </summary> /// <remarks> /// The JSON-RPC server dispatches requests concurrently, so un-awaited guest calls such as /// <c>addPostgres</c> and <c>addHealthCheck</c> can run at the same time. Exported methods mutate /// non-thread-safe builder state (for example the service and resource collections), so their /// synchronous invocations must not overlap. The gate is released when the invocation returns or /// when it first calls back into the guest, which may re-enter the remote host before completing. /// </remarks> internal sealed class CapabilityInvocationGate { // AsyncLocal (rather than ThreadStatic) so callbacks offloaded to other threads by the invocation can still release the gate. private static readonly AsyncLocal<Lease?> s_currentLease = new(); private readonly SemaphoreSlim _semaphore = new(1, 1); /// <summary> /// Runs <paramref name="invocation"/> once no other gated invocation is running. /// </summary> public async Task<T> RunAsync<T>(Func<T> invocation) { await _semaphore.WaitAsync().ConfigureAwait(false); var lease = new Lease(_semaphore); var previousLease = s_currentLease.Value; s_currentLease.Value = lease; try { return invocation(); } finally { s_currentLease.Value = previousLease; lease.Release(); } } /// <summary> /// Releases the gate held by the current invocation context before invoking a guest callback. /// </summary> public static void ReleaseForCallback() { var lease = s_currentLease.Value; if (lease is null) { return; } s_currentLease.Value = null; lease.Release(); } private sealed class Lease(SemaphoreSlim semaphore) { private SemaphoreSlim? _semaphore = semaphore; public void Release() => Interlocked.Exchange(ref _semaphore, null)?.Release(); } }