| File: Runtime\ProviderLifecycle.cs | Web Access |
| Project: src\src\Aspire.Hosting.Native.Core\Aspire.Hosting.Native.Core.csproj (Aspire.Hosting.Native.Core) |
// 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.Immutable; namespace Aspire.Hosting.Native.Runtime; internal static class ProviderLifecyclePhases { public const string Initialize = "initialize"; public const string Allocated = "allocated"; public const string Ready = "ready"; public const string Retiring = "retiring"; } /// <summary>Identifies an offline-validated provider callback without transport or language objects.</summary> internal sealed record ProviderLifecycleBinding(Guid ProviderId, string ObjectId, string TypeId, string Phase, string CapabilityId); /// <summary>Separates native lifecycle ordering and authority from the provider transport.</summary> internal interface IProviderLifecycleDispatcher { Task InvokeAsync(ProviderLifecycleBinding binding, ResourceLifecycleAuthority authority, CancellationToken cancellationToken); } internal sealed class UnavailableProviderLifecycleDispatcher : IProviderLifecycleDispatcher { public static UnavailableProviderLifecycleDispatcher Instance { get; } = new(); public Task InvokeAsync(ProviderLifecycleBinding binding, ResourceLifecycleAuthority authority, CancellationToken cancellationToken) => throw new NotSupportedException("No provider lifecycle dispatcher is configured."); } /// <summary>Grants one phase native execution with captured inputs, never credential inspection or declaration access.</summary> internal sealed class ResourceLifecycleAuthority : IDisposable { private readonly CancellationTokenSource _lifetime; private readonly CancellationToken _token; private readonly ImmutableDictionary<string, string> _values; private readonly Func<WorkloadPlan, CancellationToken, Task<bool>> _execute; private readonly Action<LogStream, string> _log; private readonly Lock _gate = new(); private readonly List<Task<bool>> _executions = []; private bool _disposed; public ResourceLifecycleAuthority(string phase, ImmutableDictionary<string, string> values, Func<WorkloadPlan, CancellationToken, Task<bool>> execute, Action<LogStream, string> log, CancellationToken cancellationToken) { Phase = phase; _values = values; _execute = execute; _log = log; _lifetime = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); _token = _lifetime.Token; } public string Phase { get; } public bool IsRevoked => _token.IsCancellationRequested; public void AppendLog(LogStream stream, string message) { _token.ThrowIfCancellationRequested(); _log(stream, message); } public Task<bool> RunExecutable(ExecutableWorkload template, SymbolicWorkloadBindings bindings) { _token.ThrowIfCancellationRequested(); bindings.Validate(template); string Resolve(string key) => _values.TryGetValue(key, out var value) ? value : throw new InvalidOperationException("The lifecycle field is unavailable in this phase."); var environment = template.Environment.AddRange(bindings.Environment.Select(binding => new WorkloadEnvironment(binding.Name, Resolve(binding.ConfigurationKey)))); var arguments = template.Arguments.ToBuilder(); foreach (var binding in bindings.Arguments) { arguments[binding.Index] = Resolve(binding.ConfigurationKey); } var plan = SymbolicWorkloadBindings.Copy(template, environment, arguments.ToImmutable()); WorkloadDefinition.Validate(plan); lock (_gate) { _token.ThrowIfCancellationRequested(); var execution = _execute(plan, _token); _executions.Add(execution); _ = execution.ContinueWith(static completed => _ = completed.Exception, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); return execution; } } public async Task DrainAsync() { Task<bool>[] executions; lock (_gate) { executions = _executions.ToArray(); } try { await Task.WhenAll(executions).ConfigureAwait(false); } catch (OperationCanceledException) when (IsRevoked) { // Cancellation revokes the phase, but all native execution cleanup // must finish before its provider-local object can be disposed. } } public void Revoke() { lock (_gate) { if (!_disposed) { _lifetime.Cancel(); } } } public void Dispose() { lock (_gate) { if (_disposed) { return; } _disposed = true; try { _lifetime.Cancel(); } finally { _lifetime.Dispose(); } } } }