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
setmetatable(handlers, {__call=function(_,...)return _pullSignal(...)end})
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 signal = event_data[1]
local ids = {}
for id in pairs(handlers) do
ids[#ids+1] = id
end
local time = computer.uptime()
for _,id in ipairs(ids) do
local handler = handlers[id]
-- timers have false keys
-- 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.timeout = computer.uptime() + handler.interval
handler.timeout = current_time + handler.interval
if handler.times <= 0 then
handlers[id] = nil
end
@ -196,15 +204,6 @@ function event.pullFiltered(...)
repeat
local closest = math.min(deadline, time_to_nearest())
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 not (seconds or filter) or filter == nil or filter(table.unpack(signal, 1, signal.n)) then
return table.unpack(signal, 1, signal.n)
@ -213,19 +212,6 @@ function event.pullFiltered(...)
until computer.uptime() >= deadline
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)
checkArg(1, interval, "number")
checkArg(2, callback, "function")

View File

@ -184,7 +184,8 @@ function pipes.createCoroutineStack(fp, init, name)
if current_index then
-- current should be waiting for yield
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
-- the stack is not running
pco.next = false
@ -202,6 +203,7 @@ function pipes.createCoroutineStack(fp, init, name)
if pco.next and pco.next ~= thread then
local next = pco.next
pco.next = false
pco.last = yield_args[1]
return pco.resume(next, table.unpack(yield_args,2,yield_args.n))
end
end

View File

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

View File

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