Class: Rubino::Interaction::CancelToken
- Inherits:
-
Object
- Object
- Rubino::Interaction::CancelToken
- Defined in:
- lib/rubino/interaction/cancel_token.rb
Overview
Thread-safe cooperative cancellation flag passed through the interaction stack (Runner -> Lifecycle -> Loop -> LLM adapter). The chat TUI flips it on Esc / second Ctrl+C, and the LLM stream callback raises Rubino::Interrupted at the next chunk boundary so the turn aborts without leaking the worker thread or losing buffered output.
Cancellation is one-shot: once cancelled, it stays cancelled. Build a fresh token per turn rather than reusing across turns.
No Mutex on purpose. The flag is written exactly once (false -> true, never back) and only ever read otherwise — a single-writer, monotonic boolean. Under MRI’s GVL a lone ivar read/write is atomic, so no lock is needed for correctness. Critically, #cancel! runs from a SIGINT Signal.trap block, and Mutex#lock is forbidden in a trap context (Ruby bug #14222: “can’t be called from trap context”). A mutex here made the chat trap raise ThreadError, the flag never flipped, and the turn ran on. Keep this lock-free and trap-safe.
Instance Attribute Summary collapse
-
#reason ⇒ Object
readonly
Why the turn was cancelled — distinguishes a deliberate user interrupt (Esc / Ctrl+C) from an EXTERNAL teardown (SIGTERM/SIGHUP from systemd, a terminal close, or a supervisor kill).
Instance Method Summary collapse
-
#cancel!(reason: :user) ⇒ Object
reasonrecords WHY: :user (Esc/Ctrl+C, default) or :external (SIGTERM/SIGHUP teardown). - #cancelled? ⇒ Boolean
-
#check! ⇒ Object
Raises Interrupted if the token has been cancelled.
-
#initialize ⇒ CancelToken
constructor
A new instance of CancelToken.
Constructor Details
#initialize ⇒ CancelToken
Returns a new instance of CancelToken.
31 32 33 34 |
# File 'lib/rubino/interaction/cancel_token.rb', line 31 def initialize @cancelled = false @reason = :user end |
Instance Attribute Details
#reason ⇒ Object (readonly)
Why the turn was cancelled — distinguishes a deliberate user interrupt (Esc / Ctrl+C) from an EXTERNAL teardown (SIGTERM/SIGHUP from systemd, a terminal close, or a supervisor kill). Both unwind the turn the same way, but the result LABEL must not claim “interrupted by user” when no user interrupted (#361b). Defaults to :user — the overwhelmingly common case and the one the historical message described.
29 30 31 |
# File 'lib/rubino/interaction/cancel_token.rb', line 29 def reason @reason end |
Instance Method Details
#cancel!(reason: :user) ⇒ Object
reason records WHY: :user (Esc/Ctrl+C, default) or :external (SIGTERM/SIGHUP teardown). One-shot like @cancelled — the first reason wins, so a later cancel! can’t relabel a genuine user interrupt.
39 40 41 42 |
# File 'lib/rubino/interaction/cancel_token.rb', line 39 def cancel!(reason: :user) @reason = reason unless @cancelled @cancelled = true end |
#cancelled? ⇒ Boolean
44 45 46 |
# File 'lib/rubino/interaction/cancel_token.rb', line 44 def cancelled? @cancelled end |
#check! ⇒ Object
Raises Interrupted if the token has been cancelled. Used as a poll point inside hot loops (per-chunk in streams, per-iteration in the agent loop). The Interrupted carries a reason-appropriate message so an external-signal teardown is not mislabeled as a user interrupt (#361b).
52 53 54 55 56 |
# File 'lib/rubino/interaction/cancel_token.rb', line 52 def check! return unless cancelled? raise Rubino::Interrupted.new(reason: @reason) end |