From 737f366741f54659b17bd9c96e2232eedb9735ee Mon Sep 17 00:00:00 2001
From: Thomas Rudin <thomas@rudin.li>
Date: Sat, 29 Dec 2018 21:48:32 +0100
Subject: [PATCH] LuaC: add lightweight interrupts (#449)

---
 mesecons_luacontroller/init.lua | 67 ++++++++++++++++++++++-----------
 settingtypes.txt                |  4 ++
 2 files changed, 48 insertions(+), 23 deletions(-)

diff --git a/mesecons_luacontroller/init.lua b/mesecons_luacontroller/init.lua
index 19b6479..1c93e48 100644
--- a/mesecons_luacontroller/init.lua
+++ b/mesecons_luacontroller/init.lua
@@ -266,32 +266,45 @@ local function remove_functions(x)
 	return x
 end
 
--- itbl: Flat table of functions to run after sandbox cleanup, used to prevent various security hazards
-local function get_interrupt(pos, itbl, send_warning)
-	-- iid = interrupt id
-	local function interrupt(time, iid)
-		-- NOTE: This runs within string metatable sandbox, so don't *rely* on anything of the form (""):y
-		-- Hence the values get moved out. Should take less time than original, so totally compatible
-		if type(time) ~= "number" then return end
-		table.insert(itbl, function ()
-			-- Outside string metatable sandbox, can safely run this now
-			local luac_id = minetest.get_meta(pos):get_int("luac_id")
-			-- Check if IID is dodgy, so you can't use interrupts to store an infinite amount of data.
-			-- Note that this is safe from alter-after-free because this code gets run after the sandbox has ended.
-			-- This runs outside of the timer and *shouldn't* harm perf. unless dodgy data is being sent in the first place
-			iid = remove_functions(iid)
-			local msg_ser = minetest.serialize(iid)
-			if #msg_ser <= mesecon.setting("luacontroller_interruptid_maxlen", 256) then
-				mesecon.queue:add_action(pos, "lc_interrupt", {luac_id, iid}, time, iid, 1)
-			else
-				send_warning("An interrupt ID was too large!")
-			end
+-- The setting affects API so is not intended to be changeable at runtime
+local get_interrupt
+if mesecon.setting("luacontroller_lightweight_interrupts", false) then
+	-- use node timer
+	get_interrupt = function(pos, itbl, send_warning)
+		return (function(time, iid)
+			if type(time) ~= "number" then error("Delay must be a number") end
+			if iid ~= nil then send_warning("Interrupt IDs are disabled on this server") end
+			table.insert(itbl, function() minetest.get_node_timer(pos):start(time) end)
 		end)
 	end
-	return interrupt
+else
+	-- use global action queue
+	-- itbl: Flat table of functions to run after sandbox cleanup, used to prevent various security hazards
+	get_interrupt = function(pos, itbl, send_warning)
+		-- iid = interrupt id
+		local function interrupt(time, iid)
+			-- NOTE: This runs within string metatable sandbox, so don't *rely* on anything of the form (""):y
+			-- Hence the values get moved out. Should take less time than original, so totally compatible
+			if type(time) ~= "number" then error("Delay must be a number") end
+			table.insert(itbl, function ()
+				-- Outside string metatable sandbox, can safely run this now
+				local luac_id = minetest.get_meta(pos):get_int("luac_id")
+				-- Check if IID is dodgy, so you can't use interrupts to store an infinite amount of data.
+				-- Note that this is safe from alter-after-free because this code gets run after the sandbox has ended.
+				-- This runs outside of the timer and *shouldn't* harm perf. unless dodgy data is being sent in the first place
+				iid = remove_functions(iid)
+				local msg_ser = minetest.serialize(iid)
+				if #msg_ser <= mesecon.setting("luacontroller_interruptid_maxlen", 256) then
+					mesecon.queue:add_action(pos, "lc_interrupt", {luac_id, iid}, time, iid, 1)
+				else
+					send_warning("An interrupt ID was too large!")
+				end
+			end)
+		end
+		return interrupt
+	end
 end
 
-
 -- Given a message object passed to digiline_send, clean it up into a form
 -- which is safe to transmit over the network and compute its "cost" (a very
 -- rough estimate of its memory usage).
@@ -414,7 +427,6 @@ local function get_digiline_send(pos, itbl, send_warning)
 	end
 end
 
-
 local safe_globals = {
 	-- Don't add pcall/xpcall unless willing to deal with the consequences (unless very careful, incredibly likely to allow killing server indirectly)
 	"assert", "error", "ipairs", "next", "pairs", "select",
@@ -651,6 +663,14 @@ local function reset(pos)
 	set_port_states(pos, {a=false, b=false, c=false, d=false})
 end
 
+local function node_timer(pos)
+	if minetest.registered_nodes[minetest.get_node(pos).name].is_burnt then
+		return false
+	end
+	run(pos, {type="interrupt"})
+	return false
+end
+
 -----------------------
 -- A.Queue callbacks --
 -----------------------
@@ -823,6 +843,7 @@ for d = 0, 1 do
 			mesecon.receptor_off(pos, output_rules)
 		end,
 		is_luacontroller = true,
+		on_timer = node_timer,
 		on_blast = mesecon.on_blastnode,
 	})
 end
diff --git a/settingtypes.txt b/settingtypes.txt
index 8be7be6..9259a83 100644
--- a/settingtypes.txt
+++ b/settingtypes.txt
@@ -24,6 +24,10 @@ 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
 
+# 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
+
 
 [mesecons_movestones]
 
-- 
GitLab