Class: RubyLLM::MCP::Native::CancellableOperation

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_llm/mcp/native/cancellable_operation.rb

Overview

Wraps server-initiated requests to support cancellation. The operation tracks terminal state so cancellation outcomes are explicit.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(request_id) ⇒ CancellableOperation

Returns a new instance of CancellableOperation.



11
12
13
14
15
16
17
18
# File 'lib/ruby_llm/mcp/native/cancellable_operation.rb', line 11

def initialize(request_id)
  @request_id = request_id
  @state = :pending
  @mutex = Mutex.new
  @thread = nil
  @result = nil
  @error = nil
end

Instance Attribute Details

#request_idObject (readonly)

Returns the value of attribute request_id.



9
10
11
# File 'lib/ruby_llm/mcp/native/cancellable_operation.rb', line 9

def request_id
  @request_id
end

Instance Method Details

#cancelSymbol

Returns :cancelled, :already_cancelled, :already_completed.

Returns:

  • (Symbol)

    :cancelled, :already_cancelled, :already_completed



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/ruby_llm/mcp/native/cancellable_operation.rb', line 25

def cancel
  thread_to_cancel = nil

  @mutex.synchronize do
    case @state
    when :cancelled, :cancelling
      return :already_cancelled
    when :completed
      return :already_completed
    when :pending
      @state = :cancelled
      return :cancelled
    when :running
      @state = :cancelling
      thread_to_cancel = @thread
    end
  end

  if thread_to_cancel&.alive?
    thread_to_cancel.raise(
      Errors::RequestCancelled.new(
        message: "Request #{@request_id} was cancelled",
        request_id: @request_id
      )
    )
  else
    @mutex.synchronize do
      @state = :cancelled if @state == :cancelling
    end
  end

  :cancelled
end

#cancelled?Boolean

Returns:

  • (Boolean)


20
21
22
# File 'lib/ruby_llm/mcp/native/cancellable_operation.rb', line 20

def cancelled?
  @mutex.synchronize { %i[cancelling cancelled].include?(@state) }
end

#executeObject

Execute a block in a separate thread so cancellation can interrupt execution. Returns the block result or re-raises non-cancellation exceptions.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/ruby_llm/mcp/native/cancellable_operation.rb', line 61

def execute(&)
  return nil if cancelled?

  worker = @mutex.synchronize do
    return nil if %i[cancelled cancelling].include?(@state)

    @state = :running
    @thread = Thread.new do
      Thread.current.abort_on_exception = false
      begin
        @result = yield
      rescue Errors::RequestCancelled, StandardError => e
        @error = e
      end
    end
  end

  worker.join
  raise @error if @error && !@error.is_a?(Errors::RequestCancelled)

  @result
ensure
  @mutex.synchronize do
    if @state == :running || @state == :cancelling
      @state = @error.is_a?(Errors::RequestCancelled) ? :cancelled : :completed
    end
    @thread = nil
  end
end

#stateObject



91
92
93
# File 'lib/ruby_llm/mcp/native/cancellable_operation.rb', line 91

def state
  @mutex.synchronize { @state }
end

#threadObject



95
96
97
# File 'lib/ruby_llm/mcp/native/cancellable_operation.rb', line 95

def thread
  @mutex.synchronize { @thread }
end