Implementation of Kino.Qx.run/2,3 and Kino.Qx.run!/2,3.
Wraps Qx.Hardware.run/3 with a live Kino.Frame status panel
(rendered above the eventual result) and a best-effort cancel
watcher that fires Qx.Hardware.cancel/3 if the caller cell
process dies during a run.
Architecture
caller cell process (Process.flag(:trap_exit, true))
│
├── frame = Kino.Frame.new() |> tap(&Kino.render/1)
│
├── watcher = spawn(...) ← unlinked
│ └── monitors caller; only fires Qx.Hardware.cancel/3
│ on the UNTRAPPABLE :kill path (see below)
│
├── worker = Task.async(fn -> Hardware.run(...) end)
│ └── on_status sends {:status, event} back to the caller
│
└── run_loop/1 (caller stays alive in a receive loop)
├─ {:status, event} → frame + job_id + caller cb
├─ {ref, result} → render terminal, :done, return
├─ {:DOWN, ref, ...} → worker crash, propagate
└─ {:EXIT, _, reason} → trappable interrupt (below)Interrupt semantics
Livebook's "Stop" may deliver :shutdown (trappable) or :kill
(untrappable). The blocking hardware call runs in a worker Task
so the caller stays in run_loop/1:
:shutdown(trappable) — the caller receives{:EXIT, _, :shutdown}, brutally stops the worker, issues the cancel once itself, signals the watcher:done(so the watcher stands down and does NOT cancel again on the caller's subsequent:DOWN), then raisesKino.Qx.Interruptedwith the last-seenjob_id. This is the now-true contract.:kill(untrappable) — the caller dies immediately without running its handler. Only here does the unlinked watcher fire the cancel (its{:DOWN, caller, reason != :normal}arm). NoKino.Qx.Interruptedis raised — the process is already gone.
Single-cancel gating
On the trappable path the caller cancels and then sends the watcher
:done. Erlang orders the :done message before the monitor
:DOWN generated when the caller later dies, so the watcher always
processes :done first and exits without cancelling — exactly one
Qx.Hardware.cancel/3 fires (the caller's). On the :kill path the
caller never sends :done, so exactly one cancel fires (the
watcher's).
Residual races (honest caveats)
- Spurious cancel. If the caller is killed in the narrow
window after the worker returns but before
send(watcher, :done), the watcher still sees an abnormal:DOWNand issues a cancel for an already-finished job.Qx.Hardware.cancel/3treats an IBM 404 as expected, so this is harmless noise. - Untrappable teardown. A
:kill(or a full Livebook session teardown that also kills the watcher) leaves the IBM job orphaned — it runs to completion and burns shots. This is the silent leak flagged in the plan's Risks section; only the upstream-side fix (job TTL / server cancel) fully closes it.
Summary
Functions
@spec run(Qx.QuantumCircuit.t(), Qx.Hardware.Config.t(), keyword()) :: {:ok, Qx.SimulationResult.t()} | {:error, term()}
@spec run!(Qx.QuantumCircuit.t(), Qx.Hardware.Config.t(), keyword()) :: Qx.SimulationResult.t()