The Cancel Drill
The Cancel Drill: structured concurrency's core promise is that cancellation is PROMPT - stop means stop, not "finish everything and then agree you'd stopped". Three drills measure what each cancel path actually delivers. In round 10 this drill caught plan-wide cancel billing for every canceled task; the round-11 release fixed it, and this file is the acceptance test that keeps it fixed.
Testing & Verification
Round 10
Samuel Williams
exit 0
bundle exec ruby examples/cancel_drill.rb
a real captured run
CANCEL DRILL (6 jobs of 100ms, 2 lanes; full plan = 300ms)
drill 1 - cancel ONE in-flight task (at 30ms):
5 completed, 1 canceled; plan finished in 311ms
the proof the lane was freed is WHEN the next job started:
job2 began at 41ms - on the canceled fiber's lane,
immediately - not at 100ms when that job would have finished.
drill 2 - cancel ONE pending task:
5 completed, 1 canceled; agents actually ran: 5/6
the canceled job never started and never billed. queued work
canceled is money returned.
drill 3 - cancel_plan at 30ms:
status flipped to :canceled by 35ms, the plan returned in 35ms,
and only 2/6 agents ever ran (6 canceled) - the two that
were mid-flight when the order came. before round 11 this row
read "301ms, 6/6 agents executing": every task reported
:canceled while every agent ran to completion and billed.
the drill's verdict, updated: all three cancel paths now keep
the structured-concurrency promise. the round-11 fix stops the
fibers instead of the reactor handle - and does its bookkeeping
FIRST, because stopping a fiber frees its slot and synchronously
admits the next waiter, which must already read as canceled in
that instant. cancellation is a race against your own scheduler;
this drill is the finish-line camera, and it stays in the repo
so the race gets re-run on every change.
source
# frozen_string_literal: true # The Cancel Drill: structured concurrency's core promise is that # cancellation is PROMPT - stop means stop, not "finish everything # and then agree you'd stopped". Three drills measure what each # cancel path actually delivers. In round 10 this drill caught # plan-wide cancel billing for every canceled task; the round-11 # release fixed it, and this file is the acceptance test that keeps # it fixed. # # bundle exec ruby examples/cancel_drill.rb # # Runs offline; every claim below is a measurement. require class="s">"bundler/setup" require class="s">"agentic" require class="s">"async" Agentic.logger.level = class="y">:fatal JOB = 0.1 def build(count, agent_runs) orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 2) epoch = Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) tasks = count.times.map { |i| Agentic:class="y">:Task.new(description: class="s">"job#{i}", agent_spec: {class="s">"name" => class="s">"w", class="s">"instructions" => class="s">"w"}) } tasks.each do |task| orchestrator.add_task(task, agent: ->(_t) { agent_runs << [task.description, Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) - epoch] sleep(JOB) class="y">:ok }) end [orchestrator, tasks] end def states(orchestrator) orchestrator.instance_variable_get(:@execution_state).transform_values(&class="y">:size) .reject { |_, v| v.zero? }.map { |k, v| class="s">"#{v} #{k}" }.join(class="s">", ") end puts class="s">"CANCEL DRILL (6 jobs of #{(JOB * 1000).to_i}ms, 2 lanes; full plan = 300ms)" puts # --- drill 1: surgical cancel of one IN-FLIGHT task --------------------------- runs = [] orchestrator, tasks = build(6, runs) elapsed = nil Sync do runner = Async { orchestrator.execute_plan } Async do sleep(0.03) # job0 and job1 are mid-sleep orchestrator.cancel_task(tasks[0].id) end started = Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) runner.wait elapsed = Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) - started end third_start = (runs[2][1] * 1000).round puts class="s">" drill 1 - cancel ONE in-flight task (at 30ms):" puts class="s">" #{states(orchestrator)}; plan finished in #{(elapsed * 1000).round}ms" puts class="s">" the proof the lane was freed is WHEN the next job started:" puts class="s">" #{runs[2][0]} began at #{third_start}ms - on the canceled fiber's lane," puts class="s">" immediately - not at 100ms when that job would have finished." puts # --- drill 2: surgical cancel of one PENDING task ------------------------------ runs2 = [] orchestrator2, tasks2 = build(6, runs2) Sync do runner = Async { orchestrator2.execute_plan } Async do sleep(0.03) orchestrator2.cancel_task(tasks2[5].id) # still queued behind the lanes end runner.wait end puts class="s">" drill 2 - cancel ONE pending task:" puts class="s">" #{states(orchestrator2)}; agents actually ran: #{runs2.size}/6" puts class="s">" the canceled job never started and never billed. queued work" puts class="s">" canceled is money returned." puts # --- drill 3: cancel_plan mid-flight ------------------------------------------- runs3 = [] orchestrator3, = build(6, runs3) flip_ms = wall = nil Sync do started = Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) runner = Async { orchestrator3.execute_plan } Async do sleep(0.03) orchestrator3.cancel_plan flip_ms = ((Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) - started) * 1000).round end runner.wait wall = Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) - started end puts class="s">" drill 3 - cancel_plan at 30ms:" puts class="s">" status flipped to class="y">:canceled by #{flip_ms}ms, the plan returned in #{(wall * 1000).round}ms," puts class="s">" and only #{runs3.size}/6 agents ever ran (#{states(orchestrator3)}) - the two that" puts class="s">" were mid-flight when the order came. before round 11 this row" puts class="s">" read \"301ms, 6/6 agents executing\class="s">": every task reported" puts class="s">" class="y">:canceled while every agent ran to completion and billed." puts puts class="s">" the drill's verdict, updated: all three cancel paths now keep" puts class="s">" the structured-concurrency promise. the round-11 fix stops the" puts class="s">" fibers instead of the reactor handle - and does its bookkeeping" puts class="s">" FIRST, because stopping a fiber frees its slot and synchronously" puts class="s">" admits the next waiter, which must already read as canceled in" puts class="s">" that instant. cancellation is a race against your own scheduler;" puts class="s">" this drill is the finish-line camera, and it stays in the repo" puts class="s">" so the race gets re-run on every change."