Class: EventedBluepill::Process
- Inherits:
-
Object
- Object
- EventedBluepill::Process
- Defined in:
- lib/evented_bluepill/process.rb
Constant Summary collapse
- CONFIGURABLE_ATTRIBUTES =
[ :start_command, :stop_command, :restart_command, :stdout, :stderr, :stdin, :daemonize, :pid_file, :working_dir, :environment, :start_grace_time, :stop_grace_time, :restart_grace_time, :uid, :gid, :monitor_children, :child_process_factory ]
Instance Attribute Summary collapse
-
#children_timer ⇒ Object
readonly
Returns the value of attribute children_timer.
-
#logger ⇒ Object
Returns the value of attribute logger.
-
#name ⇒ Object
Returns the value of attribute name.
-
#process_running ⇒ Object
Returns the value of attribute process_running.
-
#skip_ticks_until ⇒ Object
Returns the value of attribute skip_ticks_until.
-
#statistics ⇒ Object
readonly
Returns the value of attribute statistics.
-
#timer ⇒ Object
readonly
Returns the value of attribute timer.
-
#triggers ⇒ Object
Returns the value of attribute triggers.
-
#watches ⇒ Object
Returns the value of attribute watches.
Instance Method Summary collapse
- #actual_pid ⇒ Object
- #actual_pid=(pid) ⇒ Object
- #add_trigger(name, options = {}) ⇒ Object
-
#add_watch(name, options = {}) ⇒ Object
Watch related methods.
- #clear_pid ⇒ Object
- #daemonize? ⇒ Boolean
- #determine_initial_state ⇒ Object
-
#dispatch!(event, reason = nil) ⇒ Object
State machine methods.
- #handle_user_command(cmd) ⇒ Object
-
#initialize(process_name, checks, options = {}) ⇒ Process
constructor
A new instance of Process.
- #monitor_children? ⇒ Boolean
- #notify_triggers(transition) ⇒ Object
- #prepare_command(command) ⇒ Object
-
#process_running?(force = false) ⇒ Boolean
System Process Methods.
- #record_transition(transition) ⇒ Object
- #refresh_children! ⇒ Object
- #restart_process ⇒ Object
- #set_timer ⇒ Object
- #signal_process(code) ⇒ Object
-
#skip_ticks_for(seconds) ⇒ Object
Internal State Methods.
- #skipping_ticks? ⇒ Boolean
- #start_process ⇒ Object
- #stop_process ⇒ Object
- #system_command_options ⇒ Object
- #unlink_pid ⇒ Object
- #with_timeout(secs, &blk) ⇒ Object
Constructor Details
#initialize(process_name, checks, options = {}) ⇒ Process
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 |
# File 'lib/evented_bluepill/process.rb', line 116 def initialize(process_name, checks, = {}) @name = process_name @watches = [] @triggers = [] @children_timer = [] @statistics = ProcessStatistics.new @actual_pid = [:actual_pid] self.logger = [:logger] checks.each do |name, opts| if EventedBluepill::Trigger[name] self.add_trigger(name, opts) else self.add_watch(name, opts) end end # These defaults are overriden below if it's configured to be something else. @monitor_children = false @start_grace_time = @stop_grace_time = @restart_grace_time = 3 @environment = {} CONFIGURABLE_ATTRIBUTES.each do |attribute_name| self.send("#{attribute_name}=", [attribute_name]) if .has_key?(attribute_name) end # Let state_machine do its initialization stuff super() # no arguments intentional end |
Instance Attribute Details
#children_timer ⇒ Object (readonly)
Returns the value of attribute children_timer.
61 62 63 |
# File 'lib/evented_bluepill/process.rb', line 61 def children_timer @children_timer end |
#logger ⇒ Object
Returns the value of attribute logger.
59 60 61 |
# File 'lib/evented_bluepill/process.rb', line 59 def logger @logger end |
#name ⇒ Object
Returns the value of attribute name.
59 60 61 |
# File 'lib/evented_bluepill/process.rb', line 59 def name @name end |
#process_running ⇒ Object
Returns the value of attribute process_running.
59 60 61 |
# File 'lib/evented_bluepill/process.rb', line 59 def process_running @process_running end |
#skip_ticks_until ⇒ Object
Returns the value of attribute skip_ticks_until.
59 60 61 |
# File 'lib/evented_bluepill/process.rb', line 59 def skip_ticks_until @skip_ticks_until end |
#statistics ⇒ Object (readonly)
Returns the value of attribute statistics.
61 62 63 |
# File 'lib/evented_bluepill/process.rb', line 61 def statistics @statistics end |
#timer ⇒ Object (readonly)
Returns the value of attribute timer.
61 62 63 |
# File 'lib/evented_bluepill/process.rb', line 61 def timer @timer end |
#triggers ⇒ Object
Returns the value of attribute triggers.
59 60 61 |
# File 'lib/evented_bluepill/process.rb', line 59 def triggers @triggers end |
#watches ⇒ Object
Returns the value of attribute watches.
59 60 61 |
# File 'lib/evented_bluepill/process.rb', line 59 def watches @watches end |
Instance Method Details
#actual_pid ⇒ Object
319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/evented_bluepill/process.rb', line 319 def actual_pid @actual_pid ||= begin if pid_file if File.exists?(pid_file) str = File.read(pid_file) str.to_i if str.size > 0 else logger.warning("pid_file #{pid_file} does not exist or cannot be read") nil end end end end |
#actual_pid=(pid) ⇒ Object
333 334 335 |
# File 'lib/evented_bluepill/process.rb', line 333 def actual_pid=(pid) @actual_pid = pid end |
#add_trigger(name, options = {}) ⇒ Object
183 184 185 |
# File 'lib/evented_bluepill/process.rb', line 183 def add_trigger(name, = {}) self.triggers << Trigger[name].new(self, .merge(:logger => self.logger)) end |
#add_watch(name, options = {}) ⇒ Object
Watch related methods
179 180 181 |
# File 'lib/evented_bluepill/process.rb', line 179 def add_watch(name, = {}) self.watches << ProcessConditions[name].new(name, self, .merge(:logger => self.logger)) end |
#clear_pid ⇒ Object
337 338 339 |
# File 'lib/evented_bluepill/process.rb', line 337 def clear_pid @actual_pid = nil end |
#daemonize? ⇒ Boolean
304 305 306 |
# File 'lib/evented_bluepill/process.rb', line 304 def daemonize? !!self.daemonize end |
#determine_initial_state ⇒ Object
187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/evented_bluepill/process.rb', line 187 def determine_initial_state if self.process_running?(true) self.state = 'up' else # TODO: or "unmonitored" if evented_bluepill was started in no auto-start mode. self.state = 'down' end # TODO move into right position self.set_timer end |
#dispatch!(event, reason = nil) ⇒ Object
State machine methods
153 154 155 156 |
# File 'lib/evented_bluepill/process.rb', line 153 def dispatch!(event, reason = nil) @statistics.record_event(event, reason) self.send("#{event}") end |
#handle_user_command(cmd) ⇒ Object
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/evented_bluepill/process.rb', line 206 def handle_user_command(cmd) case cmd when "start" if self.process_running?(true) logger.warning("Refusing to re-run start command on an already running process.") else dispatch!(:start, "user initiated") end when "stop" stop_process dispatch!(:unmonitor, "user initiated") when "restart" restart_process when "unmonitor" # When the user issues an unmonitor cmd, reset any triggers so that # scheduled events gets cleared triggers.each {|t| t.reset! } dispatch!(:unmonitor, "user initiated") end end |
#monitor_children? ⇒ Boolean
308 309 310 |
# File 'lib/evented_bluepill/process.rb', line 308 def monitor_children? !!self.monitor_children end |
#notify_triggers(transition) ⇒ Object
174 175 176 |
# File 'lib/evented_bluepill/process.rb', line 174 def notify_triggers(transition) self.triggers.each {|trigger| trigger.notify(transition)} end |
#prepare_command(command) ⇒ Object
380 381 382 |
# File 'lib/evented_bluepill/process.rb', line 380 def prepare_command(command) command.to_s.gsub("{{PID}}", actual_pid.to_s) end |
#process_running?(force = false) ⇒ Boolean
System Process Methods
228 229 230 231 232 233 234 235 |
# File 'lib/evented_bluepill/process.rb', line 228 def process_running?(force = false) @process_running = nil if force # clear existing state if forced @process_running ||= signal_process(0) # the process isn't running, so we should clear the PID self.clear_pid unless @process_running @process_running end |
#record_transition(transition) ⇒ Object
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/evented_bluepill/process.rb', line 158 def record_transition(transition) unless transition.loopback? # When a process changes state, we should clear the memory of all the watches self.watches.each { |w| w.clear_history! } # Also, when a process changes state, we should re-populate its child list if self.monitor_children? self.logger.warning "Clearing child list" self.children_timer.each {|timer| timer.detach } self.children_timer.each {|timer| timer.process.watches.each {|w| w.detach }} self.children_timer.clear end logger.info "Going from #{transition.from_name} => #{transition.to_name}" end end |
#refresh_children! ⇒ Object
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/evented_bluepill/process.rb', line 356 def refresh_children! # First prune the list of dead children dead_children = self.children_timer.select {|timer| !timer.process.process_running?(true) } dead_children.each {|timer| timer.detach } dead_children.each {|timer| timer.process.watches.each {|w| w.detach }} @children_timer -= dead_children # Add new found children to the list new_children_pids = System.get_children(self.actual_pid) - self.children_timer.map {|timer| timer.process.actual_pid} unless new_children_pids.empty? logger.info "Existing children: #{self.children_timer.collect{|c| c.process.actual_pid}.join(",")}. Got new children: #{new_children_pids.inspect} for #{actual_pid}" end # Construct a new process wrapper for each new found children new_children_pids.each do |child_pid| name = "<child(pid:#{child_pid})>" logger = self.logger.prefix_with(name) child = self.child_process_factory.create_child_process(name, child_pid, logger) @children_timer << child.timer end end |
#restart_process ⇒ Object
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'lib/evented_bluepill/process.rb', line 281 def restart_process if restart_command cmd = self.prepare_command(restart_command) logger.warning "Executing restart command: #{cmd}" with_timeout(restart_grace_time) do result = System.execute_blocking(cmd, self.) unless result[:exit_code].zero? logger.warning "Restart command execution returned non-zero exit code:" logger.warning result.inspect end end self.skip_ticks_for(restart_grace_time) else logger.warning "No restart_command specified. Must stop and start to restart" self.stop_process # the tick will bring it back. end end |
#set_timer ⇒ Object
199 200 201 202 203 204 |
# File 'lib/evented_bluepill/process.rb', line 199 def set_timer @timer = EventedBluepill::ProcessTimer.new(self) EventedBluepill::Event.attach(self.timer) self.watches.each {|w| EventedBluepill::Event.attach(w) } end |
#signal_process(code) ⇒ Object
312 313 314 315 316 317 |
# File 'lib/evented_bluepill/process.rb', line 312 def signal_process(code) ::Process.kill(code, actual_pid) true rescue false end |
#skip_ticks_for(seconds) ⇒ Object
Internal State Methods
346 347 348 349 350 |
# File 'lib/evented_bluepill/process.rb', line 346 def skip_ticks_for(seconds) # TODO: should this be addative or longest wins? # i.e. if two calls for skip_ticks_for come in for 5 and 10, should it skip for 10 or 15? self.skip_ticks_until = (self.skip_ticks_until || Time.now.to_i) + seconds.to_i end |
#skipping_ticks? ⇒ Boolean
352 353 354 |
# File 'lib/evented_bluepill/process.rb', line 352 def skipping_ticks? self.skip_ticks_until && self.skip_ticks_until > Time.now.to_i end |
#start_process ⇒ Object
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/evented_bluepill/process.rb', line 237 def start_process logger.warning "Executing start command: #{start_command}" if self.daemonize? System.daemonize(start_command, self.) else # This is a self-daemonizing process with_timeout(start_grace_time) do result = System.execute_blocking(start_command, self.) unless result[:exit_code].zero? logger.warning "Start command execution returned non-zero exit code:" logger.warning result.inspect end end end self.skip_ticks_for(start_grace_time) end |
#stop_process ⇒ Object
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/evented_bluepill/process.rb', line 258 def stop_process if stop_command cmd = self.prepare_command(stop_command) logger.warning "Executing stop command: #{cmd}" with_timeout(stop_grace_time) do result = System.execute_blocking(cmd, self.) unless result[:exit_code].zero? logger.warning "Stop command execution returned non-zero exit code:" logger.warning result.inspect end end else logger.warning "Executing default stop command. Sending TERM signal to #{actual_pid}" signal_process("TERM") end self.unlink_pid # TODO: we only write the pid file if we daemonize, should we only unlink it if we daemonize? self.skip_ticks_for(stop_grace_time) end |
#system_command_options ⇒ Object
384 385 386 387 388 389 390 391 392 393 394 395 396 |
# File 'lib/evented_bluepill/process.rb', line 384 def { :uid => self.uid, :gid => self.gid, :working_dir => self.working_dir, :environment => self.environment, :pid_file => self.pid_file, :logger => self.logger, :stdin => self.stdin, :stdout => self.stdout, :stderr => self.stderr } end |
#unlink_pid ⇒ Object
341 342 343 |
# File 'lib/evented_bluepill/process.rb', line 341 def unlink_pid File.unlink(pid_file) if pid_file && File.exists?(pid_file) end |
#with_timeout(secs, &blk) ⇒ Object
398 399 400 401 402 403 404 405 |
# File 'lib/evented_bluepill/process.rb', line 398 def with_timeout(secs, &blk) Timeout.timeout(secs.to_f, &blk) rescue Timeout::Error logger.err "Execution is taking longer than expected. Unmonitoring." logger.err "Did you forget to tell evented_bluepill to daemonize this process?" self.dispatch!("unmonitor") end |