Skip to content

Fix CTRL-C stall issue. #10962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions src/Aspire.Cli/Commands/BaseCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using System.Diagnostics;
using Aspire.Cli.Configuration;
using Aspire.Cli.Utils;

Expand All @@ -24,16 +23,7 @@ protected BaseCommand(string name, string description, IFeatures features, ICliU
{
try
{
var currentDirectory = new DirectoryInfo(Environment.CurrentDirectory);

// We use a separate CTS here because we want this check to run even if we've got a cancellation,
// but we'll only wait so long before we get details back about updates
// being available (it should already be in the cache for longer running
// commands and some commands will opt out entirely)
var cts = !Debugger.IsAttached
? new CancellationTokenSource(TimeSpan.FromSeconds(10))
: new CancellationTokenSource();
await updateNotifier.NotifyIfUpdateAvailableAsync(currentDirectory, cancellationToken: cts.Token);
updateNotifier.NotifyIfUpdateAvailable();
}
catch
{
Expand Down
7 changes: 3 additions & 4 deletions src/Aspire.Cli/NuGet/NuGetPackagePrefetcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@

using Aspire.Cli.Configuration;
using Aspire.Cli.Packaging;
using Aspire.Cli.Utils;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Aspire.Cli.NuGet;

internal sealed class NuGetPackagePrefetcher(ILogger<NuGetPackagePrefetcher> logger, INuGetPackageCache nuGetPackageCache, CliExecutionContext executionContext, IFeatures features, IPackagingService packagingService) : BackgroundService
internal sealed class NuGetPackagePrefetcher(ILogger<NuGetPackagePrefetcher> logger, CliExecutionContext executionContext, IFeatures features, IPackagingService packagingService, ICliUpdateNotifier cliUpdateNotifier) : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
Expand Down Expand Up @@ -41,10 +42,8 @@ await Parallel.ForEachAsync(channels, stoppingToken, async (channel, ct) =>
{
try
{
await nuGetPackageCache.GetCliPackagesAsync(
await cliUpdateNotifier.CheckForCliUpdatesAsync(
workingDirectory: executionContext.WorkingDirectory,
prerelease: true,
nugetConfigFile: null,
cancellationToken: stoppingToken
);
}
Expand Down
25 changes: 2 additions & 23 deletions src/Aspire.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private static async Task<IHost> BuildApplicationAsync(string[] args)
builder.Services.AddSingleton(BuildCliExecutionContext);
builder.Services.AddSingleton(BuildAnsiConsole);
AddInteractionServices(builder);
builder.Services.AddSingleton(BuildProjectLocator);
builder.Services.AddSingleton<IProjectLocator, ProjectLocator>();
builder.Services.AddSingleton<INewCommandPrompter, NewCommandPrompter>();
builder.Services.AddSingleton<IAddCommandPrompter, AddCommandPrompter>();
builder.Services.AddSingleton<IPublishCommandPrompter, PublishCommandPrompter>();
Expand All @@ -119,7 +119,7 @@ private static async Task<IHost> BuildApplicationAsync(string[] args)
builder.Services.AddSingleton<IDotNetSdkInstaller, DotNetSdkInstaller>();
builder.Services.AddTransient<IAppHostBackchannel, AppHostBackchannel>();
builder.Services.AddSingleton<INuGetPackageCache, NuGetPackageCache>();
builder.Services.AddHostedService(BuildNuGetPackagePrefetcher);
builder.Services.AddHostedService<NuGetPackagePrefetcher>();
builder.Services.AddSingleton<ICliUpdateNotifier, CliUpdateNotifier>();
builder.Services.AddSingleton<IPackagingService, PackagingService>();
builder.Services.AddMemoryCache();
Expand Down Expand Up @@ -190,16 +190,6 @@ private static IConfigurationService BuildConfigurationService(IServiceProvider
return new ConfigurationService(configuration, executionContext, globalSettingsFile);
}

private static NuGetPackagePrefetcher BuildNuGetPackagePrefetcher(IServiceProvider serviceProvider)
{
var logger = serviceProvider.GetRequiredService<ILogger<NuGetPackagePrefetcher>>();
var nuGetPackageCache = serviceProvider.GetRequiredService<INuGetPackageCache>();
var executionContext = serviceProvider.GetRequiredService<CliExecutionContext>();
var features = serviceProvider.GetRequiredService<IFeatures>();
var packagingService = serviceProvider.GetRequiredService<IPackagingService>();
return new NuGetPackagePrefetcher(logger, nuGetPackageCache, executionContext, features, packagingService);
}

private static IAnsiConsole BuildAnsiConsole(IServiceProvider serviceProvider)
{
AnsiConsoleSettings settings = new AnsiConsoleSettings()
Expand All @@ -212,17 +202,6 @@ private static IAnsiConsole BuildAnsiConsole(IServiceProvider serviceProvider)
return ansiConsole;
}

private static IProjectLocator BuildProjectLocator(IServiceProvider serviceProvider)
{
var logger = serviceProvider.GetRequiredService<ILogger<ProjectLocator>>();
var runner = serviceProvider.GetRequiredService<IDotNetCliRunner>();
var executionContext = serviceProvider.GetRequiredService<CliExecutionContext>();
var interactionService = serviceProvider.GetRequiredService<IInteractionService>();
var configurationService = serviceProvider.GetRequiredService<IConfigurationService>();
var telemetry = serviceProvider.GetRequiredService<AspireCliTelemetry>();
return new ProjectLocator(logger, runner, executionContext, interactionService, configurationService, telemetry);
}

public static async Task<int> Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
Expand Down
51 changes: 27 additions & 24 deletions src/Aspire.Cli/Utils/CliUpdateNotifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,45 @@ namespace Aspire.Cli.Utils;

internal interface ICliUpdateNotifier
{
Task NotifyIfUpdateAvailableAsync(DirectoryInfo workingDirectory, CancellationToken cancellationToken = default);
Task CheckForCliUpdatesAsync(DirectoryInfo workingDirectory, CancellationToken cancellationToken);
void NotifyIfUpdateAvailable();
}

internal class CliUpdateNotifier(
ILogger<CliUpdateNotifier> logger,
INuGetPackageCache nuGetPackageCache,
IInteractionService interactionService) : ICliUpdateNotifier
{
private IEnumerable<Shared.NuGetPackageCli>? _availablePackages;

public async Task NotifyIfUpdateAvailableAsync(DirectoryInfo workingDirectory, CancellationToken cancellationToken = default)
public async Task CheckForCliUpdatesAsync(DirectoryInfo workingDirectory, CancellationToken cancellationToken)
{
try
_availablePackages = await nuGetPackageCache.GetCliPackagesAsync(
workingDirectory: workingDirectory,
prerelease: true,
nugetConfigFile: null,
cancellationToken: cancellationToken);
}

public void NotifyIfUpdateAvailable()
{
if (_availablePackages is null)
{
return;
}

var currentVersion = GetCurrentVersion();
if (currentVersion is null)
{
var currentVersion = GetCurrentVersion();
if (currentVersion is null)
{
logger.LogDebug("Unable to determine current CLI version for update check.");
return;
}

var availablePackages = await nuGetPackageCache.GetCliPackagesAsync(
workingDirectory: workingDirectory,
prerelease: true,
nugetConfigFile: null,
cancellationToken: cancellationToken);

var newerVersion = PackageUpdateHelpers.GetNewerVersion(currentVersion, availablePackages);

if (newerVersion is not null)
{
interactionService.DisplayVersionUpdateNotification(newerVersion.ToString());
}
logger.LogDebug("Unable to determine current CLI version for update check.");
return;
}
catch (Exception ex)

var newerVersion = PackageUpdateHelpers.GetNewerVersion(currentVersion, _availablePackages);

if (newerVersion is not null)
{
logger.LogDebug(ex, "Non-fatal error while checking for CLI updates.");
interactionService.DisplayVersionUpdateNotification(newerVersion.ToString());
}
}

Expand Down
18 changes: 12 additions & 6 deletions tests/Aspire.Cli.Tests/Utils/CliUpdateNotificationServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ public async Task PrereleaseWillRecommendUpgradeToPrereleaseOnSameVersionFamily(
var provider = services.BuildServiceProvider();
var notifier = provider.GetRequiredService<ICliUpdateNotifier>();

await notifier.NotifyIfUpdateAvailableAsync(workspace.WorkspaceRoot).WaitAsync(CliTestConstants.DefaultTimeout);
await notifier.CheckForCliUpdatesAsync(workspace.WorkspaceRoot, CancellationToken.None).WaitAsync(CliTestConstants.DefaultTimeout);
notifier.NotifyIfUpdateAvailable();
var suggestedVersion = await suggestedVersionTcs.Task.WaitAsync(CliTestConstants.DefaultTimeout);

Assert.Equal("9.4.0-preview", suggestedVersion);
Expand Down Expand Up @@ -119,7 +120,8 @@ public async Task PrereleaseWillRecommendUpgradeToStableInCurrentVersionFamily()
var provider = services.BuildServiceProvider();
var notifier = provider.GetRequiredService<ICliUpdateNotifier>();

await notifier.NotifyIfUpdateAvailableAsync(workspace.WorkspaceRoot).WaitAsync(CliTestConstants.DefaultTimeout);
await notifier.CheckForCliUpdatesAsync(workspace.WorkspaceRoot, CancellationToken.None).WaitAsync(CliTestConstants.DefaultTimeout);
notifier.NotifyIfUpdateAvailable();
var suggestedVersion = await suggestedVersionTcs.Task.WaitAsync(CliTestConstants.DefaultTimeout);

Assert.Equal("9.4.0", suggestedVersion);
Expand Down Expand Up @@ -173,7 +175,8 @@ public async Task StableWillOnlyRecommendGoingToNewerStable()
var provider = services.BuildServiceProvider();
var notifier = provider.GetRequiredService<ICliUpdateNotifier>();

await notifier.NotifyIfUpdateAvailableAsync(workspace.WorkspaceRoot).WaitAsync(CliTestConstants.DefaultTimeout);
await notifier.CheckForCliUpdatesAsync(workspace.WorkspaceRoot, CancellationToken.None).WaitAsync(CliTestConstants.DefaultTimeout);
notifier.NotifyIfUpdateAvailable();
var suggestedVersion = await suggestedVersionTcs.Task.WaitAsync(CliTestConstants.DefaultTimeout);

Assert.Equal("9.5.0", suggestedVersion);
Expand Down Expand Up @@ -223,7 +226,8 @@ public async Task StableWillNotRecommendUpdatingToPreview()
var provider = services.BuildServiceProvider();
var notifier = provider.GetRequiredService<ICliUpdateNotifier>();

await notifier.NotifyIfUpdateAvailableAsync(workspace.WorkspaceRoot).WaitAsync(CliTestConstants.DefaultTimeout);
await notifier.CheckForCliUpdatesAsync(workspace.WorkspaceRoot, CancellationToken.None).WaitAsync(CliTestConstants.DefaultTimeout);
notifier.NotifyIfUpdateAvailable();
}

[Fact]
Expand All @@ -247,7 +251,8 @@ public async Task NotifyIfUpdateAvailableAsync_WithNewerStableVersion_DoesNotThr
]);

// Act & Assert (should not throw)
await service.NotifyIfUpdateAvailableAsync(workspace.WorkspaceRoot);
await service.CheckForCliUpdatesAsync(workspace.WorkspaceRoot, CancellationToken.None);
service.NotifyIfUpdateAvailable();
}

[Fact]
Expand All @@ -265,7 +270,8 @@ public async Task NotifyIfUpdateAvailableAsync_WithEmptyPackages_DoesNotThrow()
var service = provider.GetRequiredService<ICliUpdateNotifier>();

// Act & Assert (should not throw)
await service.NotifyIfUpdateAvailableAsync(workspace.WorkspaceRoot);
await service.CheckForCliUpdatesAsync(workspace.WorkspaceRoot, CancellationToken.None);
service.NotifyIfUpdateAvailable();
}
}

Expand Down
Loading