Class: Ringleader::Process
- Inherits:
-
Object
- Object
- Ringleader::Process
- Includes:
- Celluloid, Celluloid::Logger, NameLogger
- Defined in:
- lib/ringleader/process.rb
Overview
Represents an instance of a configured application.
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
Returns the value of attribute config.
Instance Method Summary collapse
-
#already_running? ⇒ Boolean
Internal: check if the app is already running outside ringleader.
-
#child_pids(parent_pid) ⇒ Object
Internal: returns all child pids of the given parent.
-
#children_of(parent_pid, proc_table) ⇒ Object
Internal: find child pids given a parent pid and a proc table.
-
#exited ⇒ Object
Internal: callback for when the process has exited.
-
#in_clean_environment(&block) ⇒ Object
Internal: execute a command in a clean environment (bundler).
-
#initialize(config) ⇒ Process
constructor
Create a new App instance.
-
#port_opened ⇒ Object
Internal: callback for when the application port has opened.
-
#proxy_output(input) ⇒ Object
Internal: proxy output streams to the logger.
-
#reap_orphans(pids) ⇒ Object
Internal: kill orphaned processes.
-
#running? ⇒ Boolean
Public: query if the app is running.
-
#start ⇒ Object
Public: start the application.
-
#start_app ⇒ Object
Internal: start the application process and associated infrastructure.
-
#stop ⇒ Object
Public: stop the application.
Methods included from NameLogger
#debug, #error, #info, #warn, #with_name
Constructor Details
#initialize(config) ⇒ Process
Create a new App instance.
config - a configuration object for this app
14 15 16 17 |
# File 'lib/ringleader/process.rb', line 14 def initialize(config) @config = config @starting = @running = false end |
Instance Attribute Details
#config ⇒ Object (readonly)
Returns the value of attribute config.
9 10 11 |
# File 'lib/ringleader/process.rb', line 9 def config @config end |
Instance Method Details
#already_running? ⇒ Boolean
Internal: check if the app is already running outside ringleader
159 160 161 162 163 164 165 166 167 |
# File 'lib/ringleader/process.rb', line 159 def already_running? socket = TCPSocket.new config.host, config.app_port socket.close true rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT false rescue IOError, SystemCallError => e error "unexpected error when checking status: #{e}" end |
#child_pids(parent_pid) ⇒ Object
Internal: returns all child pids of the given parent
204 205 206 207 208 |
# File 'lib/ringleader/process.rb', line 204 def child_pids(parent_pid) debug "retrieving child pids of #{parent_pid}" proc_table = Sys::ProcTable.ps children_of parent_pid, proc_table end |
#children_of(parent_pid, proc_table) ⇒ Object
Internal: find child pids given a parent pid and a proc table
211 212 213 214 215 216 217 218 219 220 |
# File 'lib/ringleader/process.rb', line 211 def children_of(parent_pid, proc_table) [].tap do |pids| proc_table.each do |proc_record| if proc_record.ppid == parent_pid pids << proc_record.pid pids.concat children_of proc_record.pid, proc_table end end end end |
#exited ⇒ Object
Internal: callback for when the process has exited.
91 92 93 94 95 96 97 98 |
# File 'lib/ringleader/process.rb', line 91 def exited info "pid #{@pid} exited" @running = false @pid = nil @wait_for_port.terminate if @wait_for_port.alive? @wait_for_exit.terminate if @wait_for_exit.alive? signal :running, false end |
#in_clean_environment(&block) ⇒ Object
Internal: execute a command in a clean environment (bundler)
181 182 183 184 185 186 187 |
# File 'lib/ringleader/process.rb', line 181 def in_clean_environment(&block) if Object.const_defined?(:Bundler) ::Bundler.with_clean_env(&block) else yield end end |
#port_opened ⇒ Object
Internal: callback for when the application port has opened
85 86 87 88 |
# File 'lib/ringleader/process.rb', line 85 def port_opened info "listening on #{config.host}:#{config.app_port}" signal :running, true end |
#proxy_output(input) ⇒ Object
Internal: proxy output streams to the logger.
Fire and forget, runs in its own thread.
172 173 174 175 176 177 178 |
# File 'lib/ringleader/process.rb', line 172 def proxy_output(input) Thread.new do until input.eof? info input.gets.strip end end end |
#reap_orphans(pids) ⇒ Object
Internal: kill orphaned processes
190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/ringleader/process.rb', line 190 def reap_orphans(pids) pids.each do |pid| debug "checking for child #{pid}" next unless Sys::ProcTable.ps(pid) error "child process #{pid} was orphaned, killing it" begin ::Process.kill "KILL", pid rescue Errno::ESRCH, Errno::EPERM debug "could not kill #{pid}" end end end |
#running? ⇒ Boolean
Public: query if the app is running
20 21 22 |
# File 'lib/ringleader/process.rb', line 20 def running? @running end |
#start ⇒ Object
Public: start the application.
This method is intended to be used synchronously. If the app is already running, it’ll return immediately. If the app hasn’t been started, or is in the process of starting, this method blocks until it starts or fails to start correctly.
Returns true if the app started, false if not.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/ringleader/process.rb', line 32 def start if @running true elsif @starting wait :running else if already_running? warn "#{config.name} already running on port #{config.app_port}" return true else start_app end end end |
#start_app ⇒ Object
Internal: start the application process and associated infrastructure
Intended to be synchronous, as it blocks until the app has started (or failed to start).
Returns true if the app started, false if not.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 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 |
# File 'lib/ringleader/process.rb', line 106 def start_app @starting = true info "starting process `#{config.command}`" unless File.directory?(config.dir) error "#{config.dir} does not exist!" @starting = false @running = false return false end # give the child process a terminal so output isn't buffered @master, slave = PTY.open in_clean_environment do @pid = ::Process.spawn( config.env, %Q(bash -c "#{config.command}"), :in => slave, :out => slave, :err => slave, :chdir => config.dir, :pgroup => true ) end slave.close proxy_output @master debug "started with pid #{@pid}" @wait_for_exit = WaitForExit.new @pid, Actor.current @wait_for_port = WaitForPort.new config.host, config.app_port, Actor.current timer = after config.startup_timeout do warn "application startup took more than #{config.startup_timeout}" async.stop end @running = wait :running @starting = false timer.cancel @running rescue Errno::ENOENT @starting = false @running = false false ensure unless @running warn "could not start `#{config.command}`" end end |
#stop ⇒ Object
Public: stop the application.
Sends a SIGTERM to the app’s process, and expects it to exit like a sane and well-behaved application within 7 seconds before sending a SIGKILL.
Uses config.kill_with for the initial signal, which defaults to “TERM”. If a configured process doesn’t respond well to TERM (i.e. leaving zombies), use KILL instead.
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 |
# File 'lib/ringleader/process.rb', line 55 def stop return unless @pid children = child_pids @pid info "stopping #{@pid}" debug "child pids: #{children.inspect}" @master.close unless @master.closed? debug "kill -#{config.kill_with} #{@pid}" ::Process.kill config.kill_with, -@pid failsafe = after 7 do warn "process #{@pid} did not shut down cleanly, killing it" debug "kill -KILL #{@pid}" ::Process.kill "KILL", -@pid reap_orphans children end wait :running # wait for the exit callback failsafe.cancel sleep 2 # give the children a chance to shut down reap_orphans children rescue Errno::ESRCH, Errno::EPERM exited end |