Skip to content

Instantly share code, notes, and snippets.

@akavel
Last active August 29, 2015 13:56
Show Gist options
  • Save akavel/9254298 to your computer and use it in GitHub Desktop.
Save akavel/9254298 to your computer and use it in GitHub Desktop.
--[[ 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
From: Florian Nücke <florian <at> nuecke.de>
Subject: Re: "Image-based persistence" a la Smalltalk/LISP?
Newsgroups: gmane.comp.lang.lua.general
Date: 2014-02-27 15:10:41 GMT (5 days, 20 hours and 9 minutes ago)
If I understand the requirement correctly, with some limitations it is
possible to do this with Pluto/Eris using a simple "init" script that
gets fed to the interpreter. Since I had a portion of this lying around
from earlier tests and found the idea interesting, I put a naive
approach for such a script together [1]. It works well for simple things
(i.e. anything not userdata).
The limitations (I could think of, there are bound to be more) I
outlined in the comment on top of the script. The main issue is
userdata, of course, which cannot be persisted automatically - so even
if you "only" have open file handles flying around, it will fail. That's
an issue with any approach on persistence, though.
Florian
[1] http://pastebin.com/Xv8NN85y
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment