Skip to content

Fix GC allocation rate underflow when the heap shrinks between cycles - #2996

Open
gemtool wants to merge 2 commits into
luau-lang:masterfrom
gemtool:fix-gc-heap-shrink
Open

gemtool wants to merge 2 commits into
luau-lang:masterfrom
gemtool:fix-gc-heap-shrink

Conversation

@gemtool

@gemtool gemtool commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

The GC measures heap growth since the end of the last incremental cycle as atomicstarttotalsizebytes - endtotalsizebytes (and totalbytes - endtotalsizebytes in luaC_allocationrate). Both operands are size_t, so when the heap is smaller than it was at the end of the last cycle, the subtraction wraps around. This can happen in two ways:

  • a full collection (lua_gc(L, LUA_GCCOLLECT, 0)) frees memory but doesn't update the incremental cycle statistics
  • thread stacks are shrunk during marking, for example after a coroutine that recursed deeply returns
before after
lua_allocationrate() after a full collection -9223372036854775808 0
heap growth seen by the pacer after a coroutine stack shrink (646KB -> 348KB) 18446744073709253349 bytes 0 bytes
allocation rate used by the pacer after a full collection 1.08e21 bytes/s 0 bytes/s

The double to int64 conversions of these values are undefined behavior (reported by UBSan in luaC_allocationrate and getheaptrigger). lua_allocationrate was added in 0.731 to pace the GC externally, so an embedder that runs a full collection gets a garbage rate right after it.

This change treats a heap that shrank as zero growth. When the heap grew, the results are unchanged. The fix is behind DFFlag::LuauGcHeapShrinkFix and the flag-off paths keep the original expressions.

Resetting the cycle statistics in luaC_fullgc would only cover the first case, so the growth is clamped instead.

Added ApiAllocationRateAfterFullGC, which fails without the fix (lua_allocationrate returns -9223372036854775808).

@gemtool
gemtool requested a review from a team as a code owner September 27, 2026 13:38

@tommyscholly tommyscholly 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.

Please reset gcstats.endtimestamp and gcstats.endtotalsizebytes at the end of luaC_fullgc() as well.
Otherwise, lua_allocationrate() will continue to report 0 after a full collection until the heap exceeds its previous size, even when allocations occur.
Additionally, I would like to see this test allocate after the full GC and check that lua_allocationrate() becomes positive.

Additionally additionally we should probably flag these changes I'm requesting as well.

@gemtool

gemtool commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, good catch! luaC_fullgc now resets gcstats.endtimestamp and gcstats.endtotalsizebytes, behind the same LuauGcHeapShrinkFix flag. The test now allocates after the full collection and checks that lua_allocationrate becomes positive.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants