fix: dispatch interrupts to all handlers, pipe coroutine resume status, term.read(), and thread waitForAll() fixes

This commit is contained in:
payonel 2017-06-10 17:53:30 -07:00
parent 067f217813
commit 74d77b1fdf
4 changed files with 51 additions and 40 deletions

View File

@ -40,20 +40,28 @@ end
local _pullSignal = computer.pullSignal local _pullSignal = computer.pullSignal
setmetatable(handlers, {__call=function(_,...)return _pullSignal(...)end}) setmetatable(handlers, {__call=function(_,...)return _pullSignal(...)end})
computer.pullSignal = function(...) -- dispatch computer.pullSignal = function(...) -- dispatch
local current_time = computer.uptime()
local interrupting = current_time - lastInterrupt > 1 and keyboard.isControlDown() and keyboard.isKeyDown(keyboard.keys.c)
if interrupting then
lastInterrupt = current_time
if keyboard.isAltDown() then
error("interrupted", 0)
end
event.push("interrupted", current_time)
end
local event_data = table.pack(handlers(...)) local event_data = table.pack(handlers(...))
local signal = event_data[1] local signal = event_data[1]
local ids = {} local ids = {}
for id in pairs(handlers) do for id in pairs(handlers) do
ids[#ids+1] = id ids[#ids+1] = id
end end
local time = computer.uptime()
for _,id in ipairs(ids) do for _,id in ipairs(ids) do
local handler = handlers[id] local handler = handlers[id]
-- timers have false keys -- timers have false keys
-- nil keys match anything -- nil keys match anything
if (handler.key == nil or handler.key == signal) or time >= handler.timeout then if (handler.key == nil or handler.key == signal) or current_time >= handler.timeout then
handler.times = handler.times - 1 handler.times = handler.times - 1
handler.timeout = computer.uptime() + handler.interval handler.timeout = current_time + handler.interval
if handler.times <= 0 then if handler.times <= 0 then
handlers[id] = nil handlers[id] = nil
end end
@ -196,15 +204,6 @@ function event.pullFiltered(...)
repeat repeat
local closest = math.min(deadline, time_to_nearest()) local closest = math.min(deadline, time_to_nearest())
local signal = table.pack(computer.pullSignal(closest - computer.uptime())) local signal = table.pack(computer.pullSignal(closest - computer.uptime()))
if event.shouldInterrupt() then
lastInterrupt = computer.uptime()
error("interrupted", 0)
end
if event.shouldSoftInterrupt() and (filter == nil or filter("interrupted", computer.uptime() - lastInterrupt)) then
local awaited = computer.uptime() - lastInterrupt
lastInterrupt = computer.uptime()
return "interrupted", awaited
end
if signal.n > 0 then if signal.n > 0 then
if not (seconds or filter) or filter == nil or filter(table.unpack(signal, 1, signal.n)) then if not (seconds or filter) or filter == nil or filter(table.unpack(signal, 1, signal.n)) then
return table.unpack(signal, 1, signal.n) return table.unpack(signal, 1, signal.n)
@ -213,19 +212,6 @@ function event.pullFiltered(...)
until computer.uptime() >= deadline until computer.uptime() >= deadline
end end
function event.shouldInterrupt()
return computer.uptime() - lastInterrupt > 1 and
keyboard.isControlDown() and
keyboard.isAltDown() and
keyboard.isKeyDown(keyboard.keys.c)
end
function event.shouldSoftInterrupt()
return computer.uptime() - lastInterrupt > 1 and
keyboard.isControlDown() and
keyboard.isKeyDown(keyboard.keys.c)
end
function event.timer(interval, callback, times) function event.timer(interval, callback, times)
checkArg(1, interval, "number") checkArg(1, interval, "number")
checkArg(2, callback, "function") checkArg(2, callback, "function")

View File

@ -184,7 +184,8 @@ function pipes.createCoroutineStack(fp, init, name)
if current_index then if current_index then
-- current should be waiting for yield -- current should be waiting for yield
pco.next = thread pco.next = thread
return true, _co.yield(...) -- pass args to resume next local t = table.pack(_co.yield(...)) -- pass args to resume next
return pco.last == nil and true or pco.last, table.unpack(t,1,t.n)
else else
-- the stack is not running -- the stack is not running
pco.next = false pco.next = false
@ -202,6 +203,7 @@ function pipes.createCoroutineStack(fp, init, name)
if pco.next and pco.next ~= thread then if pco.next and pco.next ~= thread then
local next = pco.next local next = pco.next
pco.next = false pco.next = false
pco.last = yield_args[1]
return pco.resume(next, table.unpack(yield_args,2,yield_args.n)) return pco.resume(next, table.unpack(yield_args,2,yield_args.n))
end end
end end

View File

@ -183,7 +183,8 @@ function term.read(history, dobreak, hint, pwchar, filter)
if not io.stdin.tty then if not io.stdin.tty then
return io.read() return io.read()
end end
local handler = history or {} history = history or {}
local handler = history
handler.hint = handler.hint or hint handler.hint = handler.hint or hint
local cursor = tty.internal.build_vertical_reader() local cursor = tty.internal.build_vertical_reader()

View File

@ -5,39 +5,56 @@ local computer = require("computer")
local thread = {} local thread = {}
local function waitForNumber(threads, n, timeout) local function waitForDeath(threads, timeout, all)
checkArg(1, threads, "table") checkArg(1, threads, "table")
checkArg(2, n, "number") checkArg(2, timeout, "number", "nil")
checkArg(3, timeout, "number", "nil") checkArg(3, all, "boolean")
timeout = timeout or math.huge timeout = timeout or math.huge
local mortician = {}
local timed_out = true
local deadline = computer.uptime() + timeout local deadline = computer.uptime() + timeout
while deadline > computer.uptime() do while deadline > computer.uptime() do
local num_dead = 0 local dieing = {}
local living = {}
for _,t in ipairs(threads) do for _,t in ipairs(threads) do
local result = t.process and t.process.data.result local result = t.process and t.process.data.result
local proc_ok = type(result) ~= "table" or result[1] local proc_ok = type(result) ~= "table" or result[1]
if t:status() ~= "running" -- suspended is considered dead to exit local ready_to_die = t:status() ~= "running" -- suspended is considered dead to exit
or not proc_ok then -- the thread is killed if its attached process has a non zero exit or not proc_ok -- the thread is killed if its attached process has a non zero exit
t:kill() if ready_to_die then
num_dead = num_dead + 1 dieing[#dieing + 1] = t
mortician[t] = true
else
living[#living + 1] = t
end end
end end
if num_dead >= n then
return true if all and #living == 0 or not all and #dieing > 0 then
timed_out = false
break
end end
-- resume each non dead thread -- resume each non dead thread
-- we KNOW all threads are event.pull blocked -- we KNOW all threads are event.pull blocked
event.pull() event.pull()
end end
for t in pairs(mortician) do
t:kill()
end
if timed_out then
return nil, "thread join timed out" return nil, "thread join timed out"
end
return true
end end
function thread.waitForAny(threads, timeout) function thread.waitForAny(threads, timeout)
return waitForNumber(threads, 1, timeout) return waitForDeath(threads, timeout, false)
end end
function thread.waitForAll(threads, timeout) function thread.waitForAll(threads, timeout)
return waitForNumber(threads, #threads, timeout) return waitForDeath(threads, timeout, true)
end end
local box_thread = {} local box_thread = {}
@ -137,11 +154,16 @@ function thread.create(fp, ...)
while true do while true do
local result = table.pack(t.pco.resume(fp_co, table.unpack(args, 1, args.n))) local result = table.pack(t.pco.resume(fp_co, table.unpack(args, 1, args.n)))
if t.pco.status(fp_co) == "dead" then if t.pco.status(fp_co) == "dead" then
if not result[1] then
event.onError(string.format("thread crashed: %s", tostring(result[2])))
end
break break
end end
args = table.pack(event.pull(table.unpack(result, 2, result.n))) args = table.pack(event.pull(table.unpack(result, 2, result.n)))
end end
mt.__status = "dead" mt.__status = "dead"
event.push("thread_exit")
t:detach()
end) end)
local handlers = event.handlers local handlers = event.handlers
local handlers_mt = getmetatable(handlers) local handlers_mt = getmetatable(handlers)