Skip to content

Instantly share code, notes, and snippets.

@evg-zhabotinsky
Last active January 12, 2019 01:39
Show Gist options
  • Save evg-zhabotinsky/52ec7394be27dbdbab334ba41370b697 to your computer and use it in GitHub Desktop.
Save evg-zhabotinsky/52ec7394be27dbdbab334ba41370b697 to your computer and use it in GitHub Desktop.
Eris-compatible GC hook postponer
--[[Run it like this:
lua-eris user_gc.lua P | lua-eris user_gc.lua
The expected output is:
Ready
Continue:
GC start
GC: 2
GC done
1: 3
GC: 3
2: 1
GC: 1
GC queue end
End
Unpersist:
GC start
GC: 2
GC done
1: 3
GC: 3
2: 1
GC: 1
GC queue end
End ]]
local function print(...)
args = {...}
for i, v in ipairs(args) do
args[i] = tostring(v)
end
io.stderr:write(table.concat(args, '\t') .. '\n')
end
local uperm = {_ENV, coroutine.yield}
if ... then
-- Create and persist
local cr = coroutine.create(function()
--[[An implementation of setmetatable() wrapper that collects GC hooks for
later invocation by user code. While GC isn't running, the hooks
themselves stay exactly where they would normally be, but don't get
executed during GC either. You can eat a cake and have it, sometimes.
The implementation relies on Lua's deterministic GC hook invocation
order, i.e. the reversed order of sethook() invocations. ]]
local gcmetas = setmetatable({}, {__mode = 'k'})
local gcqueue = {}
local gcqueuelen = 0
local function gcwrap(obj)
--Enqueue obj for further GC by user code
gcqueuelen = gcqueuelen + 1
gcqueue[gcqueuelen] = obj
--Restore the metatable to the original state
local mtt = gcmetas[obj]
rawset(mtt[1], '__gc', mtt[2])
end
local gcmetamt = {__gc = function(mtt)
local mt = mtt[1]
local gc = rawget(mt, '__gc')
if type(gc) == 'function' then --Otherwise obj's GC wont be called anyway
mtt[2] = gc
rawset(mt, '__gc', gcwrap) --Temporarily override obj's GC
end
--Obj's (overriden) GC gets called right after we return
end}
local function setmt(obj, mt)
setmetatable(obj, mt)
if rawget(mt, '__gc') ~= nil then
--[[obj was marked for finalization above, so we mark a specially
crafted table for collection *right after* that, so that it is
finalized *right before* obj. Its GC hook will override obj's
__gc at the very last moment, and the new handler will restore
the original __gc right after that and put the object .
Both objects are guaranteed to be collected together, because
the special table's existence depends on obj's existence and
nothing else. (It's stored only as a value in a weak-keyed table,
and its key it the obj. ]]
gcmetas[obj] = setmetatable({mt}, gcmetamt)
end
return obj
end
--Done. The test code follows:
local x = setmt({1}, {__gc = function(obj) print('GC:', obj[1]) end})
local y = setmetatable({2}, {__gc = function(obj) print('GC:', obj[1]) end})
local z = setmt({3}, getmetatable(x))
print('Ready')
coroutine.yield()
collectgarbage()
print('GC start')
x, y, z = nil
collectgarbage()
print('GC done')
--[[The queue can be processed in an arbitrary context, after GC is done.
It can even be persisted and processed after unpersisting.
It is built without invocation of any user-provided code,
and should be fast, so no need for any sandboxing during GC.
The queue order is deterministic and the same as Lua's __gc invocation
order, i.e. in reverse to the order of setmetatable invocation. ]]
for i, v in ipairs(gcqueue) do
print(i, v[1])
getmetatable(v).__gc(v)
end
print('GC queue end')
collectgarbage()
print('End')
end)
coroutine.resume(cr)
do
pperm = {}
for i, v in ipairs(uperm) do
pperm[v] = i
end
eris.settings('path', true)
io.write(eris.persist(pperm, cr))
end
print('Continue:')
coroutine.resume(cr)
else
-- Unpersist and GC
local cr = eris.unpersist(uperm, io.read('*all'))
print('Unpersist:')
coroutine.resume(cr)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment