Class: Fractor::ShutdownHandler
- Inherits:
-
Object
- Object
- Fractor::ShutdownHandler
- Defined in:
- lib/fractor/shutdown_handler.rb
Overview
Manages the shutdown process for a Supervisor. Responsible for gracefully stopping all components in the correct order.
This class extracts shutdown logic from Supervisor to follow the Single Responsibility Principle.
Instance Method Summary collapse
-
#complete? ⇒ Boolean
Check if the shutdown process has completed.
-
#initialize(workers, wakeup_ractor, timer_thread, performance_monitor, main_loop_thread: nil, debug: false, continuous_mode: false) ⇒ ShutdownHandler
constructor
A new instance of ShutdownHandler.
-
#shutdown(wait_for_completion: false, timeout: 10) ⇒ void
Execute a graceful shutdown of all supervisor components.
-
#signal_all_workers ⇒ void
Send shutdown signal to all workers.
-
#signal_wakeup_ractor ⇒ void
Signal the wakeup ractor to unblock Ractor.select.
-
#status_summary ⇒ Hash
Get a summary of the shutdown status.
-
#stop_performance_monitor ⇒ void
Stop the performance monitor if it's enabled.
-
#stop_timer_thread ⇒ void
Wait for the timer thread to finish if it exists.
-
#wait_for_shutdown_completion(timeout = 10) ⇒ Boolean
Wait for all components to finish after shutdown signals have been sent.
Constructor Details
#initialize(workers, wakeup_ractor, timer_thread, performance_monitor, main_loop_thread: nil, debug: false, continuous_mode: false) ⇒ ShutdownHandler
Returns a new instance of ShutdownHandler.
10 11 12 13 14 15 16 17 18 19 |
# File 'lib/fractor/shutdown_handler.rb', line 10 def initialize(workers, wakeup_ractor, timer_thread, performance_monitor, main_loop_thread: nil, debug: false, continuous_mode: false) @workers = workers @wakeup_ractor = wakeup_ractor @timer_thread = timer_thread @performance_monitor = performance_monitor @main_loop_thread = main_loop_thread @debug = debug @continuous_mode = continuous_mode end |
Instance Method Details
#complete? ⇒ Boolean
Check if the shutdown process has completed. This is useful for testing and monitoring.
154 155 156 157 158 159 |
# File 'lib/fractor/shutdown_handler.rb', line 154 def complete? timer_stopped = @timer_thread.nil? || !@timer_thread.alive? workers_stopped = @workers.empty? || @workers.all?(&:closed?) timer_stopped && workers_stopped end |
#shutdown(wait_for_completion: false, timeout: 10) ⇒ void
This method returns an undefined value.
Execute a graceful shutdown of all supervisor components. Components are stopped in the correct order to prevent issues:
- Stop performance monitor (to stop metric collection)
- Skip stopping timer thread in continuous mode (it will exit when workers close)
- Stop timer thread in batch mode (to stop periodic wakeups)
- Signal wakeup ractor (to unblock Ractor.select)
- Signal all workers (to stop processing)
- Wait for main loop thread and workers to finish
33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/fractor/shutdown_handler.rb', line 33 def shutdown(wait_for_completion: false, timeout: 10) stop_performance_monitor # Only stop timer thread in batch mode. In continuous mode, the timer thread # will exit on its own when workers are closed (it checks this condition). stop_timer_thread unless @continuous_mode signal_wakeup_ractor signal_all_workers wait_for_shutdown_completion(timeout) if wait_for_completion end |
#signal_all_workers ⇒ void
This method returns an undefined value.
Send shutdown signal to all workers. Workers should gracefully finish their current work and exit.
138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/fractor/shutdown_handler.rb', line 138 def signal_all_workers @workers.each do |w| begin w.send(:shutdown) rescue StandardError # Ignore errors when sending shutdown to workers nil end puts "Sent shutdown signal to #{w.name}" if @debug end end |
#signal_wakeup_ractor ⇒ void
This method returns an undefined value.
Signal the wakeup ractor to unblock Ractor.select. This is done first to allow the main loop to process the shutdown.
123 124 125 126 127 128 129 130 131 132 |
# File 'lib/fractor/shutdown_handler.rb', line 123 def signal_wakeup_ractor return unless @wakeup_ractor begin @wakeup_ractor.send(:shutdown) puts "Sent shutdown signal to wakeup ractor" if @debug rescue StandardError => e puts "Error sending shutdown to wakeup ractor: #{e.}" if @debug end end |
#status_summary ⇒ Hash
Get a summary of the shutdown status.
164 165 166 167 168 169 170 171 172 |
# File 'lib/fractor/shutdown_handler.rb', line 164 def status_summary { performance_monitor: @performance_monitor&.send(:monitoring?) || false, timer_thread: @timer_thread&.alive? || false, wakeup_ractor: !@wakeup_ractor.nil?, workers_count: @workers.size, workers_closed: @workers.count(&:closed?), } end |
#stop_performance_monitor ⇒ void
This method returns an undefined value.
Stop the performance monitor if it's enabled.
94 95 96 97 98 99 100 101 102 103 |
# File 'lib/fractor/shutdown_handler.rb', line 94 def stop_performance_monitor return unless @performance_monitor begin @performance_monitor.stop puts "Performance monitor stopped" if @debug rescue StandardError => e puts "Error stopping performance monitor: #{e.}" if @debug end end |
#stop_timer_thread ⇒ void
This method returns an undefined value.
Wait for the timer thread to finish if it exists.
108 109 110 111 112 113 114 115 116 117 |
# File 'lib/fractor/shutdown_handler.rb', line 108 def stop_timer_thread return unless @timer_thread # Only wait if thread is alive if @timer_thread.alive? @timer_thread.join(1) # Wait up to 1 second @timer_thread.kill # Ensure thread is stopped puts "Timer thread stopped" if @debug end end |
#wait_for_shutdown_completion(timeout = 10) ⇒ Boolean
Wait for all components to finish after shutdown signals have been sent. This waits for both the main loop thread (if provided) and all workers to close. This is important for tests and for ensuring clean shutdown.
52 53 54 55 56 57 58 59 60 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/fractor/shutdown_handler.rb', line 52 def wait_for_shutdown_completion(timeout = 10) start_time = Time.now poll_interval = 0.1 loop do # Check if timeout exceeded break if Time.now - start_time > timeout # Check main loop thread status (if provided and alive) main_loop_done = @main_loop_thread.nil? || !@main_loop_thread.alive? # Check if all workers are closed workers_done = @workers.empty? || @workers.all?(&:closed?) # If both main loop and workers are done, we're finished if main_loop_done && workers_done puts "All components closed successfully" if @debug return true end # Show status while waiting if @debug closed_count = @workers.count(&:closed?) main_status = @main_loop_thread&.alive? ? "running" : "stopped" puts "Waiting for shutdown: main_loop=#{main_status}, workers=#{closed_count}/#{@workers.size} closed" end sleep(poll_interval) end # Timeout exceeded if @debug closed_count = @workers.count(&:closed?) main_status = @main_loop_thread&.alive? ? "running" : "stopped" puts "Shutdown timeout: main_loop=#{main_status}, workers=#{closed_count}/#{@workers.size} closed after #{timeout}s" end false end |