Skip to content
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
6 changes: 4 additions & 2 deletions extension/src/test-e2e/edgeCases.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,10 @@ builder.Build().Run();`));
test('process-owner cleanup stops the owned CLI and AppHost process tree', async () => {
await openAspireView();
await waitForRepositoryIdle();
const discovered = await waitForWorkspaceAppHost();
const appHostPath = discovered.state.workspaceAppHostPath ?? getPrimaryAppHostProjectPath();
await waitForWorkspaceAppHost();
// The previous test removes a generated AppHost; discovery can still select it while
// that deletion propagates. This test targets the permanent fixture, not the selection.
const appHostPath = getPrimaryAppHostProjectPath();

const beforeInvocation = getCommandInvocationCount('aspire-vscode.debugAppHost');
await executeE2eControlCommand({ name: 'debugAppHost', appHostPath }, { waitFor: 'started' });
Expand Down
7 changes: 5 additions & 2 deletions src/Aspire.Cli/Processes/WindowsConsoleProcessJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ namespace Aspire.Cli.Processes;
/// <para>
/// The job is also configured with <c>JOB_OBJECT_LIMIT_BREAKAWAY_OK</c> so that DCP (and
/// anything else that needs to outlive the CLI for its own cleanup) can opt out by spawning
/// itself with <c>CREATE_BREAKAWAY_FROM_JOB</c>. Our job only grants permission to break
/// away — the breakaway itself is the responsibility of the spawning code.
/// itself with <c>CREATE_BREAKAWAY_FROM_JOB</c>, provided every nested job also permits
/// breakaway. .NET AppHosts launched through <c>dotnet run</c> do not use this job because
/// its process reaper creates a nested job that does not permit breakaway. Directly launched
/// .NET AppHosts can use it because they have no intervening job. Our job only grants permission
/// to break away — the breakaway itself is the responsibility of the spawning code.
/// </para>
/// <para>
/// IMPORTANT: do NOT dispose this service during the normal shutdown ladder. The graceful
Expand Down
6 changes: 5 additions & 1 deletion src/Aspire.Cli/Projects/DotNetAppHostProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1552,7 +1552,10 @@ void SignalBuildCompletion()
// package add, layout, and other short-lived invocations leave these unset so
// they continue to use the shared ladder's force-kill mode.
IsolateConsole = true,
KillOnParentExit = true,
// dotnet run nests its own non-breakaway job inside the CLI job, preventing DCP
// from escaping the CLI job to finish resource cleanup. Direct launches have no
// intervening job and opt back into the CLI job below.
KillOnParentExit = false,
GracefulShutdownSignaler = _gracefulShutdownSignaler,
ShutdownService = _shutdownService,
// The bundled AppHost run hook delegates dotnet run to aspire run. The SDK passes
Expand Down Expand Up @@ -1616,6 +1619,7 @@ void SignalBuildCompletion()
// caller may reuse runOptions for other invocations.
var directRunOptions = runOptions.Clone();
directRunOptions.AppHostArgumentStartIndex = directRun.AppHostArgumentStartIndex;
directRunOptions.KillOnParentExit = true;

return await _runner.RunAppHostCommandAsync(
effectiveAppHostFile,
Expand Down
18 changes: 18 additions & 0 deletions tests/Aspire.Cli.Tests/Projects/AppHostServerSessionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ public async Task Start_DoesNotMutateCallerEnvironmentVariables()
Assert.Equal(session.AuthenticationToken, project.ReceivedEnvironmentVariables[KnownConfigNames.RemoteAppHostToken]);
}

[Fact]
public async Task Start_WithIsolatedConsole_RequestsKillOnParentExitForAppHostServer()
{
var project = new RecordingAppHostServerProject();

await using var session = CreateSession(
project,
CancellationToken.None,
isolateConsole: true);
await session.StartAsync();

Assert.True(project.ReceivedRunControl?.IsolateConsole);
Assert.True(project.ReceivedRunControl?.KillOnParentExit);
}

[Fact]
public async Task Start_PropagatesProfilingContextToServerEnvironment()
{
Expand Down Expand Up @@ -586,6 +601,8 @@ private sealed class RecordingAppHostServerProject : IAppHostServerProject

public Dictionary<string, string>? ReceivedEnvironmentVariables { get; private set; }

public AppHostServerRunControl? ReceivedRunControl { get; private set; }

public IProcessExecution? StartedExecution { get; private set; }

public string GetInstanceIdentifier() => AppDirectoryPath;
Expand All @@ -608,6 +625,7 @@ public async Task<AppHostServerRunResult> RunAsync(
ReceivedEnvironmentVariables = environmentVariables is null
? null
: new Dictionary<string, string>(environmentVariables);
ReceivedRunControl = runControl;

var startInfo = new ProcessStartInfo("dotnet")
{
Expand Down
4 changes: 4 additions & 0 deletions tests/Aspire.Cli.Tests/Projects/DotNetAppHostProjectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,8 @@ public async Task RunAsync_SingleFileAppHostWithoutRunJsonPassesDevelopmentEnvir
Assert.True(noBuild);
Assert.True(noRestore);
Assert.False(options.NoLaunchProfile);
Assert.True(options.IsolateConsole);
Assert.False(options.KillOnParentExit);
Assert.Equal("Development", env![KnownAspNetCoreConfigNames.DotNetEnvironment]);
Assert.False(env.ContainsKey(KnownAspNetCoreConfigNames.Environment));
Assert.Equal("https://localhost:17193;http://localhost:15069", env[KnownAspNetCoreConfigNames.Urls]);
Expand Down Expand Up @@ -1063,6 +1065,8 @@ public async Task RunAsync_ProjectAppHostUsesDirectCommandLaunchAndAppliesLaunch
Assert.Equal(appHostCommand.FullName, command);
Assert.Equal(runWorkingDirectory.FullName, workingDirectory.FullName);
Assert.False(options.NoLaunchProfile);
Assert.True(options.IsolateConsole);
Assert.True(options.KillOnParentExit);
Assert.Equal(
["--from-msbuild", "two words", "--explicit", "1"],
args);
Expand Down
Loading