Class: LittleGhost::Execution

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

Overview

Runs one dormant Run in the background while the caller remains free to serve health checks, deliver interjections, or coordinate shutdown.

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

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

The Runtime selects a scheduled fiber or worker thread for the execution. LittleGhost copies the caller's ExecutionState, but not other application fiber-local or thread-local values. The Run continues to own its workspace, sandbox, session, entrypoint, and registered resources. close requests cooperative cancellation and waits for the execution and any in-flight interjection calls.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(run, event_consumer: nil) ⇒ Execution

:nodoc:

Raises:

  • (ArgumentError)


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

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
  @mutex = Mutex.new
  @condition = ConditionVariable.new
  @active_interjections = 0
  @closing = false
  @task_runner = run.runtime.task_runner
end

Instance Attribute Details

#runObject (readonly)

The supervised Run.



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. If the work cannot start, this method closes run before raising.

The optional block receives each StreamEvent from the fiber or thread running the Execution. It must not depend on a particular thread and should not pause the scheduler or retain sensitive event content longer than the application requires.



33
34
35
# File 'lib/little_ghost/execution.rb', line 33

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

Instance Method Details

#active?Boolean

Indicates that the Execution or an interjection call is still active.

Returns:

  • (Boolean)


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

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

#cancelObject

Requests cooperative cancellation and returns self.



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

def cancel
  run.cancellation_token.cancel
  self
end

#close(deadline: nil) ⇒ Object

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



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

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

#errorObject

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



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

def error
  @mutex.synchronize { @worker }&.error
end

#finished?Boolean

Indicates that the Execution and all interjection calls have finished.

Returns:

  • (Boolean)


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

def finished?
  !active?
end

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

Prepares and delivers one interjection to the active run.

payload may be a message or a Hash containing message and the options accepted by Run#interject. 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.



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

def interject(payload = nil, **options)
  if payload.nil? && options.key?(:message)
    payload = options.delete(:message)
  end
  interjection_started = false
  begin_interjection!
  interjection_started = true
  run.interject_with do
    prepared = run.prepare_interjection(interjection_payload(payload, options))
    interjection_arguments(prepared, options)
  end
ensure
  finish_interjection! if interjection_started
end

#stateObject

Returns :pending, :running, or :finished.



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

def state
  @mutex.synchronize { @state }
end

#wait(deadline: nil) ⇒ Object

Waits for the Execution and in-flight interjections, 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 Execution is re-raised after all supervised work finishes.



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

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

  run
end