Skip to content

Fix Windows test shutdown file lock failures - #20406

Open
David Negstad (danegsta) wants to merge 4 commits into
mainfrom
danegsta-fix-test-shutdown-file-locks
Open

David Negstad (danegsta) wants to merge 4 commits into
mainfrom
danegsta-fix-test-shutdown-file-locks

Conversation

@danegsta

@danegsta David Negstad (danegsta) commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Description

Windows test runs can take long enough to stop DCP-managed child processes that a timeout shared with startup expires during teardown. DCP diagnostic forwarding also ran before the orchestrator stopped those processes, so the diagnostic files could still be exclusively locked when the test tried to read them.

This change stops reusing startup and resource-operation cancellation tokens during teardown. Standard repository test builders and TestProgram fixtures now preserve Generic Host's 30-second local shutdown budget while allowing 45 seconds on slower CI machines. DCP diagnostic forwarding runs from IHostedLifecycleService.StoppedAsync, after all hosted services complete StopAsync, so Windows processes have released their diagnostic files before the tests read them.

A focused regression test holds a DCP log open during StopAsync and verifies that it is forwarded only during StoppedAsync.

Validation:

  • DcpLogForwarderReadsLogsAfterHostedServicesStop
  • BeforeResourceStartedEvent_NotFiredForExplicitStartOnInitialCreation
  • BeforeResourceStartedEvent_FiredWhenExplicitStartResourceIsManuallyStarted
  • BeforeResourceStartedEvent_FiredForNormalResourcesOnInitialStartup
  • VerifyWithHttpHealthCheckBlocksDependentResources
  • ParentProcessLifetimeReusesResourcesAcrossAppRestartsAndStopsWhenParentExits

Fixes #15223

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

Use an independent teardown timeout for explicit-start tests and forward DCP diagnostics only after hosted services have stopped.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 20406

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 20406"

@github-actions github-actions Bot added the area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication label Sep 23, 2026
@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

All reviewed changes are covered with no unresolved issues.

Review effort: Balanced
Findings: None

What changed in this PR

Fixes Windows test shutdown failures caused by DCP diagnostic file locks and shared timeout budgets.

Changes:

  • Moves DCP log forwarding to StoppedAsync.
  • Adds independent teardown timeouts.
  • Adds locked-file regression coverage.
File Description
tests/​Shared/​DistributedApplicationTestingBuilderExtensions.cs Defers DCP log forwarding until hosted services stop.
tests/​Aspire.Hosting.Tests/​DistributedApplicationTests.cs Adds independent shutdown timeouts.
tests/​Aspire.Hosting.Testing.Tests/​TestingBuilderTests.cs Verifies forwarding after file locks are released.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/Aspire.Hosting.Testing.Tests/TestingBuilderTests.cs Outdated
Comment on lines +413 to +414
using var shutdownCts = AsyncTestHelpers.CreateDefaultTimeoutTokenSource(TestConstants.DefaultOrchestratorTestLongTimeout);
await app.StopAsync(shutdownCts.Token).DefaultTimeout(TestConstants.DefaultOrchestratorTestLongTimeout);

@JamesNK James Newton-King (JamesNK) Sep 24, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? Why in only a couple of tests? Every test is calling StopAsync. What about setting a stop timeout on the host instead when programs are created? In other words, look for a way to make this automatically applied in every test.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to apply this more universally for anything using TestDistributedApplicationBuilder.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The regression test does not validate host lifecycle ordering, and one affected teardown still uses the shorter timeout.

Review effort: Balanced
Findings: None

@github-actions

This comment has been minimized.

Configure a dedicated CI-scaled host shutdown budget and avoid reusing startup cancellation tokens during teardown.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Teardown timeout races and an uncovered expired-token shutdown path remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs Outdated
@github-actions

This comment has been minimized.

Comment thread tests/Shared/AsyncTestHelpers.cs Outdated
public static readonly int ExtraLongTimeoutDuration = 60 * 1000 * 3 * (PlatformDetection.IsRunningOnCI ? 2 : 1); // 180 sec, 360 sec in CI -- useful when a docker image might need pulling
public static readonly int DefaultOrchestratorTestTimeout = 15 * 1000 * (PlatformDetection.IsRunningOnCI ? 2 : 1); // 15 sec, 30 sec in CI
public static readonly int DefaultOrchestratorTestLongTimeout = 45 * 1000 * (PlatformDetection.IsRunningOnCI ? 4 : 1); // 45 sec, 180 sec in CI
public static readonly int DefaultOrchestratorTestShutdownTimeout = 15 * 1000 * (PlatformDetection.IsRunningOnCI ? 3 : 1); // 15 sec, 45 sec in CI

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just use DefaultOrchestratorTestTimeout. Make it 45 sec in CI if required.

Want to avoid a lot of slightly different timeouts.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this into a deeper shared layer so it can apply more consistently, but it also makes using the shared constants harder. I think the current approach is a reasonable tradeoff.

Comment on lines +2449 to +2450
testProgram.AppBuilder.Services.Configure<HostOptions>(options =>
options.ShutdownTimeout = TimeSpan.FromMilliseconds(TestConstants.DefaultOrchestratorTestShutdownTimeout));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this actually fix the problem though? I was just throwing it out as an idea.

An error gets thrown when it hits the timeout, right? Won't that fail the test. These timeouts exist so tests don't hang, not to make them non-flaky.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If tests are taking 30 seconds to shutdown it sounds like that is the problem that should be addressed, not making timeouts longer.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We haven't touched DCP shutdown in quite some time; this is very likely CI contention. We can independently spend cycles looking if we can squeeze more shutdown performance out of DCP, but in general there's a floor dictated by our graceful shutdown attempts. Given that I've seen the affected tests failing itermitantly across several PRs recently, I'd argue bumping our shutdown timeout is a reasonable approach if it increases how often tests pass on the first run.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, that separate DCP investigation turned up an interesting candidate; basically under load the underlying k8s API server we're using can switch the order events are delivered. So the tests are potentially hanging because we never actually see that a resource was deleted: microsoft/dcp#275

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

Tests selector

40 / 99 PR test projects · 0 PR jobs, from 7 changed files.

Selected PR test projects (40 / 99)

Aspire.Hosting.Azure.Kubernetes.Tests, Aspire.Hosting.Azure.Kusto.Tests, Aspire.Hosting.Azure.Tests, Aspire.Hosting.Blazor.Tests, Aspire.Hosting.Browsers.Tests, Aspire.Hosting.Containers.Tests, Aspire.Hosting.DevTunnels.Tests, Aspire.Hosting.Docker.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Hosting.DotnetTool.Tests, Aspire.Hosting.EntityFrameworkCore.Tests, Aspire.Hosting.Foundry.Tests, Aspire.Hosting.Garnet.Tests, Aspire.Hosting.Go.Tests, Aspire.Hosting.Java.Tests, Aspire.Hosting.JavaScript.Tests, Aspire.Hosting.Kafka.Tests, Aspire.Hosting.Keycloak.Tests, Aspire.Hosting.Kubernetes.Tests, Aspire.Hosting.Maui.Tests, Aspire.Hosting.Milvus.Tests, Aspire.Hosting.MongoDB.Tests, Aspire.Hosting.MySql.Tests, Aspire.Hosting.Nats.Tests, Aspire.Hosting.OpenAI.Tests, Aspire.Hosting.Oracle.Tests, Aspire.Hosting.Orleans.Tests, Aspire.Hosting.PostgreSQL.Tests, Aspire.Hosting.Python.Tests, Aspire.Hosting.Qdrant.Tests, Aspire.Hosting.RabbitMQ.Tests, Aspire.Hosting.Radius.Tests, Aspire.Hosting.Redis.Tests, Aspire.Hosting.Rust.Tests, Aspire.Hosting.Seq.Tests, Aspire.Hosting.SqlServer.Tests, Aspire.Hosting.Testing.Tests, Aspire.Hosting.Tests, Aspire.Hosting.Valkey.Tests, Aspire.Hosting.Yarp.Tests

Selected PR jobs (0)

none


How these were chosen — grouped by what changed

⚠️ 34 of the 40 selected test projects come from a single change — tests/Aspire.Hosting.TestUtilities/Utils/TestDistributedApplicationBuilder.cs.

🧪 tests/Aspire.Hosting.TestUtilities/Utils/TestDistributedApplicationBuilder.cs (changed test)
→ 34 via the project graph

show 34

Aspire.Hosting.Azure.Kubernetes.Tests, Aspire.Hosting.Azure.Kusto.Tests, Aspire.Hosting.Browsers.Tests, Aspire.Hosting.Containers.Tests, Aspire.Hosting.DevTunnels.Tests, Aspire.Hosting.Docker.Tests, Aspire.Hosting.DotnetTool.Tests, Aspire.Hosting.EntityFrameworkCore.Tests, Aspire.Hosting.Foundry.Tests, Aspire.Hosting.Garnet.Tests, Aspire.Hosting.Go.Tests, Aspire.Hosting.Java.Tests, Aspire.Hosting.JavaScript.Tests, Aspire.Hosting.Kafka.Tests, Aspire.Hosting.Keycloak.Tests, Aspire.Hosting.Kubernetes.Tests, Aspire.Hosting.Maui.Tests, Aspire.Hosting.Milvus.Tests, Aspire.Hosting.MongoDB.Tests, Aspire.Hosting.MySql.Tests, Aspire.Hosting.Nats.Tests, Aspire.Hosting.OpenAI.Tests, Aspire.Hosting.Oracle.Tests, Aspire.Hosting.Orleans.Tests, Aspire.Hosting.PostgreSQL.Tests, Aspire.Hosting.Python.Tests, Aspire.Hosting.Qdrant.Tests, Aspire.Hosting.RabbitMQ.Tests, Aspire.Hosting.Redis.Tests, Aspire.Hosting.Rust.Tests, Aspire.Hosting.Seq.Tests, Aspire.Hosting.SqlServer.Tests, Aspire.Hosting.Valkey.Tests, Aspire.Hosting.Yarp.Tests

🧪 tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs (changed test)
→ 1 directly: Aspire.Hosting.Tests
→ 4 via the project graph: Aspire.Hosting.Azure.Tests, Aspire.Hosting.Blazor.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Hosting.Radius.Tests

🧪 tests/Aspire.Hosting.Testing.Tests/Aspire.Hosting.Testing.Tests.csproj (changed test)
→ 1 directly: Aspire.Hosting.Testing.Tests

🧪 tests/Aspire.Hosting.Testing.Tests/TestingBuilderTests.cs (changed test)
→ 1 directly: Aspire.Hosting.Testing.Tests

🧪 tests/Aspire.Hosting.Tests/HealthCheckTests.cs (changed test)
→ 1 directly: Aspire.Hosting.Tests

🧪 tests/Aspire.Hosting.Tests/TestProgramFixture.cs (changed test)
→ 1 directly: Aspire.Hosting.Tests

Job reasons

none


Selection computed for commit f30f84d.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

It has a compilation failure and unresolved shutdown-token and timeout-coverage issues.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (1)

Comment thread tests/Shared/DistributedApplicationTestingBuilderExtensions.cs
public static class DistributedApplicationTestingBuilderExtensions
{
private static readonly TimeSpan s_defaultHostShutdownTimeout =
TimeSpan.FromSeconds(PlatformDetection.IsRunningOnCI ? 45 : 30); // 30 sec, 45 sec in CI

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just set to TestConstants.LongTimeoutTimeSpan

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@danegsta

Copy link
Copy Markdown
Member Author

This may actually be a weird bug in our k8s API Server (see microsoft/dcp#275)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Aspire.Hosting.Tests.DistributedApplicationTests.BeforeResourceStartedEvent_FiredForNormalResourcesOnInitialStartup

3 participants