Lua Notes

spooky gc behavior
Login

spooky gc behavior

Consider:

local r = @record{n: integer}
function r:__gc() print "gc'd" end

require 'allocators.default'
require 'allocators.gc'
local function main()
  local c = new(r)
  print(c.n)
  gc:collect()
  print 'done'
end

main()

And running it:

$ nelua gced.nelua 
0
done
gc'd

$ nelua -r gced.nelua 
0
gc'd
done

This is what's happening: in a normal build, the 'c' variable isn't garbage-collected until after 'main' returns, because 'c' is on the stack and the GC scans the stack and decides that 'c' is alive.

In a release build, the 'c' variable occupies a register instead of the stack, and is garbage-collected by gc:collect()

Hit in the wild by a D program, and discussed on the D forums.

The workarounds are similar:

to try to ensure an additional GC-traced pointer to the allocation:

local r = @record{n: integer}
function r:__gc() print "gc'd" end

require 'allocators.default'
require 'allocators.gc'
local function main()
  local c = new(r)
  local p = &c;
  gc:register(p, #[r.value.size]#)
  defer gc:unregister(p) end
  print(c.n)
  gc:collect()
  print 'done'
end

main()

and putting the pointer in static memory:

local r = @record{n: integer}
function r:__gc() print "gc'd" end

require 'allocators.default'
require 'allocators.gc'
local c: *r
local function main()
  c = new(r)
  print(c.n)
  gc:collect()
  print 'done'
end

main()

The trouble is knowing when to do that. Fortunately this is a very hard condition to encounter, as it requires you to expect stack lifetimes to continue past the last usage of a variable, which is a strange thing to expect (in the linked program, the only other live reference to an object was passed into kernel-space where the GC can't see, and was destroyed before getting passed back from the kernel).