-
Notifications
You must be signed in to change notification settings - Fork 5.4k
[NativeAOT] Print OOM message before Abort() on Linux #125311
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
Open
Copilot
wants to merge
11
commits into
main
Choose a base branch
from
copilot/fix-out-of-memory-reporting
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
faf77aa
Initial plan
Copilot bc41dce
Fix NativeAOT OOM message not printed on Linux before Abort()
Copilot 3ef2a6d
Add OomHandling smoke test for NativeAOT OOM message reporting
Copilot ac7e07e
Merge branch 'main' into copilot/fix-out-of-memory-reporting
eduardo-vp 99a6529
Update minimalFailFast condition and test
57092c7
Add test timeout
cf2ca95
Revert changes to minimalFailFast
2eafa92
Fix test
cc43588
Show consistent error messages
15e81d4
Apply suggestion from @jkotas
jkotas f373a39
Code review feedback
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
src/tests/nativeaot/SmokeTests/OomHandling/OomHandling.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be NativeAOT smoketest. There is nothing NativeAOT specific about the desired behavior here. We want to see same or similar behavior without NativeAOT too. |
||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| // This test verifies that an out-of-memory condition in a NativeAOT process | ||
| // produces a diagnostic message on stderr before the process terminates. | ||
| // | ||
| // The test spawns itself as a subprocess with a small GC heap limit set via | ||
| // DOTNET_GCHeapHardLimit so that the subprocess reliably runs out of memory. | ||
| // The outer process then validates that the subprocess wrote the expected | ||
| // OOM message to its standard error stream. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| using System.Threading.Tasks; | ||
|
|
||
| class OomHandlingTest | ||
| { | ||
| const int Pass = 100; | ||
| const int Fail = -1; | ||
| const int TimeoutMilliseconds = 30 * 1000; | ||
|
|
||
| const string AllocateSmallArg = "--allocate-small"; | ||
| const string AllocateLargeArg = "--allocate-large"; | ||
| // Both the minimal OOM fail-fast path ("Process terminated. System.OutOfMemoryException") | ||
| // and the standard unhandled-exception path ("Unhandled exception. System.OutOfMemoryException...") | ||
| // contain this token. The test validates that some OOM diagnostic is printed rather than | ||
| // just "Aborted" with no context. | ||
| const string ExpectedToken = "OutOfMemoryException"; | ||
|
|
||
| static int Main(string[] args) | ||
| { | ||
| if (args.Length > 0 && args[0] == AllocateSmallArg) | ||
| { | ||
| // Subprocess mode: allocate until OOM is triggered. | ||
| // Phase 1: fill quickly with large blocks to use most of the heap. | ||
| // Phase 2: exhaust remaining scraps with small allocations so that | ||
| // virtually no memory is left when OOM is finally thrown. | ||
| var list = new List<object>(); | ||
| try { while (true) list.Add(new byte[16 * 1024]); } catch (OutOfMemoryException) { } | ||
| while (true) list.Add(new object()); | ||
| } | ||
|
|
||
| if (args.Length > 0 && args[0] == AllocateLargeArg) | ||
| { | ||
| // Subprocess mode: allocate 128 KB chunks until OOM is triggered. | ||
| // This leaves some free memory when OOM fires, exercising the code | ||
| // path where GetRuntimeException may allocate a new OutOfMemoryException. | ||
| var list = new List<byte[]>(); | ||
| while (true) list.Add(new byte[128 * 1024]); | ||
| } | ||
|
|
||
| // Controller mode: launch subprocesses with a GC heap limit and verify their output. | ||
| string? processPath = Environment.ProcessPath; | ||
| if (processPath == null) | ||
| { | ||
| Console.WriteLine("ProcessPath is null, skipping test."); | ||
| return Pass; | ||
| } | ||
|
|
||
| int result = RunSubprocess(processPath, AllocateSmallArg, "small allocations"); | ||
| if (result != Pass) | ||
| return result; | ||
|
|
||
| result = RunSubprocess(processPath, AllocateLargeArg, "large allocations"); | ||
| return result; | ||
| } | ||
|
|
||
| static int RunSubprocess(string processPath, string allocateArg, string description) | ||
| { | ||
| Console.WriteLine($"Testing OOM with {description}..."); | ||
|
|
||
| var psi = new ProcessStartInfo(processPath, allocateArg) | ||
| { | ||
| RedirectStandardError = true, | ||
| UseShellExecute = false, | ||
| }; | ||
| // 0x2000000 = 32 MB GC heap limit: small enough to exhaust quickly but large enough for startup. | ||
| psi.Environment["DOTNET_GCHeapHardLimit"] = "2000000"; | ||
|
eduardo-vp marked this conversation as resolved.
|
||
|
|
||
| using Process? p = Process.Start(psi); | ||
| if (p is null) | ||
| { | ||
| Console.WriteLine("Failed to start subprocess."); | ||
| return Fail; | ||
| } | ||
|
|
||
| // Read stderr asynchronously so that WaitForExit can enforce the timeout. | ||
| // A synchronous ReadToEnd() would block until the child exits, defeating the timeout. | ||
| Task<string> stderrTask = p.StandardError.ReadToEndAsync(); | ||
| if (!p.WaitForExit(TimeoutMilliseconds)) | ||
| { | ||
| p.Kill(true); | ||
|
eduardo-vp marked this conversation as resolved.
|
||
| p.WaitForExit(); | ||
| _ = stderrTask.GetAwaiter().GetResult(); | ||
| Console.WriteLine($"Subprocess timed out after {TimeoutMilliseconds / 1000} seconds."); | ||
| return Fail; | ||
| } | ||
| string stderr = stderrTask.GetAwaiter().GetResult(); | ||
|
|
||
| Console.WriteLine($"Subprocess exit code: {p.ExitCode}"); | ||
| Console.WriteLine($"Subprocess stderr: {stderr}"); | ||
|
|
||
| if (p.ExitCode == 0 || p.ExitCode == Pass) | ||
| { | ||
| Console.WriteLine("Expected a non-success exit code from the OOM subprocess."); | ||
| return Fail; | ||
| } | ||
|
|
||
| if (!stderr.Contains(ExpectedToken)) | ||
| { | ||
| Console.WriteLine($"Expected stderr to contain: {ExpectedToken}"); | ||
| return Fail; | ||
| } | ||
|
|
||
| return Pass; | ||
| } | ||
| } | ||
13 changes: 13 additions & 0 deletions
13
src/tests/nativeaot/SmokeTests/OomHandling/OomHandling.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <CLRTestPriority>0</CLRTestPriority> | ||
| <!-- This test spawns a subprocess; not supported on mobile or browser platforms --> | ||
| <CLRTestTargetUnsupported Condition="'$(TargetsAppleMobile)' == 'true' or '$(TargetsAndroid)' == 'true' or '$(TargetsBrowser)' == 'true'">true</CLRTestTargetUnsupported> | ||
| <RequiresProcessIsolation>true</RequiresProcessIsolation> | ||
| <ReferenceXUnitWrapperGenerator>false</ReferenceXUnitWrapperGenerator> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <Compile Include="OomHandling.cs" /> | ||
| </ItemGroup> | ||
| </Project> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.