Class: RubyLLM::MCP::Handlers::Promise

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_llm/mcp/handlers/promise.rb

Overview

Promise implementation for async operations

Instance Method Summary collapse

Constructor Details

#initializePromise

Initialize a new promise



9
10
11
12
13
14
15
16
17
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 9

def initialize
  @state = :pending
  @value = nil
  @reason = nil
  @mutex = Mutex.new
  @condition = ConditionVariable.new
  @then_callbacks = []
  @catch_callbacks = []
end

Instance Method Details

#catch(&callback) ⇒ Promise

Register a callback for rejection

Parameters:

  • callback (Proc)

    callback to execute on rejection

Returns:

  • (Promise)

    returns self for chaining



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 45

def catch(&callback)
  should_execute = false
  reason_to_use = nil

  @mutex.synchronize do
    if @state == :rejected
      # Already rejected, will execute immediately
      should_execute = true
      reason_to_use = @reason
    elsif @state == :pending
      # Still pending, register callback
      @catch_callbacks << callback
    end
  end

  # Execute outside mutex
  execute_callback_sync(callback, reason_to_use) if should_execute
  self
end

#fulfilled?Boolean

Check if promise is fulfilled

Returns:

  • (Boolean)


125
126
127
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 125

def fulfilled?
  @mutex.synchronize { @state == :fulfilled }
end

#pending?Boolean

Check if promise is pending

Returns:

  • (Boolean)


120
121
122
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 120

def pending?
  @mutex.synchronize { @state == :pending }
end

#reasonObject



173
174
175
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 173

def reason
  @mutex.synchronize { @reason }
end

#reject(reason) ⇒ Object

Reject the promise with a reason

Parameters:

  • reason (Object)

    the rejection reason



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 94

def reject(reason)
  callbacks_to_execute = nil

  @mutex.synchronize do
    return unless @state == :pending

    @state = :rejected
    @reason = reason

    # Capture callbacks to execute outside mutex
    callbacks_to_execute = @catch_callbacks.dup
    @then_callbacks.clear
    @catch_callbacks.clear

    # Signal waiting threads
    @condition.broadcast
  end

  # Execute callbacks outside mutex to avoid deadlocks
  # Use synchronous execution to maintain order and allow tests to work
  callbacks_to_execute&.each do |callback|
    execute_callback_sync(callback, reason)
  end
end

#rejected?Boolean

Check if promise is rejected

Returns:

  • (Boolean)


130
131
132
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 130

def rejected?
  @mutex.synchronize { @state == :rejected }
end

#resolve(value) ⇒ Object

Resolve the promise with a value

Parameters:

  • value (Object)

    the resolved value



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 67

def resolve(value)
  callbacks_to_execute = nil

  @mutex.synchronize do
    return unless @state == :pending

    @state = :fulfilled
    @value = value

    # Capture callbacks to execute outside mutex
    callbacks_to_execute = @then_callbacks.dup
    @then_callbacks.clear
    @catch_callbacks.clear

    # Signal waiting threads
    @condition.broadcast
  end

  # Execute callbacks outside mutex to avoid deadlocks
  # Use synchronous execution to maintain order and allow tests to work
  callbacks_to_execute&.each do |callback|
    execute_callback_sync(callback, value)
  end
end

#settled?Boolean

Check if promise is settled (fulfilled or rejected)

Returns:

  • (Boolean)


135
136
137
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 135

def settled?
  !pending?
end

#stateObject



165
166
167
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 165

def state
  @mutex.synchronize { @state }
end

#then(&callback) ⇒ Promise

Register a callback for successful resolution

Parameters:

  • callback (Proc)

    callback to execute on resolution

Returns:

  • (Promise)

    returns self for chaining



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 22

def then(&callback)
  should_execute = false
  value_to_use = nil

  @mutex.synchronize do
    if @state == :fulfilled
      # Already fulfilled, will execute immediately
      should_execute = true
      value_to_use = @value
    elsif @state == :pending
      # Still pending, register callback
      @then_callbacks << callback
    end
  end

  # Execute outside mutex
  execute_callback_sync(callback, value_to_use) if should_execute
  self
end

#valueObject



169
170
171
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 169

def value
  @mutex.synchronize { @value }
end

#wait(timeout: nil) ⇒ Object

Wait for promise to settle with optional timeout

Parameters:

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

    timeout in seconds

Returns:

  • (Object)

    resolved value or raises error



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/ruby_llm/mcp/handlers/promise.rb', line 142

def wait(timeout: nil)
  @mutex.synchronize do
    # Wait until promise is settled
    if timeout
      deadline = Time.now + timeout
      while @state == :pending
        remaining = deadline - Time.now
        if remaining <= 0
          raise Timeout::Error, "Promise timed out after #{timeout} seconds"
        end

        @condition.wait(@mutex, remaining)
      end
    else
      @condition.wait(@mutex) while @state == :pending
    end

    # Return value or raise error
    return @value if @state == :fulfilled
    raise @reason if @state == :rejected
  end
end