Class: Phronomy::EventLoop
- Inherits:
-
Object
- Object
- Phronomy::EventLoop
- Defined in:
- lib/phronomy/engine/event_loop.rb
Overview
Runtime-owned event loop that manages all FSMSession instances.
A dedicated real thread reads from a Runtime-local AsyncQueue and dispatches events to their target FSMSession. The EventLoop is created at most once by its owning Runtime and is never restarted after shutdown.
FSMSession handlers run on the EventLoop thread. They must not perform blocking work or call synchronous invoke APIs from that thread.
Constant Summary collapse
- SYSTEM_CHANNEL_ID =
"__event_loop__"
Instance Method Summary collapse
- #average_lag_seconds ⇒ Float private
-
#begin_draining ⇒ Object
private
Stops external admission at the Runtime boundary while allowing already accepted work to complete on this EventLoop.
-
#current? ⇒ Boolean
private
Returns true only for this EventLoop's dispatcher Task.
- #idle? ⇒ Boolean private
-
#initialize(runtime:) ⇒ EventLoop
constructor
private
A new instance of EventLoop.
- #last_lag_seconds ⇒ Float private
- #max_lag_seconds ⇒ Float private
-
#post(event) ⇒ Boolean
private
Posts an event.
-
#register(fsm_session, completion: nil) ⇒ Phronomy::Concurrency::AsyncQueue, Phronomy::Task
private
Registers an FSMSession and returns its completion queue.
-
#shutdown(deadline:, cancel_grace:) ⇒ Symbol
private
Runtime-only terminal shutdown.
- #state ⇒ Symbol private
- #task_alive? ⇒ Boolean private
-
#wait_until_idle(deadline) ⇒ Boolean
private
Waits until no queued :start or active session remains.
Constructor Details
#initialize(runtime:) ⇒ EventLoop
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 EventLoop.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/phronomy/engine/event_loop.rb', line 20 def initialize(runtime:) @runtime = runtime @queue = Phronomy::Concurrency::AsyncQueue.new @fsms = {} @waiting = {} @lifecycle_mutex = Mutex.new @idle_cond = ConditionVariable.new @shutdown_mutex = Mutex.new @state = :running @outstanding_sessions = 0 @cancel_requested = false @shutdown_status = nil @lag_mutex = Mutex.new @last_lag_ns = 0 @max_lag_ns = 0 @dispatch_count = 0 @total_lag_ns = 0 @task = @runtime.__spawn_event_loop_service { run_loop } end |
Instance Method Details
#average_lag_seconds ⇒ Float
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.
57 58 59 60 61 62 63 |
# File 'lib/phronomy/engine/event_loop.rb', line 57 def average_lag_seconds @lag_mutex.synchronize do return 0.0 if @dispatch_count.zero? @total_lag_ns.to_f / @dispatch_count / 1_000_000_000.0 end end |
#begin_draining ⇒ 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.
Stops external admission at the Runtime boundary while allowing already accepted work to complete on this EventLoop.
141 142 143 144 145 146 |
# File 'lib/phronomy/engine/event_loop.rb', line 141 def begin_draining @lifecycle_mutex.synchronize do @state = :draining if @state == :running end self end |
#current? ⇒ 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 only for this EventLoop's dispatcher Task.
128 129 130 |
# File 'lib/phronomy/engine/event_loop.rb', line 128 def current? Phronomy::Task.current.equal?(@task) end |
#idle? ⇒ 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.
150 151 152 |
# File 'lib/phronomy/engine/event_loop.rb', line 150 def idle? @lifecycle_mutex.synchronize { @outstanding_sessions.zero? } end |
#last_lag_seconds ⇒ Float
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.
45 46 47 |
# File 'lib/phronomy/engine/event_loop.rb', line 45 def last_lag_seconds @lag_mutex.synchronize { @last_lag_ns } / 1_000_000_000.0 end |
#max_lag_seconds ⇒ Float
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.
51 52 53 |
# File 'lib/phronomy/engine/event_loop.rb', line 51 def max_lag_seconds @lag_mutex.synchronize { @max_lag_ns } / 1_000_000_000.0 end |
#post(event) ⇒ 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.
Posts an event. Returns false after stopping begins.
Async completion callbacks may race with shutdown, so rejection is a boolean result rather than an exception.
117 118 119 120 121 122 123 124 |
# File 'lib/phronomy/engine/event_loop.rb', line 117 def post(event) @lifecycle_mutex.synchronize do return false unless accepting_events? @queue.push([event, monotonic_nanoseconds]) end true end |
#register(fsm_session, completion: nil) ⇒ Phronomy::Concurrency::AsyncQueue, Phronomy::Task
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.
Registers an FSMSession and returns its completion queue.
outstanding_sessions is incremented before the :start event is enqueued,
so shutdown also accounts for accepted sessions that have not yet been
dispatched.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/phronomy/engine/event_loop.rb', line 75 def register(fsm_session, completion: nil) if current? && !completion.is_a?(Phronomy::Task) raise Phronomy::Error, "Cannot call synchronous Workflow#invoke from an EventLoop action. " \ "Schedule work asynchronously instead." end completion_queue = completion || Phronomy::Concurrency::AsyncQueue.new scheduler = Phronomy::Runtime::Scheduler.current if scheduler && completion_queue.respond_to?(:expect_cross_thread_push) completion_queue.expect_cross_thread_push(scheduler) end event = Phronomy::Event.new( type: :start, target_id: SYSTEM_CHANNEL_ID, payload: {session: fsm_session, completion: completion_queue} ) @lifecycle_mutex.synchronize do ensure_accepting_registrations! @outstanding_sessions += 1 begin @queue.push([event, monotonic_nanoseconds]) rescue @outstanding_sessions -= 1 @idle_cond.broadcast if @outstanding_sessions.zero? raise end end completion_queue end |
#shutdown(deadline:, cancel_grace:) ⇒ Symbol
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.
Runtime-only terminal shutdown.
On graceful timeout the dispatcher is cancelled. Queue cleanup is only performed after the dispatcher is confirmed dead, so there is never more than one queue consumer.
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/phronomy/engine/event_loop.rb', line 180 def shutdown(deadline:, cancel_grace:) @shutdown_mutex.synchronize do return @shutdown_status if @shutdown_status if state == :failed join_until(deadline) @shutdown_status = :failed return @shutdown_status end begin_draining if wait_until_idle(deadline) begin_stopping_if_idle join_until(deadline) end @shutdown_status = if task_alive? cancel_and_cleanup(cancel_grace) elsif state == :failed :failed else finalize_terminated(:terminated) end end end |
#state ⇒ Symbol
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.
134 135 136 |
# File 'lib/phronomy/engine/event_loop.rb', line 134 def state @lifecycle_mutex.synchronize { @state } end |
#task_alive? ⇒ 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.
208 209 210 |
# File 'lib/phronomy/engine/event_loop.rb', line 208 def task_alive? @task&.alive? || false end |
#wait_until_idle(deadline) ⇒ 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.
Waits until no queued :start or active session remains.
158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/phronomy/engine/event_loop.rb', line 158 def wait_until_idle(deadline) @lifecycle_mutex.synchronize do until @outstanding_sessions.zero? remaining = deadline - monotonic_now return false if remaining <= 0 @idle_cond.wait(@lifecycle_mutex, remaining) end true end end |