diff --git a/mesecons_luacontroller/init.lua b/mesecons_luacontroller/init.lua index 1c411dd..a29b6da 100644 --- a/mesecons_luacontroller/init.lua +++ b/mesecons_luacontroller/init.lua @@ -590,8 +590,12 @@ local function load_memory(meta) end -local function save_memory(pos, meta, mem) - local memstring = minetest.serialize(remove_functions(mem)) +local function serialize_memory(mem) + return minetest.serialize(remove_functions(mem)) +end + + +local function save_serialized_memory(pos, meta, memstring) local memsize_max = mesecon.setting("luacontroller_memsize", 100000) if (#memstring <= memsize_max) then @@ -629,6 +633,8 @@ local function run_inner(pos, code, event) -- Create the sandbox and execute code local f, msg = create_sandbox(code, env) if not f then return false, msg end + local mem_use_limit = collectgarbage("count") + mesecon.setting("luacontroller_volatile_mem_limit", 2000) + local oom_msg = "not enough memory" -- Used to detect OOM errors, kind of a hack. -- Start string true sandboxing local onetruestring = getmetatable("") -- If a string sandbox is already up yet inconsistent, something is very wrong @@ -637,16 +643,43 @@ local function run_inner(pos, code, event) local success, msg = pcall(f) onetruestring.__index = string -- End string true sandboxing - if not success then return false, msg end + if collectgarbage("count") > mem_use_limit then + -- Volatile memory limit exceeded. + success = false + msg = oom_msg + end + -- Serialize the persistent memory. + local memstring + if success then + success, memstring = pcall(serialize_memory, env.mem) + if not success then + msg = memstring + -- Rethrow non-OOM errors. + if msg ~= oom_msg then error(msg, 0) end + end + end + if not success then + -- Handle OOM errors by collecting garbage. + if msg == oom_msg then + env, itbl, mem = nil -- Let the memory be collected. + collectgarbage() + print("Error: Luacontroller at " .. minetest.pos_to_string(pos) + .. " exhausted its available volatile memory. Controller overheats.") + burn_controller(pos) + end + return false, msg + end if type(env.port) ~= "table" then return false, "Ports set are invalid." end + local success, memstring = pcall(serialize_memory, env.mem) + if not success then return false, memstring end -- memstring is the error message here. -- Actually set the ports set_port_states(pos, env.port) -- Save memory. This may burn the luacontroller if a memory overflow occurs. - save_memory(pos, meta, env.mem) + save_serialized_memory(pos, meta, memstring) -- Execute deferred tasks for _, v in ipairs(itbl) do diff --git a/settingtypes.txt b/settingtypes.txt index 5627440..7e247d1 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -24,6 +24,11 @@ mesecon.luacontroller_digiline_maxlen (Digiline message size limit) int 50000 10 mesecon.luacontroller_maxevents (Controller execution time limit) int 10000 1000 100000 mesecon.luacontroller_memsize (Controller memory limit) int 100000 10000 1000000 +# The amount of server memory a controller is allowed, measured in kilobytes. +# This actually measures the memory usage after the script is done, not during its runtime. +# Thus, controllers can lag the server even with a low limit. +mesecon.luacontroller_volatile_mem_limit (Controller volatile memory limit) int 2000 10 10000000 + # Use node timer for interrupts (runs in active blocks only). # IID is ignored and at most one interrupt may be queued if this setting is enabled. mesecon.luacontroller_lightweight_interrupts (Lightweight interrupts) bool false