Skip to content

Respect VmallocTotal from /proc/meminfo as virtual address space limit#130994

Open
janvorli wants to merge 1 commit into
dotnet:mainfrom
janvorli:respect-vmalloctotal-limit
Open

Respect VmallocTotal from /proc/meminfo as virtual address space limit#130994
janvorli wants to merge 1 commit into
dotnet:mainfrom
janvorli:respect-vmalloctotal-limit

Conversation

@janvorli

Copy link
Copy Markdown
Member

While .NET runtime and GC takes into account the virtual memory limit extracted from the RLIMIT_AS, there is another limiting factor on Linux that it doesn't consider - the VmallocTotal in /proc/meminfo. This value exposes a kernel compile time constant and we've got a report of a problem when it was mere 256GB.

Add minipal_get_virtual_address_space_limit() in src/native/minipal/vmlimit.c that computes the effective virtual address space limit by taking the minimum of RLIMIT_AS and VmallocTotal from /proc/meminfo. The result is cached since these values don't change during process lifetime.

All existing RLIMIT_AS call sites in coreclr (GC, PAL, minipal doublemapping) now use this single consolidated function instead of duplicating the getrlimit logic.

Close #85556

@janvorli janvorli added this to the 11.0.0 milestone Jul 17, 2026
@janvorli
janvorli requested a review from kkokosa July 17, 2026 19:13
@janvorli janvorli self-assigned this Jul 17, 2026
Copilot AI review requested due to automatic review settings July 17, 2026 19:13
@janvorli
janvorli force-pushed the respect-vmalloctotal-limit branch from f3334ea to fb497cc Compare July 17, 2026 19:14
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

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

This PR centralizes virtual address space limit detection on Unix by introducing a minipal helper that computes an effective limit as min(RLIMIT_AS, VmallocTotal) (Linux) and updates CoreCLR call sites (GC, PAL, minipal double-mapping) to use it instead of duplicating getrlimit logic.

Changes:

  • Add minipal_get_virtual_address_space_limit() in src/native/minipal and cache the computed value.
  • Update CoreCLR consumers (GC, PAL virtual memory, double-mapping) to use the consolidated minipal helper.
  • Wire the new minipal source file into the native build.

Reviewed changes

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

Show a summary per file
File Description
src/native/minipal/vmlimit.h Declares new minipal helper for effective virtual address space limit.
src/native/minipal/vmlimit.c Implements limit computation (RLIMIT_AS + Linux VmallocTotal) with caching.
src/native/minipal/CMakeLists.txt Adds vmlimit.c to minipal sources on Unix hosts.
src/coreclr/pal/src/map/virtual.cpp Switches executable memory reservation sizing to use minipal VM limit helper.
src/coreclr/minipal/Unix/doublemapping.cpp Clips double-mapped memory sizing using minipal VM limit helper.
src/coreclr/gc/unix/gcenv.unix.cpp Uses minipal VM limit helper for GC VM limit and virtual load computation.
src/coreclr/gc/unix/cgroup.cpp Uses minipal VM limit helper when computing restricted physical memory limit.
Comments suppressed due to low confidence (1)

src/coreclr/pal/src/map/virtual.cpp:1642

  • initialReserveLimit is an int32_t, but it’s being assigned from a size_t computation (virtualAddressSpaceLimit * percent / 100). When the VM limit is large (e.g. 256GB VmallocTotal), this can overflow to a negative value and then be used as an allocation size later (see line 1720), potentially turning into a huge reservation after integer conversion.
    size_t virtualAddressSpaceLimit = minipal_get_virtual_address_space_limit();
    if (virtualAddressSpaceLimit != SIZE_MAX)
    {
        // By default reserve max 20% of the available virtual address space
        size_t initialExecMemoryPerc = 20;
        CLRConfigNoCache defInitialExecMemoryPerc = CLRConfigNoCache::Get("InitialExecMemoryPercent", /*noprefix*/ false, &getenv);
        if (defInitialExecMemoryPerc.IsSet())
        {
            DWORD perc;
            if (defInitialExecMemoryPerc.TryAsInteger(16, perc))
            {
                initialExecMemoryPerc = perc;
            }
        }

        initialReserveLimit = virtualAddressSpaceLimit * initialExecMemoryPerc / 100;
        if (initialReserveLimit < sizeOfAllocation)
        {
            sizeOfAllocation = initialReserveLimit;
        }

Comment thread src/native/minipal/vmlimit.c
Comment thread src/native/minipal/vmlimit.c
Comment thread src/native/minipal/vmlimit.c
Copilot AI review requested due to automatic review settings July 17, 2026 19:21

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 7 out of 7 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

src/coreclr/pal/src/map/virtual.cpp:1642

  • initialReserveLimit is an int32_t, but it's currently assigned from a size_t calculation (virtualAddressSpaceLimit * percent / 100). With the new VmallocTotal-based limit, this can be a large finite value (e.g., hundreds of GB) and overflow/truncate when stored into int32_t, which can then flow into ReserveVirtualMemory as a negative/garbage size. Since this logic is only intended to reduce the default reservation, only set initialReserveLimit (and shrink sizeOfAllocation) when the computed value is smaller than the current sizeOfAllocation, and compute in size_t to avoid overflow.
    size_t virtualAddressSpaceLimit = minipal_get_virtual_address_space_limit();
    if (virtualAddressSpaceLimit != SIZE_MAX)
    {
        // By default reserve max 20% of the available virtual address space
        size_t initialExecMemoryPerc = 20;
        CLRConfigNoCache defInitialExecMemoryPerc = CLRConfigNoCache::Get("InitialExecMemoryPercent", /*noprefix*/ false, &getenv);
        if (defInitialExecMemoryPerc.IsSet())
        {
            DWORD perc;
            if (defInitialExecMemoryPerc.TryAsInteger(16, perc))
            {
                initialExecMemoryPerc = perc;
            }
        }

        initialReserveLimit = virtualAddressSpaceLimit * initialExecMemoryPerc / 100;
        if (initialReserveLimit < sizeOfAllocation)
        {
            sizeOfAllocation = initialReserveLimit;
        }

Comment thread src/native/minipal/vmlimit.h
Comment thread src/native/minipal/vmlimit.c Outdated
Comment on lines +6 to +10
#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <sys/resource.h>
Comment thread src/native/minipal/vmlimit.c
Comment thread src/native/minipal/vmlimit.c
Copilot AI review requested due to automatic review settings July 17, 2026 20:24
@janvorli
janvorli force-pushed the respect-vmalloctotal-limit branch from fb497cc to deeb4a9 Compare July 17, 2026 20:24

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 7 out of 7 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

src/coreclr/pal/src/map/virtual.cpp:1643

  • initialReserveLimit is int32_t, but it's assigned from virtualAddressSpaceLimit * initialExecMemoryPerc / 100 (a size_t). If the VM limit is large (e.g. VmallocTotal=256GB) this overflows/truncates to a negative int32_t, and later sizeOfAllocation = initialReserveLimit can pass a negative size into ReserveVirtualMemory. Only compute/store initialReserveLimit when the computed limit is actually smaller than the current sizeOfAllocation (and therefore fits in int32_t).
    size_t virtualAddressSpaceLimit = minipal_get_virtual_address_space_limit();
    if (virtualAddressSpaceLimit != SIZE_MAX)
    {
        // By default reserve max 20% of the available virtual address space
        size_t initialExecMemoryPerc = 20;
        CLRConfigNoCache defInitialExecMemoryPerc = CLRConfigNoCache::Get("InitialExecMemoryPercent", /*noprefix*/ false, &getenv);
        if (defInitialExecMemoryPerc.IsSet())
        {
            DWORD perc;
            if (defInitialExecMemoryPerc.TryAsInteger(16, perc))
            {
                initialExecMemoryPerc = perc;
            }
        }

        initialReserveLimit = virtualAddressSpaceLimit * initialExecMemoryPerc / 100;
        if (initialReserveLimit < sizeOfAllocation)
        {
            sizeOfAllocation = initialReserveLimit;
        }
    }

Comment thread src/native/minipal/vmlimit.h
Comment thread src/native/minipal/vmlimit.c
Comment thread src/native/minipal/vmlimit.c
Comment thread src/native/minipal/vmlimit.c
Comment thread src/native/minipal/vmlimit.c
Copilot AI review requested due to automatic review settings July 17, 2026 20:30
@janvorli
janvorli force-pushed the respect-vmalloctotal-limit branch from deeb4a9 to 0d6914c Compare July 17, 2026 20:30

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 7 out of 7 changed files in this pull request and generated 4 comments.

Comment thread src/native/minipal/vmlimit.c
Comment thread src/coreclr/pal/src/map/virtual.cpp Outdated
Comment thread src/native/minipal/vmlimit.c
Comment thread src/native/minipal/vmlimit.c
Add minipal_get_virtual_address_space_limit() in src/native/minipal/vmlimit.c
that computes the effective virtual address space limit by taking the minimum
of RLIMIT_AS and VmallocTotal from /proc/meminfo. The result is cached since
these values don't change during process lifetime.

All existing RLIMIT_AS call sites in coreclr (GC, PAL, minipal doublemapping)
now use this single consolidated function instead of duplicating the getrlimit
logic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
{
// Cache the result since the values don't change during process lifetime.
// Use 0 as the "not yet computed" sentinel since a zero limit is not a valid result.
static volatile size_t cached_limit = 0;

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.

volatile is not correct here, in c it doesnt have any threading guarantees.

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.

That is not for threading guarantees, but for compiler to understand that this value can change in a way that the compiler doesn't see. We use this pattern at multiple places.

@MichalPetryka MichalPetryka Jul 18, 2026

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.

this value can change in a way that the compiler doesn't see

I don't see how that'd be supposed to happen here?

Even if this doesn't care about threading, the possibility of multiple threads accessing it at once is itself UB in C:

The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.

where normal or volatile accesses are not guaranteed atomic.

It'd also trigger TSAN errors, not sure if @jkoritzinsky plans to add that to sanitized pipelines at some point.

Copilot AI review requested due to automatic review settings July 17, 2026 21:19
@janvorli
janvorli force-pushed the respect-vmalloctotal-limit branch from 0d6914c to ba59eea Compare July 17, 2026 21:19

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 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread src/native/minipal/vmlimit.c
Comment on lines +1638 to +1643
size_t computedLimit = virtualAddressSpaceLimit * initialExecMemoryPerc / 100;
if (computedLimit > INT32_MAX)
{
computedLimit = INT32_MAX;
}
initialReserveLimit = (int32_t)computedLimit;
Comment on lines +11 to +12

#if !defined(__APPLE__) && !defined(__HAIKU__)

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.

Suggested change
#if !defined(__APPLE__) && !defined(__HAIKU__)
#include "minipalconfig.h"
#if defined(TARGET_LINUX)


return result;
}
#endif // !defined(__APPLE__) && !defined(__HAIKU__)

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.

Suggested change
#endif // !defined(__APPLE__) && !defined(__HAIKU__)
#endif // defined(TARGET_LINUX)

}
#endif

#if !defined(__APPLE__) && !defined(__HAIKU__)

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.

Suggested change
#if !defined(__APPLE__) && !defined(__HAIKU__)
#if defined(TARGET_LINUX)

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.

crash with 0x8007000E error on .NET 7.0

4 participants