Class: Phronomy::CancellationScope

Inherits:
Object
  • Object
show all
Defined in:
lib/phronomy/cancellation_scope.rb

Overview

Represents a bounded execution scope that owns a CancellationToken and optionally a Deadline.

+CancellationScope+ replaces ad-hoc +Timeout.timeout+ calls in agent and tool code. All work performed within a scope should observe the scope's token; when the scope is cancelled (explicitly or by deadline expiry) the token is cancelled and all child tasks that check it will stop.

Examples:

Time-bounded invocation

scope = Phronomy::CancellationScope.new.deadline_in(30)
result = scope.pop_queue(completion_queue) do
  raise Phronomy::TimeoutError, "timed out"
end

Explicit cancellation

scope = Phronomy::CancellationScope.new
Phronomy::Runtime.instance.spawn(name: "worker") do
  scope.token.raise_if_cancelled!
  # ... do work ...
end
scope.cancel! if some_condition

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent_token: nil) ⇒ CancellationScope

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of CancellationScope.

Parameters:

  • parent_token (CancellationToken, nil) (defaults to: nil)

    when provided, cancellation of the parent token is propagated to this scope's token via a callback (for explicit cancel) and/or the Runtime timer queue (for monotonic deadline expiry). No polling thread is spawned.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/phronomy/cancellation_scope.rb', line 37

def initialize(parent_token: nil)
  @token = Phronomy::CancellationToken.new
  @deadline = nil

  if parent_token
    # Propagate explicit cancel() from parent to child via callback.
    parent_token.on_cancel { @token.cancel! }

    # Propagate monotonic-deadline expiry from parent to child via the
    # timer queue (avoids a polling thread).
    remaining = parent_token.remaining_monotonic_seconds
    if !remaining.nil?
      if remaining <= 0
        @token.cancel!
      else
        Phronomy::Runtime.instance.timer_queue.schedule(seconds: remaining) do
          @token.cancel!
        end
      end
    end
  end
end

Instance Attribute Details

#deadlineDeadline? (readonly)

Returns the deadline attached to this scope, if any.

Returns:

  • (Deadline, nil)

    the deadline attached to this scope, if any



30
31
32
# File 'lib/phronomy/cancellation_scope.rb', line 30

def deadline
  @deadline
end

#tokenCancellationToken (readonly)

Returns the token owned by this scope.

Returns:



27
28
29
# File 'lib/phronomy/cancellation_scope.rb', line 27

def token
  @token
end

Instance Method Details

#cancel!void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Cancels this scope immediately.



74
75
76
# File 'lib/phronomy/cancellation_scope.rb', line 74

def cancel!
  @token.cancel!
end

#cancelled?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns +true+ if this scope has been cancelled.

Returns:

  • (Boolean)


81
82
83
# File 'lib/phronomy/cancellation_scope.rb', line 81

def cancelled?
  @token.cancelled?
end

#deadline_in(seconds) ⇒ self

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Attaches a deadline that will cancel this scope after +seconds+.

Parameters:

  • seconds (Numeric)

    timeout duration

Returns:

  • (self)


65
66
67
68
69
# File 'lib/phronomy/cancellation_scope.rb', line 65

def deadline_in(seconds)
  @deadline = Phronomy::Deadline.in(seconds)
  @deadline.attach_to(@token)
  self
end

#pop_queue(queue, fallback_timeout: nil) { ... } ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Pops from +queue+ with a timeout derived from the attached deadline (or +fallback_timeout+ seconds when no deadline is set). If the pop times out, the scope is cancelled and the block is called (or a TimeoutError raised).

Parameters:

  • queue (Phronomy::AsyncQueue)

    the queue to pop from

  • fallback_timeout (Numeric, nil) (defaults to: nil)

    used when no deadline is attached

Yields:

  • called when the operation times out

Returns:

  • (Object)

    the popped value

Raises:



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/phronomy/cancellation_scope.rb', line 103

def pop_queue(queue, fallback_timeout: nil)
  timeout = @deadline&.remaining_seconds || fallback_timeout
  result = if timeout
    queue.pop(timeout: timeout)
  else
    queue.pop
  end

  if result.nil?
    cancel!
    if block_given?
      yield
    else
      raise Phronomy::TimeoutError, "CancellationScope timed out"
    end
  end

  result
end

#remaining_secondsFloat?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the remaining time in seconds before the deadline expires, or +nil+ when no deadline is set.

Returns:

  • (Float, nil)


89
90
91
# File 'lib/phronomy/cancellation_scope.rb', line 89

def remaining_seconds
  @deadline&.remaining_seconds
end