Class: Workhorse::Daemon
- Inherits:
-
Object
- Object
- Workhorse::Daemon
- Defined in:
- lib/workhorse/daemon.rb
Overview
Daemon class for managing multiple worker processes. Provides functionality to start, stop, restart, and monitor worker processes through a simple Ruby DSL.
Defined Under Namespace
Classes: ShellHandler, Worker
Instance Attribute Summary collapse
-
#lockfile ⇒ File?
Lockfile handle to close in forked children.
-
#workers ⇒ Array<Worker>
readonly
Array of defined workers.
Instance Method Summary collapse
-
#initialize(pidfile: nil, quiet: false) {|ScopedEnv| ... } ⇒ Daemon
constructor
Creates a new daemon instance.
-
#restart ⇒ Integer
Restarts all workers by stopping and then starting them.
-
#restart_logging ⇒ Integer
Sends HUP signal to all workers to restart their logging.
-
#soft_restart ⇒ Integer
Sends USR1 signal to all workers to initiate a soft restart.
-
#start(quiet: false) ⇒ Integer
Starts all defined workers.
-
#status(quiet: false) ⇒ Integer
Checks the status of all workers.
-
#stop(kill = false, quiet: false) ⇒ Integer
Stops all running workers.
-
#watch ⇒ Integer
Watches workers and starts them if they’re not running.
-
#worker(name = 'Job Worker') { ... } ⇒ void
Defines a worker process.
Constructor Details
#initialize(pidfile: nil, quiet: false) {|ScopedEnv| ... } ⇒ Daemon
Creates a new daemon instance.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/workhorse/daemon.rb', line 47 def initialize(pidfile: nil, quiet: false, &_block) @pidfile = pidfile @quiet = quiet @workers = [] yield ScopedEnv.new(self, [:worker]) @count = @workers.count fail 'No workers are defined.' if @count < 1 FileUtils.mkdir_p('tmp/pids') if @pidfile.nil? @pidfile = @count > 1 ? 'tmp/pids/workhorse.%i.pid' : 'tmp/pids/workhorse.pid' elsif @count > 1 && !@pidfile.include?('%s') fail 'Pidfile must include placeholder "%s" for worker id when specifying a count > 1.' elsif @count == 0 && @pidfile.include?('%s') fail 'Pidfile must not include placeholder "%s" for worker id when specifying a count of 1.' end end |
Instance Attribute Details
#lockfile ⇒ File?
Returns Lockfile handle to close in forked children.
39 40 41 |
# File 'lib/workhorse/daemon.rb', line 39 def lockfile @lockfile end |
#workers ⇒ Array<Worker> (readonly)
Returns Array of defined workers.
35 36 37 |
# File 'lib/workhorse/daemon.rb', line 35 def workers @workers end |
Instance Method Details
#restart ⇒ Integer
Restarts all workers by stopping and then starting them.
213 214 215 216 |
# File 'lib/workhorse/daemon.rb', line 213 def restart stop return start end |
#restart_logging ⇒ Integer
Sends HUP signal to all workers to restart their logging. Useful for log rotation without full process restart.
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/workhorse/daemon.rb', line 222 def restart_logging code = 0 Workhorse.debug_log("restart_logging: sending HUP to #{@workers.count} worker(s)") for_each_worker do |worker| _pid_file, pid, active = read_pid(worker) Workhorse.debug_log("restart_logging: worker ##{worker.id} (#{worker.name}): pid=#{pid.inspect}, active=#{active.inspect}") next unless pid && active begin Process.kill 'HUP', pid Workhorse.debug_log("restart_logging: HUP sent successfully to PID #{pid}") puts "Worker (#{worker.name}) ##{worker.id}: Sent signal for restart-logging" rescue Errno::ESRCH Workhorse.debug_log("restart_logging: HUP failed for PID #{pid}: process not found") warn "Worker (#{worker.name}) ##{worker.id}: Could not send signal for restart-logging, process not found" code = 2 end end Workhorse.debug_log("restart_logging: done, exit code=#{code}") return code end |
#soft_restart ⇒ Integer
Sends USR1 signal to all workers to initiate a soft restart. Workers will finish their current jobs before shutting down. The watch mechanism will then start fresh workers. This method returns immediately (fire-and-forget).
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/workhorse/daemon.rb', line 255 def soft_restart code = 0 Workhorse.debug_log("Daemon: sending USR1 to #{@workers.count} worker(s)") for_each_worker do |worker| _pid_file, pid, active = read_pid(worker) Workhorse.debug_log("Daemon soft_restart: worker ##{worker.id} (#{worker.name}): pid=#{pid.inspect}, active=#{active.inspect}") next unless pid && active begin Process.kill 'USR1', pid Workhorse.debug_log("Daemon: USR1 sent successfully to PID #{pid}") puts "Worker (#{worker.name}) ##{worker.id}: Sent soft-restart signal" rescue Errno::ESRCH Workhorse.debug_log("Daemon: USR1 failed for PID #{pid}: process not found") warn "Worker (#{worker.name}) ##{worker.id}: Process not found" code = 2 end end Workhorse.debug_log("Daemon soft_restart: done, exit code=#{code}") return code end |
#start(quiet: false) ⇒ Integer
Starts all defined workers.
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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/workhorse/daemon.rb', line 82 def start(quiet: false) code = 0 Workhorse.debug_log("Daemon: starting #{@workers.count} worker(s)") # Holds messages in format [[<message>, <severity>]] = [] for_each_worker do |worker| pid_file, pid, active = read_pid(worker) if pid_file && pid && active Workhorse.debug_log("Daemon start: worker ##{worker.id} (#{worker.name}) already running (PID #{pid})") << ["Worker ##{worker.id} (#{worker.name}): Already started (PID #{pid})", 2] unless quiet code = 2 elsif pid_file Workhorse.debug_log("Daemon start: worker ##{worker.id} (#{worker.name}) has stale pid file (PID #{pid.inspect}), starting") File.delete pid_file shutdown_file = pid ? Workhorse::Worker.shutdown_file_for(pid) : nil shutdown_file = nil if shutdown_file && !File.exist?(shutdown_file) << ["Worker ##{worker.id} (#{worker.name}): Starting (stale pid file)", 1] unless quiet || shutdown_file start_worker worker FileUtils.rm(shutdown_file) if shutdown_file else Workhorse.debug_log("Daemon start: worker ##{worker.id} (#{worker.name}) not running, starting") << ["Worker ##{worker.id} (#{worker.name}): Starting", 1] unless quiet start_worker worker end end if .any? min = .min_by(&:last)[1] # Only print messages if there is at least one message with severity 1 if min == 1 .each { |(, _severity)| warn } end end Workhorse.debug_log("Daemon: start complete, exit code=#{code}") return code end |
#status(quiet: false) ⇒ Integer
Checks the status of all workers.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/workhorse/daemon.rb', line 163 def status(quiet: false) code = 0 for_each_worker do |worker| pid_file, pid, active = read_pid(worker) if pid_file && pid && active Workhorse.debug_log("Daemon status: worker ##{worker.id} (#{worker.name}) running (PID #{pid})") puts "Worker ##{worker.id} (#{worker.name}): Running" unless quiet elsif pid_file Workhorse.debug_log("Daemon status: worker ##{worker.id} (#{worker.name}) not running (stale PID file, PID #{pid.inspect})") warn "Worker ##{worker.id} (#{worker.name}): Not running (stale PID file)" unless quiet code = 2 else Workhorse.debug_log("Daemon status: worker ##{worker.id} (#{worker.name}) not running (no pid file)") warn "Worker ##{worker.id} (#{worker.name}): Not running" unless quiet code = 2 end end Workhorse.debug_log("Daemon: status complete, exit code=#{code}") return code end |
#stop(kill = false, quiet: false) ⇒ Integer
Stops all running workers.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/workhorse/daemon.rb', line 132 def stop(kill = false, quiet: false) code = 0 Workhorse.debug_log("Daemon: stopping #{@workers.count} worker(s) (kill=#{kill})") for_each_worker do |worker| pid_file, pid, active = read_pid(worker) if pid_file && pid && active Workhorse.debug_log("Daemon stop: worker ##{worker.id} (#{worker.name}) running (PID #{pid}), stopping") puts "Worker (#{worker.name}) ##{worker.id}: Stopping" unless quiet stop_worker pid_file, pid, kill: kill elsif pid_file Workhorse.debug_log("Daemon stop: worker ##{worker.id} (#{worker.name}) stale pid file (PID #{pid.inspect})") File.delete pid_file puts "Worker (#{worker.name}) ##{worker.id}: Already stopped (stale PID file)" unless quiet else Workhorse.debug_log("Daemon stop: worker ##{worker.id} (#{worker.name}) already stopped") warn "Worker (#{worker.name}) ##{worker.id}: Already stopped" unless quiet code = 2 end end Workhorse.debug_log("Daemon: stop complete, exit code=#{code}") return code end |
#watch ⇒ Integer
Watches workers and starts them if they’re not running. In Rails environments, respects the tmp/stop.txt file.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/workhorse/daemon.rb', line 191 def watch if defined?(Rails) should_be_running = !File.exist?(Rails.root.join('tmp/stop.txt')) else should_be_running = true end status_code = status(quiet: true) Workhorse.debug_log("Daemon watch: should_be_running=#{should_be_running}, status_code=#{status_code}") if should_be_running && status_code != 0 Workhorse.debug_log('Daemon watch: starting workers') return start(quiet: Workhorse.silence_watcher) else Workhorse.debug_log('Daemon watch: no action needed') return 0 end end |