1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 16:49:31 +00:00

Proper checkpointing of mutable values

This commit is contained in:
Étienne Fildadut 2021-12-06 18:34:58 +01:00
parent 801df67461
commit 04c6683de8
14 changed files with 243 additions and 23 deletions

View file

@ -49,6 +49,8 @@ $ parallel
parallel: {main.var} parallel: {main.var}
~ main ~ main
(note: if two scripts try to modify the same value at the same time, one of them will win, but which one is undefined/a surprise)
``` ```
The purpose of this system is both to allow several scripts to run at the same time with an easy way to avoid interferences, and to make sure the global state is always in a consistent (and not in the middle of a calculation): since scripts can be interrupted at any time, when it is interrupted, anything that was changed between the last checkpoint and the interruption will be discarded. If you're a RDBMS person, that's more-or-less equivalent to a transaction with a repeatable read isolation level (without any sort of locking or lost update protection though). The purpose of this system is both to allow several scripts to run at the same time with an easy way to avoid interferences, and to make sure the global state is always in a consistent (and not in the middle of a calculation): since scripts can be interrupted at any time, when it is interrupted, anything that was changed between the last checkpoint and the interruption will be discarded. If you're a RDBMS person, that's more-or-less equivalent to a transaction with a repeatable read isolation level (without any sort of locking or lost update protection though).

View file

@ -479,11 +479,13 @@ local vm_mt = {
__index = function(variables, k) __index = function(variables, k)
local cache = getmetatable(variables).cache local cache = getmetatable(variables).cache
if cache[k] == nil then if cache[k] == nil then
cache[k] = copy(self.state.variables[k]) cache[k] = copy(self.state.variables[k], getmetatable(variables).copy_cache)
end end
return cache[k] return cache[k]
end, end,
cache = {} -- cache of previously read values, to get repeatable reads & handle mutable types without changing global state copy_cache = {}, -- table of [original table] = copied table
modified_tables = {}, -- list of modified tables (copies) that should be merged with global state on next checkpoint
cache = {} -- cache of previously read values (copies), to get repeatable reads & handle mutable types without changing global state
}), }),
interpreter = { interpreter = {
-- constant -- constant

View file

@ -1,6 +1,20 @@
--- replace values recursively in table t according to to_replace ([old table] = new table)
local function replace_in_table(t, to_replace, already_replaced)
already_replaced = already_replaced or {}
already_replaced[t] = true
for k, v in pairs(t) do
if to_replace[v] then
t[k] = to_replace[v]
elseif type(v) == "table" and not already_replaced[v] then
replace_in_table(v, to_replace, already_replaced)
end
end
end
local common local common
common = { common = {
--- recursively copy a table, handle cyclic references, no metatable --- recursively copy a table, handle cyclic references, no metatable, don't copy keys
-- cache is table with copied tables [original table] = copied value, will use temporary table is omitted
copy = function(t, cache) copy = function(t, cache)
if type(t) == "table" then if type(t) == "table" then
cache = cache or {} cache = cache or {}
@ -17,6 +31,33 @@ common = {
else else
return t return t
end end
end,
--- given a table t from which some copy was issued, the copy cache, and a list of tables from the copied version,
-- put theses copied tables in t in place of their original values, preserving references to non-modified values
replace_with_copied_values = function(t, cache, copied_to_replace)
-- reverse copy cache
local ehcac = {}
for k, v in pairs(cache) do ehcac[v] = k end
-- build table of [original table] = replacement copied table
local to_replace = {}
for _, v in ipairs(copied_to_replace) do
local original = ehcac[v]
if original then -- table doesn't have an original value if it's a new table...
to_replace[original] = v
end
end
-- fix references to not-modified tables in modified values
local not_modified = {}
for original, modified in pairs(cache) do
if not to_replace[original] then
not_modified[modified] = original
end
end
for _, m in ipairs(copied_to_replace) do
replace_in_table(m, not_modified)
end
-- replace
replace_in_table(t, to_replace)
end end
} }
return common return common

View file

@ -1,6 +1,6 @@
local atypes, ltypes local atypes, ltypes
local eval, run_block local eval, run_block
local copy local replace_with_copied_values
local common local common
--- copy some text & process it to be suited to be sent to Lua in an event --- copy some text & process it to be suited to be sent to Lua in an event
@ -49,17 +49,13 @@ common = {
global.aliases[alias] = fqm global.aliases[alias] = fqm
state.aliases[alias] = nil state.aliases[alias] = nil
end end
-- variable state -- merge modified mutable varables
-- move values modifed in-place from read cache to variables local mt = getmetatable(state.variables)
local cache = getmetatable(state.variables).cache replace_with_copied_values(global.variables, mt.copy_cache, mt.modified_tables)
for var, value in pairs(cache) do mt.copy_cache = {}
if value.modified then mt.modified = {}
value.modified = nil mt.cache = {}
state.variables[var] = value -- merge modified re-assigned variables
end
cache[var] = nil
end
-- merge modified variables
for var, value in pairs(state.variables) do for var, value in pairs(state.variables) do
global.variables[var] = value global.variables[var] = value
state.variables[var] = nil state.variables[var] = nil
@ -109,6 +105,23 @@ common = {
end end
end end
return true return true
elseif a.type == "function reference" then
if #a.value ~= #b.value then
return false
end
for _, aname in ipairs(a.value) do
local found = false
for _, bname in ipairs(b.value) do
if aname == bname then
found = true
break
end
end
if not found then
return false
end
end
return true
else else
return a.value == b.value return a.value == b.value
end end
@ -394,6 +407,6 @@ local types = require((...):gsub("interpreter%.common$", "stdlib.types"))
atypes, ltypes = types.anselme, types.lua atypes, ltypes = types.anselme, types.lua
eval = require((...):gsub("common$", "expression")) eval = require((...):gsub("common$", "expression"))
run_block = require((...):gsub("common$", "interpreter")).run_block run_block = require((...):gsub("common$", "interpreter")).run_block
copy = require((...):gsub("interpreter%.common$", "common")).copy replace_with_copied_values = require((...):gsub("interpreter%.common$", "common")).replace_with_copied_values
return common return common

View file

@ -1,5 +1,10 @@
local truthy, anselme, compare, is_of_type, identifier_pattern, format_identifier, find, get_variable local truthy, anselme, compare, is_of_type, identifier_pattern, format_identifier, find, get_variable
local function mark_as_modified(v)
local modified = getmetatable(anselme.running.state.variables).modified_tables
table.insert(modified, v)
end
local functions local functions
functions = { functions = {
-- discard left -- discard left
@ -107,23 +112,23 @@ functions = {
["()(l::list, i::number) := v"] = { ["()(l::list, i::number) := v"] = {
mode = "raw", mode = "raw",
value = function(l, i, v) value = function(l, i, v)
l.modified = true
local lv = l.type == "type" and l.value[1] or l local lv = l.type == "type" and l.value[1] or l
local iv = i.type == "type" and i.value[1] or i local iv = i.type == "type" and i.value[1] or i
lv.value[iv.value] = v lv.value[iv.value] = v
mark_as_modified(lv.value)
return v return v
end end
}, },
["()(l::list, k::string) := v"] = { ["()(l::list, k::string) := v"] = {
mode = "raw", mode = "raw",
value = function(l, k, v) value = function(l, k, v)
l.modified = true
local lv = l.type == "type" and l.value[1] or l local lv = l.type == "type" and l.value[1] or l
local kv = k.type == "type" and k.value[1] or k local kv = k.type == "type" and k.value[1] or k
-- update index -- update index
for _, x in ipairs(lv.value) do for _, x in ipairs(lv.value) do
if x.type == "pair" and compare(x.value[1], kv) then if x.type == "pair" and compare(x.value[1], kv) then
x.value[2] = v x.value[2] = v
mark_as_modified(x.value)
return v return v
end end
end end
@ -132,6 +137,7 @@ functions = {
type = "pair", type = "pair",
value = { kv, v } value = { kv, v }
}) })
mark_as_modified(lv.value)
return v return v
end end
}, },
@ -192,33 +198,33 @@ functions = {
["insert(l::list, v)"] = { ["insert(l::list, v)"] = {
mode = "raw", mode = "raw",
value = function(l, v) value = function(l, v)
l.modified = true
local lv = l.type == "type" and l.value[1] or l local lv = l.type == "type" and l.value[1] or l
table.insert(lv.value, v) table.insert(lv.value, v)
mark_as_modified(lv.value)
return l return l
end end
}, },
["insert(l::list, i::number, v)"] = { ["insert(l::list, i::number, v)"] = {
mode = "raw", mode = "raw",
value = function(l, i, v) value = function(l, i, v)
l.modified = true
local lv = l.type == "type" and l.value[1] or l local lv = l.type == "type" and l.value[1] or l
local iv = i.type == "type" and i.value[1] or i local iv = i.type == "type" and i.value[1] or i
table.insert(lv.value, iv.value, v) table.insert(lv.value, iv.value, v)
mark_as_modified(lv.value)
return l return l
end end
}, },
["remove(l::list)"] = { ["remove(l::list)"] = {
mode = "untyped raw", mode = "untyped raw",
value = function(l) value = function(l)
l.modified = true mark_as_modified(l.value)
return table.remove(l.value) return table.remove(l.value)
end end
}, },
["remove(l::list, i::number)"] = { ["remove(l::list, i::number)"] = {
mode = "untyped raw", mode = "untyped raw",
value = function(l, i) value = function(l, i)
l.modified = true mark_as_modified(l.value)
return table.remove(l.value, i.value) return table.remove(l.value, i.value)
end end
}, },

View file

@ -44,7 +44,7 @@ local function write_result(filebase, result)
o:write(ser(result)) o:write(ser(result))
o:write("\n--[[\n") o:write("\n--[[\n")
for _, v in ipairs(result) do for _, v in ipairs(result) do
o:write(inspect(v).."\n") o:write(inspect(v):gsub("]]", "] ]").."\n") -- professional-level bandaid when ]] appear in the output
end end
o:write("]]--") o:write("]]--")
o:close() o:close()

View file

@ -0,0 +1,17 @@
:post run = "check"
:a = [1]
:b = [2]
~ a!insert(b)
§ c
~ b!insert(3)
§ d
~ b!insert(4)
$ check
\[1,\[2,3,4]]: {a}

View file

@ -0,0 +1,21 @@
local _={}
_[8]={}
_[7]={}
_[6]={text="[1, [2, 3, 4]]",tags=_[8]}
_[5]={text="[1,[2,3,4]]: ",tags=_[7]}
_[4]={_[5],_[6]}
_[3]={"return"}
_[2]={"text",_[4]}
_[1]={"return"}
return {_[1],_[2],_[3]}
--[[
{ "return" }
{ "text", { {
tags = {},
text = "[1,[2,3,4] ]: "
}, {
tags = {},
text = "[1, [2, 3, 4] ]"
} } }
{ "return" }
]]--

View file

@ -0,0 +1,19 @@
:post run = "check"
:a = [1]
:b = [2]
~ a!insert(b)
§ c
~ b!insert(3)
§ d
~ b!insert(4)
~ error("abort")
$ check
\[1,\[2,3]]: {a}

View file

@ -0,0 +1,21 @@
local _={}
_[8]={}
_[7]={}
_[6]={text="[1, [2, 3]]",tags=_[8]}
_[5]={text="[1,[2,3]]: ",tags=_[7]}
_[4]={_[5],_[6]}
_[3]={"return"}
_[2]={"text",_[4]}
_[1]={"error","abort; in Lua function \"error\"; at test/tests/merge nested mutable error bis.ans:16"}
return {_[1],_[2],_[3]}
--[[
{ "error", 'abort; in Lua function "error"; at test/tests/merge nested mutable error bis.ans:16' }
{ "text", { {
tags = {},
text = "[1,[2,3] ]: "
}, {
tags = {},
text = "[1, [2, 3] ]"
} } }
{ "return" }
]]--

View file

@ -0,0 +1,19 @@
:post run = "check"
:a = [1]
:b = [2]
~ a!insert(b)
§ c
~ b!insert(3)
§ d
~ a!insert(4)
~ error("abort")
$ check
\[1,\[2,3]]: {a}

View file

@ -0,0 +1,21 @@
local _={}
_[8]={}
_[7]={}
_[6]={text="[1, [2, 3]]",tags=_[8]}
_[5]={text="[1,[2,3]]: ",tags=_[7]}
_[4]={_[5],_[6]}
_[3]={"return"}
_[2]={"text",_[4]}
_[1]={"error","abort; in Lua function \"error\"; at test/tests/merge nested mutable error.ans:16"}
return {_[1],_[2],_[3]}
--[[
{ "error", 'abort; in Lua function "error"; at test/tests/merge nested mutable error.ans:16' }
{ "text", { {
tags = {},
text = "[1,[2,3] ]: "
}, {
tags = {},
text = "[1, [2, 3] ]"
} } }
{ "return" }
]]--

View file

@ -0,0 +1,17 @@
:post run = "check"
:a = [1]
:b = [2]
~ a!insert(b)
§ c
~ b!insert(3)
§ d
~ a!insert(4)
$ check
\[1,\[2,3],4]: {a}

View file

@ -0,0 +1,21 @@
local _={}
_[8]={}
_[7]={}
_[6]={text="[1, [2, 3], 4]",tags=_[8]}
_[5]={text="[1,[2,3],4]: ",tags=_[7]}
_[4]={_[5],_[6]}
_[3]={"return"}
_[2]={"text",_[4]}
_[1]={"return"}
return {_[1],_[2],_[3]}
--[[
{ "return" }
{ "text", { {
tags = {},
text = "[1,[2,3],4]: "
}, {
tags = {},
text = "[1, [2, 3], 4]"
} } }
{ "return" }
]]--