Class: LittleGhost::Execution

Inherits:
Object
  • Object
show all
Defined in:
lib/little_ghost/execution.rb

Overview

Runs one dormant Run in a supervised worker while the caller remains free to serve health checks, deliver interruptions, or coordinate process shutdown.

execution = agent.start_execution(message: "Investigate transfer 481") do |event|
event_buffer << event
end

execution.interrupt_response(message: "Include the latest ledger entry")
execution.wait(deadline: Time.now + 30)
execution.run.completed? # => true

An execution owns its worker. The worker receives a snapshot of the caller's request-scoped ExecutionState. The Run continues to own its workspace, sandbox, session, entrypoint, and registered resources. close requests cooperative cancellation and waits for both the worker and in-flight interruption calls.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(run, event_consumer: nil) ⇒ Execution

:nodoc:

Raises:

  • (ArgumentError)


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/little_ghost/execution.rb', line 36

def initialize(run, event_consumer: nil) # :nodoc:
  raise ArgumentError, "run must be a LittleGhost::Run" unless run.is_a?(Run)
  unless event_consumer.nil? || event_consumer.respond_to?(:call)
    raise ArgumentError, "event consumer must be callable"
  end

  @run = run
  @event_consumer = event_consumer
  @state = :pending
  @error = nil
  @mutex = Mutex.new
  @condition = ConditionVariable.new
  @active_interruptions = 0
  @closing = false
  @execution_state = ExecutionState.capture
end

Instance Attribute Details

#runObject (readonly)

The supervised Run and an exception raised outside the Run's ordinary terminal outcome, such as event delivery or cleanup failure.



23
24
25
# File 'lib/little_ghost/execution.rb', line 23

def run
  @run
end

Class Method Details

.start(run, &event_consumer) ⇒ Object

Starts run immediately and returns its supervising Execution.

The optional block receives each StreamEvent on the worker thread. It must be safe to call from that thread and should not retain sensitive event content longer than the application requires.



31
32
33
# File 'lib/little_ghost/execution.rb', line 31

def start(run, &event_consumer)
  new(run, event_consumer:).send(:start)
end

Instance Method Details

#active?Boolean

Indicates that the worker or an interruption call is still active.

Returns:

  • (Boolean)


64
65
66
# File 'lib/little_ghost/execution.rb', line 64

def active?
  @mutex.synchronize { @state != :finished || @active_interruptions.positive? }
end

#cancelObject

Requests cooperative cancellation and returns self.



96
97
98
99
# File 'lib/little_ghost/execution.rb', line 96

def cancel
  run.cancellation_token.cancel
  self
end

#close(deadline: nil) ⇒ Object

Prevents new interruptions, requests cancellation, and waits for shutdown. The operation is idempotent. deadline has the same meaning as in #wait.



117
118
119
120
121
# File 'lib/little_ghost/execution.rb', line 117

def close(deadline: nil)
  @mutex.synchronize { @closing = true }
  cancel
  wait(deadline:)
end

#errorObject

Returns an event-delivery or cleanup exception raised by the worker.



59
60
61
# File 'lib/little_ghost/execution.rb', line 59

def error
  @mutex.synchronize { @error }
end

#finished?Boolean

Indicates that the worker and all interruption calls have finished.

Returns:

  • (Boolean)


69
70
71
# File 'lib/little_ghost/execution.rb', line 69

def finished?
  !active?
end

#interrupt_response(payload = nil, **options) ⇒ Object

Prepares and delivers one interruption to the active run.

payload may be a message or a Hash containing message and the options accepted by Run#interrupt_response. Runtime hooks receive the Hash before delivery, allowing them to materialize trusted application attachments. Calls may overlap, but close prevents new calls and waits for calls that have already begun.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/little_ghost/execution.rb', line 80

def interrupt_response(payload = nil, **options)
  if payload.nil? && options.key?(:message)
    payload = options.delete(:message)
  end
  interruption_started = false
  begin_interruption!
  interruption_started = true
  run.interrupt_response_with do
    prepared = run.prepare_interruption(interruption_payload(payload, options))
    interruption_arguments(prepared, options)
  end
ensure
  finish_interruption! if interruption_started
end

#stateObject

Returns :pending, :running, or :finished.



54
55
56
# File 'lib/little_ghost/execution.rb', line 54

def state
  @mutex.synchronize { @state }
end

#wait(deadline: nil) ⇒ Object

Waits for the worker and in-flight interruptions, then returns the Run.

deadline is an absolute Time. Reaching it raises DeadlineExceededError without cancelling the run. An event-delivery or cleanup failure raised by the worker is re-raised after all supervised work finishes.



106
107
108
109
110
111
112
113
# File 'lib/little_ghost/execution.rb', line 106

def wait(deadline: nil)
  worker = wait_until_finished(deadline:)
  worker.join
  caught = error
  raise caught if caught

  run
end