| File: Publishing\ContainerRuntimeReadiness.cs | Web Access |
| Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #pragma warning disable ASPIRECONTAINERRUNTIME001 using Microsoft.Extensions.DependencyInjection; namespace Aspire.Hosting.Publishing; /// <summary> /// Coordinates interactive container runtime readiness checks for pipeline builds. /// </summary> internal sealed class ContainerRuntimeReadiness(IServiceProvider services) { private readonly object _lock = new(); private readonly Dictionary<IContainerRuntime, Task> _checks = new(ReferenceEqualityComparer.Instance); public Task EnsureRunningAsync(IContainerRuntime containerRuntime, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); lock (_lock) { // Share an in-flight prompt across concurrent build steps, but do not cache completed // checks: the runtime can stop or recover before a later build or retry. if (!_checks.TryGetValue(containerRuntime, out var check) || check.IsCompleted) { check = EnsureRunningCoreAsync(containerRuntime, cancellationToken); _checks[containerRuntime] = check; } return check.WaitAsync(cancellationToken); } } private async Task EnsureRunningCoreAsync(IContainerRuntime containerRuntime, CancellationToken cancellationToken) { if (await containerRuntime.CheckIfRunningAsync(cancellationToken).ConfigureAwait(false)) { return; } var interactionService = services.GetService<IInteractionService>(); if (interactionService?.IsAvailable == true) { var result = await interactionService.PromptNotificationAsync( $"{containerRuntime.Name} is not running", $"Start {containerRuntime.Name} and confirm to continue.", new NotificationInteractionOptions { Intent = MessageIntent.Warning }, cancellationToken: cancellationToken).ConfigureAwait(false); if (!result.Canceled && result.Data) { var timeProvider = services.GetRequiredService<TimeProvider>(); using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(5), timeProvider); using var waitCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); try { // Starting Docker can take a while even after the user confirms. while (!await containerRuntime.CheckIfRunningAsync(waitCts.Token).ConfigureAwait(false)) { await Task.Delay(TimeSpan.FromSeconds(2), timeProvider, waitCts.Token).ConfigureAwait(false); } return; } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { // Timed out waiting for startup; report the same actionable failure below. } } } throw new DistributedApplicationException( $"{containerRuntime.Name} is not running. Start {containerRuntime.Name} and try again."); } }