| File: Api\NativeApplicationLifetime.cs | Web Access |
| Project: src\src\Aspire.Hosting.Native.Server\Aspire.Hosting.Native.Server.csproj (Aspire.Hosting.Native.Server) |
// 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.Native.Api; /// <summary>Allows a guest to retire its application before bounded server shutdown.</summary> internal sealed class NativeApplicationLifetime : IDisposable { private readonly Lock _gate = new(); private readonly TaskCompletionSource _requested = new(TaskCreationOptions.RunContinuationsAsynchronously); private readonly TaskCompletionSource _released = new(TaskCreationOptions.RunContinuationsAsynchronously); private int _runs; private Task? _stopping; private bool _closed; public ApplicationRunLifetime CreateRun() { lock (_gate) { ObjectDisposedException.ThrowIf(_closed, this); if (_stopping is not null) { throw new InvalidOperationException("Application shutdown has started."); } _runs++; return new(this, _requested.Task); } } public Task RequestStopAsync(TimeSpan timeout, TimeProvider clock, Action reportTimeout, CancellationToken cancellationToken) { lock (_gate) { if (_closed) { return Task.CompletedTask; } _requested.TrySetResult(); if (_runs == 0) { _released.TrySetResult(); } return _stopping ??= WaitForReleaseAsync(timeout, clock, reportTimeout, cancellationToken); } } private async Task WaitForReleaseAsync(TimeSpan timeout, TimeProvider clock, Action reportTimeout, CancellationToken cancellationToken) { try { await _released.Task.WaitAsync(timeout, clock, cancellationToken).ConfigureAwait(false); } catch (TimeoutException) { reportTimeout(); } } internal void Release() { lock (_gate) { if (--_runs == 0 && _stopping is not null) { _released.TrySetResult(); } } } public void Dispose() { lock (_gate) { _closed = true; _requested.TrySetCanceled(); _released.TrySetResult(); } } } /// <summary>Keeps application transport alive until the owning guest finishes cleanup and disconnects.</summary> [AspireExport] internal sealed class ApplicationRunLifetime(NativeApplicationLifetime owner, Task requested) : ICapabilityOwner, ICapabilityLifetime { private int _closed; private readonly CancellationTokenSource _connection = new(); public bool IsRevoked => Volatile.Read(ref _closed) != 0; /// <summary>Waits for the host to request ordered application shutdown.</summary> [AspireExport] public Task WaitForStop() => requested.WaitAsync(_connection.Token); public void Close() { if (Interlocked.Exchange(ref _closed, 1) == 0) { _connection.Cancel(); _connection.Dispose(); owner.Release(); } } }