Add stop --force persistent resource cleanup#18718
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 18718Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 18718" |
There was a problem hiding this comment.
Pull request overview
Adds aspire stop --force to stop an AppHost and relaunch it in DCP cleanup mode for persistent resources.
Changes:
- Adds CLI cleanup launch, version gating, warnings, and guest propagation.
- Adds DCP persistent/cleanup lifecycle modes.
- Adds unit and end-to-end coverage.
Reviewed changes
Copilot reviewed 34 out of 35 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
tests/Aspire.Hosting.Tests/Dcp/ResourceSnapshotBuilderTests.cs |
Updates lifecycle snapshot coverage. |
tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs |
Tests persistent and cleanup modes. |
tests/Aspire.Hosting.Tests/Dcp/ConfigureDefaultDcpOptionsTests.cs |
Tests cleanup configuration binding. |
tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs |
Registers the cleanup launcher. |
tests/Aspire.Cli.Tests/Commands/StopCommandTests.cs |
Tests stop --force behavior. |
tests/Aspire.Cli.EndToEnd.Tests/StopForceTests.cs |
Verifies persistent container removal. |
src/Shared/KnownConfigNames.cs |
Defines the cleanup configuration key. |
src/Aspire.Hosting/Dcp/ResourceSnapshotBuilder.cs |
Reads the new lifecycle mode. |
src/Aspire.Hosting/Dcp/Model/ModelCommon.cs |
Defines lifecycle mode values. |
src/Aspire.Hosting/Dcp/Model/Executable.cs |
Adds executable lifecycle mode. |
src/Aspire.Hosting/Dcp/Model/ContainerNetwork.cs |
Adds network lifecycle mode. |
src/Aspire.Hosting/Dcp/Model/Container.cs |
Adds container lifecycle mode. |
src/Aspire.Hosting/Dcp/ExecutableCreator.cs |
Emits cleanup executable specifications. |
src/Aspire.Hosting/Dcp/DcpVersion.cs |
Raises the minimum DCP version. |
src/Aspire.Hosting/Dcp/DcpOptions.cs |
Configures and validates cleanup mode. |
src/Aspire.Hosting/Dcp/ContainerCreator.cs |
Emits cleanup container specifications. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.zh-Hant.xlf |
Updates Traditional Chinese localization data. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.zh-Hans.xlf |
Updates Simplified Chinese localization data. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.tr.xlf |
Updates Turkish localization data. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.ru.xlf |
Updates Russian localization data. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.pt-BR.xlf |
Updates Portuguese localization data. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.pl.xlf |
Updates Polish localization data. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.ko.xlf |
Updates Korean localization data. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.ja.xlf |
Updates Japanese localization data. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.it.xlf |
Updates Italian localization data. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.fr.xlf |
Updates French localization data. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.es.xlf |
Updates Spanish localization data. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.de.xlf |
Updates German localization data. |
src/Aspire.Cli/Resources/xlf/StopCommandStrings.cs.xlf |
Updates Czech localization data. |
src/Aspire.Cli/Resources/StopCommandStrings.resx |
Adds force-cleanup messages. |
src/Aspire.Cli/Resources/StopCommandStrings.Designer.cs |
Exposes the new resources. |
src/Aspire.Cli/Projects/GuestAppHostProject.cs |
Propagates cleanup mode to guests. |
src/Aspire.Cli/Program.cs |
Registers the cleanup launcher. |
src/Aspire.Cli/Commands/StopCommand.cs |
Implements stop --force. |
src/Aspire.Cli/Commands/AppHostCleanupLauncher.cs |
Launches and coordinates cleanup mode. |
Files not reviewed (1)
- src/Aspire.Cli/Resources/StopCommandStrings.Designer.cs: Generated file
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Treat failures while inspecting the AppHost version as an unknown version so a successful stop --force does not become a failed command before cleanup. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Converting back to draft while I fix a timing issue with running the cleanup command while DCP is doing graceful shutdown. |
…esource-cleanup-mode
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- src/Aspire.Cli/Resources/StopCommandStrings.Designer.cs: Generated file
Comments suppressed due to low confidence (2)
src/Aspire.Cli/Projects/DotNetAppHostProject.cs:548
- This value is not authoritative on the
dotnet runfallback paths (watch, single-file, extension-host, multi-targeted, or direct-launch-disabled AppHosts).dotnet runreapplies launch-profileenvironmentVariablesto the child process, overriding inherited values, so a profile containingDCP_WORKLOAD_IDrecords resources under that value whilestop --forcecomputes the stable ID and reports successful cleanup of zero matches. Preserve the profile inputs but reapply this reserved value after profile merging for every launch path.
env[KnownConfigNames.DcpWorkloadId] = AppHostWorkloadId.Create(effectiveAppHostFile);
src/Aspire.Cli/Commands/DcpWorkloadCleanupService.cs:65
- Bundle acquisition calls
FileLock.AcquireAsync, which throwsTimeoutExceptionafter its five-minute contention timeout. That expected failure is not included in this filter, so it escapes the cleanup-specific result handling and is reported byBaseCommandas an unexpected command failure instead ofDcpCleanupFailed. IncludeTimeoutExceptionin the cleanup failure filter.
catch (Exception ex) when (ex is InvalidOperationException or Win32Exception or IOException or UnauthorizedAccessException)
PR Testing ReportPR Information
Artifact Version Verification
Changes AnalyzedFiles Changed
Change Categories
Test Scenarios ExecutedScenario 1: Foreground run cleanup raceObjective: Verify official DCP Steps:
Evidence:
Observations:
Scenario 2: Detached start cleanup happy pathObjective: Verify Steps:
Evidence:
Observations:
Scenario 3: Cleanup after AppHost is already stoppedObjective: Verify Steps:
Evidence:
Observations:
Scenario 4: Symlinked parent-directory AppHost pathObjective: Verify workload identity is stable when the AppHost is started through a symlinked parent directory and force-stopped using the real parent directory path. Steps:
Evidence:
Observations:
Scenario 5: Invalid
|
| Scenario | Status | Notes |
|---|---|---|
| Foreground run cleanup race | Passed | Official DCP 0.25.8 cleaned one persistent container and one network immediately after foreground run stop. |
| Detached start cleanup happy path | Passed | Persistent container removed after detached start. |
| Already-stopped AppHost cleanup | Passed | Cleanup proceeded after “No AppHost is currently running.” |
| Symlinked parent-directory AppHost path | Passed | Start via symlink path and cleanup via real path succeeded. |
Invalid --force --all option combination |
Passed | Non-zero exit with clear validation error. |
Overall Result
PR VERIFIED
All approved local smoke scenarios passed against PR dogfood CLI 13.5.0-pr.18718.gb8ceb485 and official bundled DCP 0.25.8. The previous foreground-run cleanup race did not reproduce; DCP cleanup reported successful removal of the persistent container and network.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fa0e5f04-5e48-4592-8d8c-d20e961cbadb
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- src/Aspire.Cli/Resources/StopCommandStrings.Designer.cs: Generated file
Comments suppressed due to low confidence (1)
src/Aspire.Cli/Projects/DotNetAppHostProject.cs:547
- This value is not authoritative on fallback
dotnet runpaths (watch mode, extension hosts, disabled direct launch, or unsupported launch profiles).dotnet runapplieslaunchSettings.jsonenvironment variables after inherited process variables, so a profile containingDCP_WORKLOAD_IDoverrides this stable ID. DCP then records resources under the profile value whilestop --forcecomputes a different ID and reports successful cleanup of zero resources. Preserve the other launch-profile inputs while ensuring this internal key cannot be overridden; the added conflicting-profile test currently exercises only direct launch.
env[KnownConfigNames.DcpWorkloadId] = AppHostWorkloadId.Create(effectiveAppHostFile);
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fa0e5f04-5e48-4592-8d8c-d20e961cbadb
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- src/Aspire.Cli/Resources/StopCommandStrings.Designer.cs: Generated file
Comments suppressed due to low confidence (2)
src/Aspire.Cli/Commands/StopCommand.cs:123
- This resolves and validates an explicit AppHost before performing the normal stop. Unlike the existing socket fast path,
ProjectLocatorrunsValidateAppHostAsync; if a running .NET AppHost becomes unbuildable after launch, resolution returns no project and--forcenever stops it. Preserve the normal file/socket-first stop behavior, then use the stopped connection path for cleanup (falling back to project discovery only when nothing is running).
var appHostFile = passedAppHostProjectFile is not null
? await TryResolveAppHostFileAsync(passedAppHostProjectFile, cancellationToken).ConfigureAwait(false)
: null;
src/Aspire.Cli/Projects/DotNetAppHostProject.cs:547
- The workload ID is only added to the environment of the
dotnet runprocess. On fallback paths, the .NET SDK subsequently applies launch-profile environment variables to the AppHost command, so a profile containingDCP_WORKLOAD_IDoverrides this computed value. DCP then records resources under the profile value andstop --forcecleans the computed value, leaving resources behind. Pass this internal value through the SDK's authoritative runtime-environment mechanism while preserving the other launch-profile inputs.
env[KnownConfigNames.DcpWorkloadId] = AppHostWorkloadId.Create(effectiveAppHostFile);
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fa0e5f04-5e48-4592-8d8c-d20e961cbadb Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- src/Aspire.Cli/Resources/StopCommandStrings.Designer.cs: Generated file
Comments suppressed due to low confidence (1)
src/Aspire.Cli/Projects/DotNetAppHostProject.cs:547
- This value is authoritative only on the direct-launch path, where
TryCreateDirectRunSpecAsyncoverlaysenvafter launch-profile variables. On any fallback to_runner.RunAsync(including watch and executable profiles),NoLaunchProfileis false anddotnet runreapplieslaunchSettings.jsonvariables to the AppHost process. A profile containingDCP_WORKLOAD_IDtherefore overrides this computed ID, so DCP records resources under a different workload andstop --forcecan report success without cleaning them. Ensure the fallback launch gives the computed workload ID precedence over profile values, and cover a fallback profile containing a conflicting value.
env[KnownConfigNames.DcpWorkloadId] = AppHostWorkloadId.Create(effectiveAppHostFile);
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fa0e5f04-5e48-4592-8d8c-d20e961cbadb
Tests selector (audit mode)The full test matrix and all jobs still run in audit mode. The tests and jobs below are what selective CI would run under enforcement. 54 / 100 test projects · 5 jobs, from 30 changed files. Selected test projects (54 / 100)
Selected jobs (5)
How these were chosen — grouped by what changed
🔧 show 47
🧪 📦 affected project 📦 affected project 🔧 🧪 🧪 🧪 🧪 🧪 🧪 🧪 Job reasons
Selection computed for commit |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- src/Aspire.Cli/Resources/StopCommandStrings.Designer.cs: Generated file
Comments suppressed due to low confidence (3)
src/Aspire.Cli/Commands/StopCommand.cs:299
- The AppHost path is already known from the selected connection, but this branch discards it when the socket disappears between resolution and this second lookup. In the
--forceflow that triggers fresh project discovery, which can clean a different local AppHost workload (or fail) instead of the one the user selected. PreserveappHostFileeven when there is nothing left to stop.
return new StopAppHostResult(CliExitCodes.Success, null);
src/Aspire.Cli/Commands/StopCommand.cs:320
- A successfully resolved AppHost can disappear or fail the redundant monitor scan before this point. Returning
nullloses that selected path, sostop --forcefalls back to project discovery and may clean the wrong workload. Return the knownappHostFilehere just as on a successful stop.
return new StopAppHostResult(CliExitCodes.Success, null);
src/Aspire.Cli/Projects/DotNetAppHostProject.cs:547
- This environment assignment is not authoritative on the
dotnet runfallback path.dotnet runapplies launch-profile environment variables to the AppHost child after inheriting this environment, so a profile containingDCP_WORKLOAD_IDoverrides the stable ID; direct launch explicitly avoids that by mergingenvafter the profile. In fallback scenarios such as watch mode or disabled/unsupported direct launch,stop --forcethen cleans the computed ID, finds no resources, and reports success. Ensure the internal workload ID is reapplied after launch-profile processing for every launch path.
env[KnownConfigNames.DcpWorkloadId] = AppHostWorkloadId.Create(effectiveAppHostFile);
PR Testing ReportPR Information
Artifact Version Verification
Changes AnalyzedFiles Changed
Change Categories
Test Scenarios ExecutedScenario 1: Focused source validationObjective: Verify focused unit coverage for the changed CLI stop, workload ID, and AppHost launch environment behavior. Steps:
Evidence:
Observations:
Scenario 2: Dogfood artifact verificationObjective: Verify the tested CLI came from PR #18718 and matches the latest PR head. Steps:
Evidence:
Observations:
Scenario 3: Persistent cleanup happy pathObjective: Verify Steps:
Evidence:
Observations:
Scenario 4: Already-stopped cleanup fallbackObjective: Verify Steps:
Evidence:
Expected Unhappy-Path Outcome: The command should treat the not-running AppHost as nonfatal when Observations:
Scenario 5: Same-AppHost multi-instance shutdownObjective: Verify one Steps:
Evidence:
Expected Boundary Outcome: The selected AppHost path maps to both matching sockets, both processes are stopped, and a follow-up stop reports no running AppHost for that path. Observations:
Scenario 6: Invalid option boundaryObjective: Verify Steps:
Evidence:
Expected Unhappy-Path Outcome: The command should fail safely and not attempt an all-AppHosts persistent cleanup. Observations:
Summary
Overall ResultPR VERIFIED The PR dogfood CLI at head |
Description
This adds
aspire stop --forcesupport for cleaning up persistent DCP resources for an AppHost without relaunching the AppHost.User-facing usage
Users can stop the selected AppHost and clean up its persistent resources with:
The command first performs the normal AppHost stop flow. It then computes the stable workload ID for the AppHost and invokes DCP cleanup for that workload.
Implementation notes
The CLI now stamps
DCP_WORKLOAD_IDwhen launching .NET and guest AppHosts. The workload ID is derived from the AppHost path after symlink resolution, with Windows-only path lowercasing to preserve Windows path casing behavior without collapsing distinct case-sensitive paths on macOS or Linux.aspire stop --forcecomputes the same workload ID and runsdcp cleanup <workloadId>using the CLI bundle when available, or the discovered Aspire layout DCP otherwise. The command does not relaunch the AppHost to discover resources.For .NET AppHosts, cleanup compatibility is checked from AppHost metadata. AppHosts using the Aspire CLI bundle are treated as compatible. Non-bundle AppHosts on Aspire.Hosting 13.5 or later are also treated as compatible. If the AppHost version is older, missing, or cannot be inspected, the CLI emits a warning and still attempts DCP cleanup for the workload ID.
Validation
Fixes #17324
Fixes #9363
Checklist
<remarks />and<code />elements on your triple slash comments?