Last active
January 12, 2019 01:39
-
-
Save evg-zhabotinsky/52ec7394be27dbdbab334ba41370b697 to your computer and use it in GitHub Desktop.
Eris-compatible GC hook postponer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[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