Last active
August 29, 2015 13:56
-
-
Save akavel/9254298 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 | |
This file contains hidden or 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
| 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