Created
January 21, 2015 20:59
-
-
Save fnuecke/6dadb1e2598b4ff153cc to your computer and use it in GitHub Desktop.
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
--[[ This script automatically saves the global environment periodically | |
and restores it when this script is run. Recommended usage is with | |
the standalone Lua interpreter like this: | |
lua -i autosave.lua | |
It was coded for and tested with Eris (https://github.com/fnuecke/eris). | |
This approach is quite limited and could be improved in a number of ways: | |
- Userdata cannot be persisted automatically, so if, for example, some | |
files are open the session cannot be saved / restored. | |
- There's the (unlikely) possibility of the script trying to save while | |
already doing so, if the save takes longer than `autosaveInterval` | |
and the garbage collector is triggered in the save logic. | |
- I could not figure out a way to check whether Lua is shutting down in | |
the autosave() function. It gets called when Lua closes, but it may | |
decide not to save if the last save was earlier than the set interval. | |
]] | |
-- [[ Config ]] | |
-- The name of the file to persist the global state to. | |
local stateFile = ".lua.eris" | |
-- The interval in seconds in which to save the global state. Avoids | |
-- excessive saves if the garbage collector hyperventilates. | |
local autosaveInterval = 10 | |
-- [[ General persistence bootstrapping ]] | |
-- Stuff we cannot persist directly. Basically all of the built-in | |
-- C functions and userdata. We need a mapping to and from 'permanent' | |
-- values, that act as placeholders for these values. | |
local perms, uperms = {}, {} | |
do | |
local processed = {} -- list of seen tables, avoids cycles. | |
local function flattenAndStore(path, t) | |
-- Get the list of keys so we can sort it. This is needed because | |
-- permanent values have to be unique, so we need to pick a name for | |
-- them in a deterministic fashion (which pairs isn't). | |
local keys = {} | |
for k, v in pairs(t) do | |
if type(k) ~= "string" and type(k) ~= "number" then | |
io.stderr:write("Cannot generate permanent value for global with non-string key at " .. path .. ". Things may not go well.\n") | |
else | |
table.insert(keys, k) | |
end | |
end | |
table.sort(keys) | |
for _, k in ipairs(keys) do | |
local name = path .. "." .. k | |
local v = t[k] | |
-- This avoids duplicate permanent value entries, see above. | |
if perms[v] == nil then | |
local vt = type(v) | |
if "function" == vt or "userdata" == vt then | |
perms[v] = name | |
uperms[name] = v | |
elseif "table" == vt and not processed[v] then | |
processed[v] = true | |
flattenAndStore(name, v) | |
end | |
end | |
end | |
end | |
flattenAndStore("_G", _G) | |
end | |
-- [[ Autosave background task ]] | |
local lastSave = os.time() | |
local function autosave() | |
if os.difftime(os.time(), lastSave) > autosaveInterval then | |
lastSave = os.time() | |
local data, reason = eris.persist(perms, _ENV) | |
if not data then | |
io.stderr:write("Failed to persist environment: " .. tostring(reason) .. "\n") | |
else | |
local f, reason = io.open(stateFile, "wb") | |
if not f then | |
io.stderr:write("Failed to save environment: " .. tostring(reason) .. "\n") | |
else | |
f:write(data) | |
f:close() | |
end | |
end | |
end | |
-- Check again later. This basically uses the garbage collector as a | |
-- scheduler (this method is called when the table is collected). It | |
-- is a concept I first saw posted by Roberto on the list a while | |
-- back, in the context of sandboxing I think. | |
setmetatable({}, {__gc=autosave}) | |
end | |
autosave() | |
-- [[ Startup / load autosave ]] | |
local f = io.open(stateFile, "rb") | |
if f then | |
-- Got a previous state, try to restore it. | |
local data = f:read("*a") | |
f:close() | |
local env, reason = eris.unpersist(uperms, data) | |
if not env then | |
io.stderr:write("Failed to unpersist environment: " .. tostring(reason) .. "\n") | |
else | |
for k, v in pairs(env) do | |
_ENV[k] = env[k] | |
setmetatable(_ENV, getmetatable(env)) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment