Summary
The memory governor that throttles World Partition builders, FWorldPartitionHelpers::HasExceededMaxMemory() (WorldPartitionHelpers.cpp:249), decides only from physical-RAM residency (available physical, process working set). On any machine with a page file, Windows keeps those signals healthy by trimming working sets and paging committed memory out to disk — so the governor never throttles. The builder keeps dispatching tasks, and commit charge climbs unbounded until it hits the system commit limit (physical + page file) and the process dies with “Out of Page File.” The metric that actually runs out — commit charge — is never consulted, even though the platform layer already computes it (MemStats.AvailableVirtual, MemStats.UsedVirtual). This lives in core FWorldPartitionHelpers, so it affects every WP builder (HLOD, navmesh, MeshPartition, …), and is worst on ≤32GB machines (typical CI/build hardware).
What Type of Bug are you experiencing?
Foundation (C++ Tools, Profiling, & Pipeline)
Steps to Reproduce
- Open a large World Partition map whose build processes enough actors to exceed physical RAM (our case: a MeshTerrainMode “Mega Mesh” / MeshPartition map of thousands of sections; any large WP build that fills memory
will do). - Ensure a sizable Windows page file (the default system-managed size is enough).
- Trigger a whole-world build — e.g. Build → Build Mesh Partition (force rebuild), or any WorldPartition builder commandlet over the full map.
- Watch the editor/commandlet process in Task Manager → Details → Commit size (and Performance → Memory → Committed).
Expected Result
As commit headroom (available page file) runs low, the builder’s memory governor throttles task dispatch / forces garbage collection, so the build completes — slowly if necessary — without exhausting the system commit limit.
Observed Result
Commit charge climbs monotonically while the process’s resident physical stays modest (Windows trims the working set and pages out). The governor never fires because it only watches physical residency. Commit reaches the system commit limit → allocation fails → “Out of Page File” hard crash. On our box the crash also dropped displays and required a remote reboot. Enlarging the page file only delays the crash and induces heavy disk thrash; shrinking it makes the crash arrive sooner. The page-file size is a red herring — the governor is watching the wrong metric.
Affects Versions
5.8
Platform(s)
Windows
Additional Notes
Engine / platform: UE 5.8, Windows 11 (reproducible on any Windows host with a page file; the logic is platform-independent).
Root cause — WorldPartitionHelpers.cpp:249:
bool FWorldPartitionHelpers::HasExceededMaxMemory()
{
const FPlatformMemoryStats MemStats = FPlatformMemory::GetStats();
const uint64 MemoryMinFreePhysical = 1llu * 1024 * 1024 * 1024; // 1 GB
const uint64 MemoryMaxUsedPhysical = FMath::Max(32llu*1024*1024*1024, MemStats.TotalPhysical / 2);
const bool bHasExceededMinFreePhysical = MemStats.AvailablePhysical < MemoryMinFreePhysical;
const bool bHasExceededMaxUsedPhysical = MemStats.UsedPhysical >= MemoryMaxUsedPhysical;
return bHasExceededMinFreePhysical || bHasExceededMaxUsedPhysical; // physical only
}
- AvailablePhysical < 1GB effectively never trips: AvailablePhysical (ullAvailPhys) includes the standby list, which Windows keeps padded by paging. It only collapses once the page file is already full — i.e. when you are already crashing.
- UsedPhysical >= max(32GB, RAM/2) does not trip either: under pressure Windows trims the process working set (evicts resident pages to the page file), so resident physical stays low while committed memory balloons.
The max(32GB, …) floor makes the used-physical check dead on small machines. On a 32GB box the threshold is 32GB — you can never have a 32GB working set on a 32GB machine, so that clause can never fire; on 16GB it is even more impossible. The check only has a theoretical chance on >64GB machines (and trimming still defeats it there). So the existing safety net is weakest on exactly the ≤32GB hardware typical of CI/build farms (often decommissioned dev desktops with less RAM than a working dev box). If a whole-world build crashes on a dev machine, it is guaranteed to crash — unattended — on CI.
The numbers needed are already computed — WindowsPlatformMemory.cpp:
// :340 AvailableVirtual is clamped to available page file == available COMMIT headroom
MemoryStats.AvailableVirtual = FMath::Min(MemoryStats.AvailableVirtual, MemoryStatusEx.ullAvailPageFile);
// :344-345 PagefileUsage == commit charge
MemoryStats.UsedVirtual = ProcessMemoryCounters.PagefileUsage;
HasExceededMaxMemory() reads neither, even though the GC diagnostics at WorldPartitionHelpers.cpp:276 already log AvailableVirtual.
Suggested fix — throttle on commit pressure, two clauses (each covers a mode the other misses):
// (a) thrash detector — THIS process is paging itself out (commit >> resident). Self-scaling.
const bool bThrashing = (MemStats.UsedPhysical > 0) &&
((double)MemStats.UsedVirtual / (double)MemStats.UsedPhysical) > 2.0; // tune vs measured baseline
// (b) absolute backstop — the SYSTEM is about to run out of commit, whoever is using it.
const uint64 MemoryMinFreeVirtual = 8llu * 1024 * 1024 * 1024; // ~8 GB commit headroom
const bool bHasExceededMinFreeVirtual = MemStats.AvailableVirtual < MemoryMinFreeVirtual;
return bHasExceededMinFreePhysical || bHasExceededMaxUsedPhysical
|| bThrashing || bHasExceededMinFreeVirtual;
Note on (a): a healthy UE editor already runs commit:resident ~1.3–1.6:1 (untouched allocator arenas, file-backed code/asset pages, idle working-set trim), so a 1.25 trigger would false-fire; ~2.0 is a safe default — measure the resting ratio and set ~1.5× it. Clause (b) catches the system-wide case the process-local ratio cannot see. Because the fix is in FWorldPartitionHelpers, it benefits all WP builders.
Call site (for context): WorldPartitionMeshPartitionBuilder.cpp:726 gates dispatch on HasExceededMaxMemory() with a hardcoded MaxTasksInFlight = 16 (…Builder.h:208, not a cvar), so there is no user-facing way to reduce concurrency to bound peak commit either.
Related (possibly a separate report): the MeshPartition build commands are whole-world only — there is no “compile loaded/selected sections” path, so iterative work has no choice but the whole-world bake that triggers this crash. The live loaded-region preview (AInteractiveSection) is transient/editor-only and is not persisted output.