-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Refactor FrozenObjectHeapManager for dynamic segment size #121526
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the FrozenObjectHeapManager to support dynamic segment sizing via an environment variable (DOTNET_FOH_SEGMENT_DEFAULT_SIZE), enabling better support for IoT devices with limited memory. The change converts the hardcoded 4MB default segment size to a runtime-configurable value while maintaining backward compatibility and safe fallback behavior.
Key changes:
- Changed
FOH_SEGMENT_DEFAULT_SIZEfrom a compile-time constant to a runtime-initialized static readonly field - Added custom integer parsing logic (
TryParsePositiveULong) to avoid globalization dependencies - Added
GetDefaultSegmentSize()to read and validate the environment variable with proper fallbacks
|
Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas |
| string? env = Environment.GetEnvironmentVariable("DOTNET_FOH_SEGMENT_DEFAULT_SIZE"); | ||
| if (!string.IsNullOrEmpty(env)) | ||
| { | ||
| if (TryParsePositiveULong(env, out ulong parsed) && parsed > 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not an acceptable pattern to parse an environment variable like this. Use AppContext switch instead, which is integrated with more toolchain. Check GC.NativeAot:
runtime/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs
Lines 883 to 890 in 118ff8e
| ulong heapHardLimit = (AppContext.GetData("GCHeapHardLimit") as ulong?) ?? ulong.MaxValue; | |
| ulong heapHardLimitPercent = (AppContext.GetData("GCHeapHardLimitPercent") as ulong?) ?? ulong.MaxValue; | |
| ulong heapHardLimitSOH = (AppContext.GetData("GCHeapHardLimitSOH") as ulong?) ?? ulong.MaxValue; | |
| ulong heapHardLimitLOH = (AppContext.GetData("GCHeapHardLimitLOH") as ulong?) ?? ulong.MaxValue; | |
| ulong heapHardLimitPOH = (AppContext.GetData("GCHeapHardLimitPOH") as ulong?) ?? ulong.MaxValue; | |
| ulong heapHardLimitSOHPercent = (AppContext.GetData("GCHeapHardLimitSOHPercent") as ulong?) ?? ulong.MaxValue; | |
| ulong heapHardLimitLOHPercent = (AppContext.GetData("GCHeapHardLimitLOHPercent") as ulong?) ?? ulong.MaxValue; | |
| ulong heapHardLimitPOHPercent = (AppContext.GetData("GCHeapHardLimitPOHPercent") as ulong?) ?? ulong.MaxValue; |
|
The frozen object heap is committed in 64kB chunks (see FOH_COMMIT_SIZE). Why is it not enough for your cases? |
He is working on embedded SoCs with no virtual memory support. Any continuous memory region in virtual memory would require continuous physical memory, in which case 4MB is too large. |
|
I think niche scenarios like that should be addressed by custom build. There are way too many things in default .NET runtime that will work poorly. For example, you would want a completely different GC implementation. |
I have modified many parts of the NATIVEAOT runtime, including the GC, to adapt it to ARMv7-M. From many examples so far it works well — for example TCP/IP, HTTP, OpenSSL, P/Invoke, GC stress tests, etc. I only want to maintain the runtime portion; for the core libraries I would like to use the official upstream repository. |
CoreLib is also considered as a part of runtime portion, despite containing a lot of libraries code. It's tightly associated with the runtime. You may need to maintain a set of patches that needs to be applied to CoreLib. |
|
perhaps instead of adding yet another obscure env.var we can somehow detect that we're on device without virtual memory support? |
Make FOH segment default size configurable and add safe parser for environment variable
Description: I added TryParsePositiveULong and GetDefaultSegmentSize to allow reading the frozen object heap (FOH) default segment size from the DOTNET_FOH_SEGMENT_DEFAULT_SIZE environment variable.
Rationale:
I implemented TryParsePositiveULong instead of using ulong.TryParse because using the built-in parsing can pull in globalization code and in some scenarios cause crashes when accessing environment variables. To be conservative and avoid that dependency, the parser is a small, self-contained routine that accepts optional leading whitespace and an optional '+' and verifies overflow and numeric characters.
Many IoT devices (for example ESP32, STM32 with 8–64 MB configurations) have very limited memory. To better support such devices, the FOH segment size is now configurable and can be reduced. This makes the FOH effectively dynamic for constrained environments.
Behavior and safety:
If DOTNET_FOH_SEGMENT_DEFAULT_SIZE is present and successfully parsed to a positive integer, it is used as the reserved segment size.
The code enforces a minimum of FOH_COMMIT_SIZE and falls back to the default value on parse failures or exceptions.
If reserving the requested size fails, the implementation will try the default FOH size and ultimately throw OutOfMemoryException if allocation fails.
Testing:
I tested with a small FOH size (128 KB) on a Kestrel static server demo and it worked fine.
This change enables better support for low-memory IoT scenarios while avoiding potential globalization-related crashes when reading and parsing the environment variable.