Skip to content

Re-enable Microsoft.Extensions.Hosting shutdown functional coverage in CI and harden shutdown startup/Helix/NET481 compatibility#128252

Open
Copilot wants to merge 23 commits into
mainfrom
copilot/enable-functional-tests-on-hosting
Open

Re-enable Microsoft.Extensions.Hosting shutdown functional coverage in CI and harden shutdown startup/Helix/NET481 compatibility#128252
Copilot wants to merge 23 commits into
mainfrom
copilot/enable-functional-tests-on-hosting

Conversation

Copilot AI commented May 15, 2026

Copy link
Copy Markdown
Contributor

main PR

This PR now includes the full follow-up hardening required by CI feedback after re-enabling Microsoft.Extensions.Hosting functional shutdown coverage.

Final scope of changes

  • Re-enabled Hosting FunctionalTests in CI (removed CI gating/active issue suppression).
  • Updated shutdown test deployment to run from deployed output (AppContext.BaseDirectory) instead of source-tree project discovery.
  • Removed the load-bearing PreservePublishedApplicationForDebugging dependency by using a borrowed published app path that does not delete test output on cleanup.
  • Replaced external kill process usage in shutdown signaling with libc signal calls (SIGINT/SIGTERM) for Helix/minimal-image compatibility.
  • Kept Unix cleanup best-effort and resilient:
    • graceful termination first (SIGTERM)
    • hard-kill fallback (Process.Kill()) when needed
    • tolerance for missing process tools / constrained environments
  • Increased startup/shutdown wait windows to 180 seconds for stress/JIT-stress stability.
  • Fixed the shutdown test startup race by wiring output observation before DeployAsync() starts process output capture, so the initial Started line cannot be missed.
  • Fixed net481 compatibility by using Marshal.GetLastWin32Error().

Validation

  • ./build.sh clr+libs -rc release succeeds.
  • ./build.sh clr+libs+host -rc release -lc release succeeds.
  • Microsoft.Extensions.Hosting.Functional.Tests builds successfully for net481.
  • dotnet build /t:test Microsoft.Extensions.Hosting.Functional.Tests.csproj -c Release -f net11.0 /p:TestFilter=Microsoft.AspNetCore.Hosting.FunctionalTests.ShutdownTests passes (2/2).
  • CI-feedback-driven shutdown/process-cleanup paths were updated to match Helix runtime constraints and avoid prior failure modes.

Copilot AI requested review from Copilot and removed request for Copilot May 15, 2026 12:32
Copilot AI linked an issue May 15, 2026 that may be closed by this pull request
@github-actions github-actions Bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label May 15, 2026
Copilot AI requested review from Copilot and removed request for Copilot May 15, 2026 13:03
Copilot AI changed the title [WIP] Fix FunctionalTests/TestApp on Extensions Hosting Re-enable Microsoft.Extensions.Hosting FunctionalTests/TestApp shutdown coverage May 15, 2026
Copilot AI requested a review from rosebyte May 15, 2026 13:07
@github-actions github-actions Bot added area-Extensions-Hosting and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels May 15, 2026
@rosebyte

Copy link
Copy Markdown
Member

@copilot , the shutdown tests are failing with this error on helix machines:

Comments
{ "HelixJobId": "bf5b8a6f-0bdd-40b3-9fbc-f0eec7148f40", "HelixWorkItemName": "Microsoft.Extensions.Hosting.Functional.Tests" }

Error message
System.IO.DirectoryNotFoundException : Could not find Microsoft.Extensions.Hosting.TestApp.csproj from base directory '/root/helix/work/workitem/e/'.

Stack trace
at Microsoft.AspNetCore.Hosting.FunctionalTests.ShutdownTests.GetApplicationPath() in //src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/ShutdownTests.cs:line 173
at Microsoft.AspNetCore.Hosting.FunctionalTests.ShutdownTests.ExecuteShutdownTest(String testName, String shutdownMechanic) in /
/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/ShutdownTests.cs:line 53
at Microsoft.AspNetCore.Hosting.FunctionalTests.ShutdownTests.ShutdownTestRun() in /_/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/ShutdownTests.cs:line 35
--- End of stack trace from previous location ---

Copilot AI requested review from Copilot and removed request for Copilot May 18, 2026 11:55

Copilot AI commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

@copilot , the shutdown tests are failing with this error on helix machines:

...

Fixed in b9cd702. I removed the source-tree lookup and switched the shutdown tests to run TestApp from the deployed test output (AppContext.BaseDirectory) using a custom ApplicationPublisher, so Helix no longer requires Microsoft.Extensions.Hosting.TestApp.csproj to exist in the workitem.

Copilot AI changed the title Re-enable Microsoft.Extensions.Hosting FunctionalTests/TestApp shutdown coverage Re-enable Microsoft.Extensions.Hosting FunctionalTests/TestApp shutdown coverage across local and Helix environments May 18, 2026
@rosebyte

Copy link
Copy Markdown
Member

@copilot , this time it failed with this error:

Test Failures (700 tests failed)
🔹 [All failing tests from runtime]

⚠️ 698 failing tests not displayed for this pipeline due to space limitations

❌ Microsoft.AspNetCore.Hosting.FunctionalTests.ShutdownTests.ShutdownTestRun [Console] [Details] [Artifacts] [0.70% failure rate]
[ 🚧 Create issue against infrastructure team] [ 📄 Create issue in this repo]
Failing Configuration
AzureLinux.3.0.ArmArch.Open
Exception Message
System.ComponentModel.Win32Exception : An error occurred trying to start process 'pgrep' with working directory '/root/helix/work/workitem/e'. No such file or directory
CallStack
at Microsoft.Win32.SafeHandles.SafeProcessHandle.ForkAndExecProcess(ProcessStartInfo startInfo, String resolvedFilename, String[] argv, IDictionary2 env, String cwd, Boolean setCredentials, UInt32 userId, UInt32 groupId, UInt32[] groups, SafeFileHandle stdinHandle, SafeFileHandle stdoutHandle, SafeFileHandle stderrHandle, Boolean usesTerminal, SafeHandle[] inheritedHandles, Holder& waitStateHolder, Boolean throwOnNoExec) in /_/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.cs:line 369 at Microsoft.Win32.SafeHandles.SafeProcessHandle.StartCore(ProcessStartInfo startInfo, SafeFileHandle stdinHandle, SafeFileHandle stdoutHandle, SafeFileHandle stderrHandle, SafeHandle[] inheritedHandles, Holder& waitStateHolder) in /_/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.cs:line 213 at System.Diagnostics.Process.StartCore(ProcessStartInfo startInfo, SafeFileHandle stdinHandle, SafeFileHandle stdoutHandle, SafeFileHandle stderrHandle, SafeHandle[] inheritedHandles) in /_/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs:line 363 at System.Diagnostics.Process.Start() in /_/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs:line 1272 at System.Diagnostics.Process.Start(ProcessStartInfo startInfo) in /_/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs:line 1383 at Microsoft.Extensions.Internal.ProcessExtensions.RunProcessAndWaitForExit(String fileName, String arguments, TimeSpan timeout, String& stdout) in /_/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/ProcessExtensions.cs:line 93 at Microsoft.Extensions.Internal.ProcessExtensions.GetAllChildIdsUnix(Int32 parentId, ISet1 children, TimeSpan timeout) in //src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/ProcessExtensions.cs:line 44
at Microsoft.Extensions.Internal.ProcessExtensions.KillTree(Process process, TimeSpan timeout) in /
/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/ProcessExtensions.cs:line 33
at Microsoft.Extensions.Internal.ProcessExtensions.KillTree(Process process) in //src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/ProcessExtensions.cs:line 17
at Microsoft.Extensions.Hosting.IntegrationTesting.ApplicationDeployer.ShutDownIfAnyHostProcess(Process hostProcess) in /
/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Deployers/ApplicationDeployer.cs:line 126
at Microsoft.Extensions.Hosting.IntegrationTesting.SelfHostDeployer.Dispose() in //src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Deployers/SelfHostDeployer.cs:line 179
at Microsoft.AspNetCore.Hosting.FunctionalTests.ShutdownTests.ExecuteShutdownTest(String testName, String shutdownMechanic) in /
/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/ShutdownTests.cs:line 107
at Microsoft.AspNetCore.Hosting.FunctionalTests.ShutdownTests.ShutdownTestRun() in //src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/ShutdownTests.cs:line 34
--- End of stack trace from previous location ---
❌ Microsoft.AspNetCore.Hosting.FunctionalTests.ShutdownTests.ShutdownTestWaitForShutdown [Console] [Details] [Artifacts] [0.69% failure rate]
[ 🚧 Create issue against infrastructure team] [ 📄 Create issue in this repo]
Failing Configuration
AzureLinux.3.0.ArmArch.Open
Exception Message
System.ComponentModel.Win32Exception : An error occurred trying to start process 'pgrep' with working directory '/root/helix/work/workitem/e'. No such file or directory
CallStack
at Microsoft.Win32.SafeHandles.SafeProcessHandle.ForkAndExecProcess(ProcessStartInfo startInfo, String resolvedFilename, String[] argv, IDictionary2 env, String cwd, Boolean setCredentials, UInt32 userId, UInt32 groupId, UInt32[] groups, SafeFileHandle stdinHandle, SafeFileHandle stdoutHandle, SafeFileHandle stderrHandle, Boolean usesTerminal, SafeHandle[] inheritedHandles, Holder& waitStateHolder, Boolean throwOnNoExec) in /_/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.cs:line 369 at Microsoft.Win32.SafeHandles.SafeProcessHandle.StartCore(ProcessStartInfo startInfo, SafeFileHandle stdinHandle, SafeFileHandle stdoutHandle, SafeFileHandle stderrHandle, SafeHandle[] inheritedHandles, Holder& waitStateHolder) in /_/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.cs:line 213 at System.Diagnostics.Process.StartCore(ProcessStartInfo startInfo, SafeFileHandle stdinHandle, SafeFileHandle stdoutHandle, SafeFileHandle stderrHandle, SafeHandle[] inheritedHandles) in /_/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs:line 363 at System.Diagnostics.Process.Start() in /_/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs:line 1272 at System.Diagnostics.Process.Start(ProcessStartInfo startInfo) in /_/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs:line 1383 at Microsoft.Extensions.Internal.ProcessExtensions.RunProcessAndWaitForExit(String fileName, String arguments, TimeSpan timeout, String& stdout) in /_/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/ProcessExtensions.cs:line 93 at Microsoft.Extensions.Internal.ProcessExtensions.GetAllChildIdsUnix(Int32 parentId, ISet1 children, TimeSpan timeout) in /
/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/ProcessExtensions.cs:line 44
at Microsoft.Extensions.Internal.ProcessExtensions.KillTree(Process process, TimeSpan timeout) in //src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/ProcessExtensions.cs:line 33
at Microsoft.Extensions.Internal.ProcessExtensions.KillTree(Process process) in /
/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/ProcessExtensions.cs:line 17
at Microsoft.Extensions.Hosting.IntegrationTesting.ApplicationDeployer.ShutDownIfAnyHostProcess(Process hostProcess) in //src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Deployers/ApplicationDeployer.cs:line 126
at Microsoft.Extensions.Hosting.IntegrationTesting.SelfHostDeployer.Dispose() in /
/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/IntegrationTesting/src/Deployers/SelfHostDeployer.cs:line 179
at Microsoft.AspNetCore.Hosting.FunctionalTests.ShutdownTests.ExecuteShutdownTest(String testName, String shutdownMechanic) in //src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/ShutdownTests.cs:line 107
at Microsoft.AspNetCore.Hosting.FunctionalTests.ShutdownTests.ShutdownTestWaitForShutdown() in /
/src/libraries/Microsoft.Extensions.Hosting/tests/FunctionalTests/ShutdownTests.cs:line 41
--- End of stack trace from previous location ---

…fter Kill()

Co-authored-by: rosebyte <14963300+rosebyte@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot June 5, 2026 08:01
@rosebyte

rosebyte commented Jun 5, 2026

Copy link
Copy Markdown
Member

@copilot , you only addressed one of my comments, resolve all of them.

… 180s for stress environments

Co-authored-by: rosebyte <14963300+rosebyte@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot June 5, 2026 08:15

Copilot AI commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

@copilot , you only addressed one of my comments, resolve all of them.

Fixed in f9172b4. I've addressed both remaining issues:

  1. KillProcessUnix now uses kill -TERM for graceful termination (SIGTERM) instead of Process.Kill() (SIGKILL)
  2. Test timeouts increased from 60 seconds to 180 seconds (3 minutes) for startup and shutdown waits to handle stress/JIT-stress Helix environments

Copilot AI review requested due to automatic review settings June 5, 2026 12:12
@rosebyte rosebyte marked this pull request as ready for review June 5, 2026 12:12

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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comment on lines 143 to +147
if (!process.HasExited)
{
process.Kill();
// Wait for the process to actually exit after Kill() before accessing ExitCode
if (!process.WaitForExit(5000))
Comment on lines +108 to +111
if (!process.WaitForExit((int)timeout.TotalMilliseconds))
{
throw new TimeoutException($"Process {processId} did not exit within the allotted timeout of {timeout}.");
}

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.

@copilot, OK, do it that way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 16bc0339: KillProcessUnix no longer throws on SIGTERM timeout; it now escalates to Process.Kill() (hard kill) as best-effort cleanup instead of failing disposal.

Comment on lines +118 to +125
catch (InvalidOperationException)
{
// Ignore if process has already exited.
}
catch (Win32Exception)
{
// Ignore permission or process-not-found errors (e.g., kill not available).
}

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.

@copilot, implement the fix.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Implemented in 16bc0339: Unix termination now uses libc kill(pid, SIGTERM) and falls back to Process.Kill() when graceful termination fails (including Win32Exception/error paths), so orphaned processes are still terminated.

Comment on lines +87 to +94
var psi = new ProcessStartInfo
{
FileName = "kill",
Arguments = $"-TERM {processId}",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};

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.

On .Net 11, you could use Process.GetProcessById(processId).SafeHandle.Signal(PosixSignal.SIGTERM). I think this only runs on Unix on .Net 11, so it could make sense to use that instead.

Or you could use the PInvoke approach from ShutdownTests. Both calls to kill should probably use the same approach.

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.

@copilot , this sounds a good idea, implement it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Implemented in 16bc033. ProcessExtensions.KillProcessUnix now uses libc kill(pid, SIGTERM) (matching the ShutdownTests signal approach), waits for exit, and falls back to Process.Kill() if graceful termination fails.

Co-authored-by: rosebyte <14963300+rosebyte@users.noreply.github.com>

@rosebyte rosebyte left a comment

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.

@copilot, I left two comments to implement.

Comment on lines +108 to +111
if (!process.WaitForExit((int)timeout.TotalMilliseconds))
{
throw new TimeoutException($"Process {processId} did not exit within the allotted timeout of {timeout}.");
}

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.

@copilot, OK, do it that way.

Comment on lines +118 to +125
catch (InvalidOperationException)
{
// Ignore if process has already exited.
}
catch (Win32Exception)
{
// Ignore permission or process-not-found errors (e.g., kill not available).
}

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.

@copilot, implement the fix.

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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment on lines +97 to 100
await started.Task.WaitAsync(TimeSpan.FromSeconds(180));

SendShutdownSignal(deployer.HostProcess);

@rosebyte

Copy link
Copy Markdown
Member

@copilot , I left comments for you, please implement them.

Co-authored-by: rosebyte <14963300+rosebyte@users.noreply.github.com>

Copilot AI commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

@copilot , I left comments for you, please implement them.

Fixed in 14a6f4c97. I addressed the remaining shutdown-test startup race by subscribing before DeployAsync() starts output capture and forwarding early process output through SelfHostDeployer, so the test can no longer miss the initial Started line.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable FunctionalTests/TestApp on Extensions Hosting

4 participants