Module: Rbg
- Defined in:
- lib/rbg.rb,
lib/rbg/config.rb
Defined Under Namespace
Class Attribute Summary collapse
-
.child_processes ⇒ Object
An array of child PIDs for the current process which have been spawned.
-
.config_file ⇒ Object
The path to the config file that was specified.
Class Method Summary collapse
-
.config ⇒ Object
Return a configration object for this backgroundable application.
-
.fork_worker(id) ⇒ Object
Fork a single worker.
-
.fork_workers(n) ⇒ Object
Wrapper to fork multiple workers.
-
.kill_child_process(id) ⇒ Object
Kill a given child process.
-
.kill_child_processes ⇒ Object
Kill all child processes.
-
.load_config ⇒ Object
Load or reload the config file defined at startup.
-
.logger ⇒ Object
Return a logger object for this application.
-
.master_process ⇒ Object
This is the master process, it spawns some workers then loops.
-
.pid_from_file ⇒ Object
Get the PID from the pidfile defined in the config.
-
.reload(config_file, options = {}) ⇒ Object
Reload the running instance.
- .start(config_file, options = {}) ⇒ Object
-
.start_parent ⇒ Object
Creates a ‘parent’ process.
-
.stop(config_file, options = {}) ⇒ Object
Stop the running instance.
Class Attribute Details
.child_processes ⇒ Object
An array of child PIDs for the current process which have been spawned
9 10 11 |
# File 'lib/rbg.rb', line 9 def child_processes @child_processes end |
.config_file ⇒ Object
The path to the config file that was specified
12 13 14 |
# File 'lib/rbg.rb', line 12 def config_file @config_file end |
Class Method Details
.config ⇒ Object
Return a configration object for this backgroundable application.
15 16 17 |
# File 'lib/rbg.rb', line 15 def config @config ||= Rbg::Config.new end |
.fork_worker(id) ⇒ Object
Fork a single worker
130 131 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 158 159 160 161 162 163 164 165 |
# File 'lib/rbg.rb', line 130 def fork_worker(id) pid = fork do # Set process name $0="#{config.name}[#{id}]" # Ending workers on INT is not useful or desirable Signal.trap('INT', proc {}) Signal.trap('HUP', proc {}) # Restore normal behaviour Signal.trap('TERM', proc {Process.exit(0)}) # Execure before_fork code if config.after_fork.is_a?(Proc) self.config.after_fork.call end if self.config.script.is_a?(String) require self.config.script elsif self.config.script.is_a?(Proc) self.config.script.call end end # Print some debug info and save the pid logger.info "Spawned #{config.name}[#{id}] (with PID #{pid})" STDOUT.flush # Detach to eliminate Zombie processes later Process.detach(pid) # Save the worker PID into the Parent's child process list self.child_processes[id] ||= {} self.child_processes[id][:pid] ||= pid self.child_processes[id][:respawns] ||= 0 self.child_processes[id][:started_at] = Time.now end |
.fork_workers(n) ⇒ Object
Wrapper to fork multiple workers
123 124 125 126 127 |
# File 'lib/rbg.rb', line 123 def fork_workers(n) n.times do |i| self.fork_worker(i) end end |
.kill_child_process(id) ⇒ Object
Kill a given child process
168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/rbg.rb', line 168 def kill_child_process(id) if opts = self.child_processes[id] logger.info "Killing #{config.name}[#{id}] (with PID #{opts[:pid]})" STDOUT.flush begin Process.kill('TERM', opts[:pid]) rescue logger.info "Process already gone away" end end end |
.kill_child_processes ⇒ Object
Kill all child processes
181 182 183 184 185 186 |
# File 'lib/rbg.rb', line 181 def kill_child_processes logger.info 'Killing child processes...' STDOUT.flush self.child_processes.keys.each { |id| kill_child_process(id) } self.child_processes = Hash.new end |
.load_config ⇒ Object
Load or reload the config file defined at startup
251 252 253 254 255 256 257 258 |
# File 'lib/rbg.rb', line 251 def load_config @config = nil if File.exist?(self.config_file.to_s) load self.config_file else raise Error, "Configuration file not found at '#{config_file}'" end end |
.logger ⇒ Object
Return a logger object for this application
20 21 22 |
# File 'lib/rbg.rb', line 20 def logger self.config.logger end |
.master_process ⇒ Object
This is the master process, it spawns some workers then loops
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 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 248 |
# File 'lib/rbg.rb', line 189 def master_process # Log the master PID logger.info "New master process: #{Process.pid}" STDOUT.flush # Set the process name $0="#{self.config.name}[Master]" # Fork a Parent process # This will load the before_fork in a clean process then fork the script as required self.start_parent # A restart is not required yet... restart_needed = false # If we get a USR1, set this process as waiting for a restart Signal.trap("USR1", proc { logger.info "Master got a USR1." restart_needed = true }) # If we get a TERM, send the existing workers a TERM before bowing out Signal.trap("TERM", proc { logger.info "Master got a TERM." STDOUT.flush kill_child_processes Process.exit(0) }) # INT is useful for when we don't want to background Signal.trap("INT", proc { logger.info "Master got an INT." STDOUT.flush kill_child_processes Process.exit(0) }) Signal.trap('HUP', proc {}) # Main loop, we mostly idle, but check if the parent we created has died and exit loop do sleep 2 if restart_needed STDOUT.flush self.kill_child_processes load_config self.start_parent restart_needed = false end self.child_processes.each do |id, opts| begin Process.getpgid(opts[:pid]) rescue Errno::ESRCH logger.info "Parent process #{config.name}[#{id}] has died (from PID #{opts[:pid]}), exiting master" Process.exit(0) end end end end |
.pid_from_file ⇒ Object
Get the PID from the pidfile defined in the config
316 317 318 319 320 321 322 323 |
# File 'lib/rbg.rb', line 316 def pid_from_file raise Error, "PID not defined in '#{config_file}'" unless self.config.pid_path begin File.read(self.config.pid_path).strip.to_i rescue raise Error, "PID file not found" end end |
.reload(config_file, options = {}) ⇒ Object
Reload the running instance
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 |
# File 'lib/rbg.rb', line 345 def reload(config_file, = {}) [:environment] ||= "development" $rbg_env = [:environment].dup # Define the config file then load it self.config_file = config_file self.load_config pid = self.pid_from_file begin Process.kill('USR1', pid) puts "Sent USR1 to PID #{pid}" rescue raise Error, "Process #{pid} not found" end end |
.start(config_file, options = {}) ⇒ Object
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/rbg.rb', line 260 def start(config_file, = {}) [:background] ||= false [:environment] ||= "development" $rbg_env = [:environment].dup # Define the config file then load it self.config_file = config_file self.load_config # If the PID file is set and exists, check that the process is not running if self.config.pid_path and File.exists?(self.config.pid_path) oldpid = File.read(self.config.pid_path) begin Process.getpgid( oldpid.to_i ) raise Error, "Process already running! PID #{oldpid}" rescue Errno::ESRCH # No running process false end end # Initialize child process array self.child_processes = Hash.new if [:background] # Fork the master control process and return to a shell master_pid = fork do # Ignore input and log to a file STDIN.reopen('/dev/null') if self.config.log_path STDOUT.reopen(self.config.log_path, 'a') STDOUT.sync = true STDERR.reopen(self.config.log_path, 'a') STDERR.sync = true else raise Error, "Log location not specified in '#{config_file}'" end self.master_process end # Ensure the process is properly backgrounded Process.detach(master_pid) if self.config.pid_path File.open(self.config.pid_path, 'w') {|f| f.write(master_pid) } end logger.info "Master started as PID #{master_pid}" else # Run using existing STDIN / STDOUT and set logger to use use STDOUT regardless self.config.logger = MonoLogger.new(STDOUT) self.master_process end end |
.start_parent ⇒ Object
Creates a ‘parent’ process. This is responsible for executing ‘before_fork’ and then forking the worker processes.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 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 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 |
# File 'lib/rbg.rb', line 26 def start_parent # Record the PID of this parent in the Master parent_pid = fork do # Clear the child process list as this fork doesn't have any children yet self.child_processes = Hash.new # Set the process name (Parent) $0="#{self.config.name}[Parent]" # Debug information logger.info "New parent process: #{Process.pid}" STDOUT.flush # Run the before_fork function if config.before_fork.is_a?(Proc) self.config.before_fork.call end # Fork an appropriate number of workers self.fork_workers(self.config.workers) # If we get a TERM, send the existing workers a TERM then exit Signal.trap("TERM", proc { # Debug output logger.info "Parent got a TERM." STDOUT.flush # Send TERM to workers kill_child_processes # Exit the parent Process.exit(0) }) # Ending parent processes on INT is not useful or desirable # especially when running in the foreground Signal.trap('INT', proc {}) Signal.trap('HUP', proc {}) # Parent loop, the purpose of this is simply to do nothing until we get a signal # We will exit if all child processes die # We may add memory management code here in the future loop do sleep 2 child_processes.dup.each do |id, opts| begin Process.getpgid(opts[:pid]) if config.memory_limit # Lookup the memory usge for this PID memory_usage = `ps -o rss= -p #{opts[:pid]}`.strip.to_i / 1024 if memory_usage > config.memory_limit logger.info "#{self.config.name}[#{id}] is using #{memory_usage}MB of memory (limit: #{config.memory_limit}MB). It will be killed." kill_child_process(id) end end rescue Errno::ESRCH logger.info "Child process #{config.name}[#{id}] has died (from PID #{opts[:pid]})" child_processes[id][:pid] = nil if config.respawn if opts[:started_at] > Time.now - config.respawn_limits[1] if opts[:respawns] >= config.respawn_limits[0] logger.info "Process #{config.name}[#{id}] has instantly respawned #{opts[:respawns]} times. It won't be respawned again." child_processes.delete(id) else logger.info "Process has died within #{config.respawn_limits[1]}s of the last spawn." child_processes[id][:respawns] += 1 fork_worker(id) end else logger.info "Process was started more than #{config.respawn_limits[1]}s since the last spawn. Resetting spawn counter" child_processes[id][:respawns] = 0 fork_worker(id) end else child_processes.delete(id) end end end if child_processes.empty? logger.info "All child processes died, exiting parent" Process.exit(0) end end end # Store the PID for the parent child_processes[0] = {:pid => parent_pid, :respawns => 0} # Ensure the new parent is detached Process.detach(parent_pid) end |
.stop(config_file, options = {}) ⇒ Object
Stop the running instance
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/rbg.rb', line 326 def stop(config_file, = {}) [:environment] ||= "development" $rbg_env = [:environment].dup # Define the config file then load it self.config_file = config_file self.load_config pid = self.pid_from_file begin Process.kill('TERM', pid) puts "Sent TERM to PID #{pid}" rescue raise Error, "Process #{pid} not found" end end |