diff options
Diffstat (limited to 'subagents/jobs.lua')
| -rw-r--r-- | subagents/jobs.lua | 35 |
1 files changed, 27 insertions, 8 deletions
diff --git a/subagents/jobs.lua b/subagents/jobs.lua index a774b27..9fe5c93 100644 --- a/subagents/jobs.lua +++ b/subagents/jobs.lua @@ -11,10 +11,9 @@ -- an edge trigger, never a count, so every wake drains the pipe, then drains -- `job:next_event()` to exhaustion, then checks `job:result()`. -- --- Waiting parks the CALLING coroutine — the tool handler's — and a poll --- callback resumes exactly that coroutine. subagents/workflow.lua's rule that --- a workflow callback must never run on a nested coroutine follows from this: --- the parked thread is the one the resume goes to. +-- Waiting parks the CALLING coroutine and a poll callback resumes that exact +-- coroutine. Foreground tools use their handler coroutine; background +-- workflows provide a dedicated coroutine anchored in their registry record. -- -- The wake pipe is therefore mandatory wherever luv is: a started job whose -- pipe or poll could not be armed is a start failure, not a degraded job. @@ -43,9 +42,9 @@ local READ_CHUNK = 4096 local M = {} --- The session-wide bound. A field, not a constant, so a caller (or a spec) can --- lower it without reaching into the queue. -M.MAX_CONCURRENT = 4 +-- The activation-time default is five; init.lua may replace it from the +-- layered `[subagents] max_concurrent` setting before any child can start. +M.MAX_CONCURRENT = 5 local handle_mt = {} handle_mt.__index = handle_mt @@ -58,6 +57,7 @@ local queued = {} local waiters = {} local running = 0 local pumping = false +local cancelling = false -- --------------------------------------------------------------------------- -- Wake pipes @@ -191,7 +191,7 @@ end -- Start queued jobs while the gate has room. Reentrant: a job that settles the -- instant it starts calls back in here, and the outer loop keeps going. function pump_queue() - if pumping then + if pumping or cancelling then return end pumping = true @@ -442,9 +442,28 @@ end -- The turn was interrupted: ask every child to stop. Cancellation is a request, -- not a settle — each job still reports its own cancelled result. function M.cancel_all() + cancelling = true for index = #live, 1, -1 do live[index]:cancel() end + cancelling = false + pump_queue() + wake() +end + +-- Close settled jobs without disturbing queued or running background work. +-- Called at ordinary turn boundaries; session teardown still uses close_all. +function M.reap() + local kept = {} + for _, handle in ipairs(live) do + if handle.settled ~= nil then + close_pipe(handle) + close_job(handle) + else + kept[#kept + 1] = handle + end + end + live = kept end -- The turn is over: cancel every child and drop the state. Never blocks — a |
