From cdfdb277f69d2543f10fc929d8beaf2fc0b91a91 Mon Sep 17 00:00:00 2001 From: payonel Date: Thu, 22 Jun 2017 16:46:52 -0700 Subject: [PATCH] replacing yield_all with yield_past to control stack yielding This change should allow popen, pipes, and threads to specify with exactness how many coroutines back they want to yield. This makes popen and threads immune to internal coroutine scenarios Also using a code cleanup provided by @SDPhantom --- .../loot/openos/boot/01_process.lua | 11 ++-- .../opencomputers/loot/openos/lib/pipe.lua | 53 ++++++++++++------- .../opencomputers/loot/openos/lib/thread.lua | 4 +- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua index 9bab80513..8d1929bbc 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua @@ -6,7 +6,11 @@ local _coroutine = coroutine -- real coroutine backend _G.coroutine = setmetatable( { resume = function(co, ...) - return assert(process.info(co), "thread has no proc").data.coroutine_handler.resume(co, ...) + local proc = process.info(co) + -- proc is nil if the process closed, natural resume will likely complain the coroutine is dead + -- but if proc is dead and an aborted coroutine is alive, it doesn't have any proc data like stack info + -- if the user really wants to resume it, let them + return (proc and proc.data.coroutine_handler.resume or _coroutine.resume)(co, ...) end }, { @@ -51,10 +55,7 @@ end _coroutine.wrap = function(f) local thread = coroutine.create(f) return function(...) - local result_pack = table.pack(coroutine.resume(thread, ...)) - local result, reason = result_pack[1], result_pack[2] - assert(result, reason) - return select(2, table.unpack(result_pack)) + return select(2, coroutine.resume(thread, ...)) end end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/pipe.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/pipe.lua index 4f16688cd..80904f055 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/pipe.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/pipe.lua @@ -22,17 +22,18 @@ function pipe.createCoroutineStack(root, env, name) function pco.yield(...) return _root_co.yield(nil, ...) end - function pco.yield_all(...) - return _root_co.yield(true, ...) + function pco.yield_past(co, ...) + return _root_co.yield(co, ...) end function pco.resume(co, ...) checkArg(1, co, "thread") local args = table.pack(...) while true do -- for consecutive sysyields local result = table.pack(_root_co.resume(co, table.unpack(args, 1, args.n))) + local target = result[2] == true and pco.root or result[2] if not result[1] or _root_co.status(co) == "dead" then return table.unpack(result, 1, result.n) - elseif result[2] and pco.root ~= co then + elseif target and target ~= co then args = table.pack(_root_co.yield(table.unpack(result, 2, result.n))) else return true, table.unpack(result, 3, result.n) @@ -70,7 +71,8 @@ local pipe_stream = return self end -- not reading, it is requesting a yield - result = table.pack(coroutine.yield_all(table.unpack(result, 2, result.n))) + -- yield_past(true) will exit this coroutine stack + result = table.pack(coroutine.yield_past(true, table.unpack(result, 2, result.n))) result = table.pack(coroutine.resume(self.next, table.unpack(result, 1, result.n))) -- the request was for an event end end, @@ -113,7 +115,7 @@ local pipe_stream = -- natural yield (i.e. for events). To differentiate this yield from natural -- yields we set read_mode here, which the pipe_stream write detects self.read_mode = true - coroutine.yield_all() + coroutine.yield_past(self.next) -- next is the first croutine in this stack self.read_mode = false end local result = string.sub(self.buffer, 1, n) @@ -158,16 +160,24 @@ end local chain_stream = { - read = function(self, value) + read = function(self, value, ...) if self.io_stream.closed then return nil end - -- handler is currently on yield all [else we wouldn't have control here] - local read_ok, ret = self.pco.resume(self.pco.root, value) - -- ret can be non string when a process ends - ret = type(ret) == "string" and ret or nil - return select(read_ok and 2 or 1, nil, ret) + -- wake up prog + self.ready = false -- the pipe proc sets this true when ios completes + local ret = table.pack(coroutine.resume(self.pco.root, value, ...)) + if coroutine.status(self.pco.root) == "dead" then + return nil + elseif not ret[1] then + return table.unpack(ret, 1, ret.n) + end + if not self.ready then + -- prog yielded back without writing/reading + return self:read(coroutine.yield()) + end + return ret[2] end, write = function(self, ...) - return self:read(table.concat({...})) + return self:read(...) end, close = function(self) self.io_stream:close() @@ -181,8 +191,8 @@ function pipe.popen(prog, mode, env) end local r = mode == "r" - local key = r and "read" or "write" + local chain = {} -- to simplify the code - shell.execute is run within a function to pass (prog, env) -- if cmd_proc where to come second (mode=="w") then the pipe_proc would have to pass -- the starting args. which is possible, just more complicated @@ -194,22 +204,27 @@ function pipe.popen(prog, mode, env) -- the stream needs its own process for io local pipe_proc = process.load(function() local n = r and 0 or "" + local key = r and "read" or "write" local ios = stream.io_stream while not ios.closed do - n = coroutine.yield_all(ios[key](ios, n)) + -- read from pipe + local ret = table.pack(ios[key](ios, n)) + stream.ready = true + -- yield outside the chain now + n = coroutine.yield_past(chain[1], table.unpack(ret, 1, ret.n)) end end, nil, nil, "pipe_handler") - local pipe_index = r and 2 or 1 - local cmd_index = r and 1 or 2 - local chain = {[cmd_index]=cmd_proc, [pipe_index]=pipe_proc} + chain[r and 1 or 2] = cmd_proc + chain[r and 2 or 1] = pipe_proc -- link the cmd and pipe proc io pipe.buildPipeChain(chain) - local cmd_stack = process.info(chain[1]).data.coroutine_handler + local cmd_data = process.info(chain[1]).data + local cmd_stack = cmd_data.coroutine_handler -- store handle to io_stream from easy access later - stream.io_stream = process.info(chain[1]).data.io[1].stream + stream.io_stream = cmd_data.io[1].stream stream.pco = cmd_stack -- popen commands start out running, like threads diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/thread.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/thread.lua index f602e7b77..317b28a7e 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/thread.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/thread.lua @@ -213,11 +213,11 @@ function thread.create(fp, ...) function mt.process.data.pull(_, timeout) mt.register(timeout) - -- yield_all will yield this pco stack + -- yield_past(root) will yield until out of this thread -- the callback will resume this stack local event_data repeat - event_data = table.pack(t.pco.yield_all(timeout)) + event_data = table.pack(t.pco.yield_past(t.pco.root, timeout)) -- during sleep, we may have been suspended until t:status() ~= "suspended" return table.unpack(event_data, 1, event_data.n)