Skip to content

Conversation

@snikeguo
Copy link

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.

Copilot AI review requested due to automatic review settings November 11, 2025 14:11
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Nov 11, 2025
@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 Nov 11, 2025
Copilot finished reviewing on behalf of snikeguo November 11, 2025 14:13
Copy link
Contributor

Copilot AI left a 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_SIZE from 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

@huoyaoyuan huoyaoyuan added area-NativeAOT-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Nov 11, 2025
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Comment on lines +69 to +72
string? env = Environment.GetEnvironmentVariable("DOTNET_FOH_SEGMENT_DEFAULT_SIZE");
if (!string.IsNullOrEmpty(env))
{
if (TryParsePositiveULong(env, out ulong parsed) && parsed > 0)
Copy link
Member

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:

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;

@jkotas
Copy link
Member

jkotas commented Nov 11, 2025

The frozen object heap is committed in 64kB chunks (see FOH_COMMIT_SIZE). Why is it not enough for your cases?

@huoyaoyuan
Copy link
Member

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.

@jkotas
Copy link
Member

jkotas commented Nov 11, 2025

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.

@snikeguo
Copy link
Author

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.

@huoyaoyuan
Copy link
Member

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.

@EgorBo
Copy link
Member

EgorBo commented Nov 11, 2025

perhaps instead of adding yet another obscure env.var we can somehow detect that we're on device without virtual memory support?

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

Labels

area-NativeAOT-coreclr community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants