class Procodile::Instance
Attributes
Public Class Methods
# File lib/procodile/instance.rb, line 13 def initialize(supervisor, process, id) @supervisor = supervisor @process = process @id = id @respawns = 0 @started_at = nil end
Public Instance Methods
Increment the counter of respawns for this process
# File lib/procodile/instance.rb, line 322 def add_respawn if @last_respawn && @last_respawn < (Time.now - @process.respawn_window) @respawns = 1 else @last_respawn = Time.now @respawns += 1 end end
Find a port number for this instance to listen on. We just check that nothing is already listening on it. The process is expected to take it straight away if it wants it.
# File lib/procodile/instance.rb, line 352 def allocate_port(max_attempts = 10) attempts = 0 until @port attempts += 1 possible_port = rand(10000) + 20000 if self.port_available?(possible_port) Procodile.log(@process.log_color, description, "Allocated port as #{possible_port}") return @port = possible_port elsif attempts >= max_attempts raise Procodile::Error, "Couldn't allocate port for #{instance.name}" end end end
Can this process be respawned if needed?
# File lib/procodile/instance.rb, line 304 def can_respawn? !stopping? && (respawns + 1) <= @process.max_respawns end
Check the status of this process and handle as appropriate.
# File lib/procodile/instance.rb, line 271 def check(options = {}) return if failed? if self.running? # Everything is OK. The process is running. true else # If the process isn't running any more, update the PID in our memory from # the file in case the process has changed itself. return check if update_pid if @supervisor.allow_respawning? if can_respawn? Procodile.log(@process.log_color, description, "Process has stopped. Respawning...") start add_respawn elsif respawns >= @process.max_respawns Procodile.log(@process.log_color, description, "\e[41;37mWarning:\e[0m\e[31m this process has been respawned #{respawns} times and keeps dying.\e[0m") Procodile.log(@process.log_color, description, "It will not be respawned automatically any longer and will no longer be managed.".color(31)) @failed = Time.now tidy end else Procodile.log(@process.log_color, description, "Process has stopped. Respawning not available.") @failed = Time.now tidy end end end
Return a description for this instance
# File lib/procodile/instance.rb, line 24 def description "#{@process.name}.#{@id}" end
Return an array of environment variables that should be set
# File lib/procodile/instance.rb, line 59 def environment_variables vars = @process.environment_variables.merge({ 'PROC_NAME' => self.description, 'PID_FILE' => self.pid_file_path, 'APP_ROOT' => @process.config.root }) vars['PORT'] = @port.to_s if @port vars end
Has this failed?
# File lib/procodile/instance.rb, line 179 def failed? @failed ? true : false end
A method that will be called when this instance has been stopped and it isn't going to be started again
# File lib/procodile/instance.rb, line 202 def on_stop @started_at = nil @stopped = true tidy end
Return the path to this instance's PID file
# File lib/procodile/instance.rb, line 72 def pid_file_path File.join(@process.config.pid_root, "#{description}.pid") end
Return the PID that is in the instances process PID file
# File lib/procodile/instance.rb, line 79 def pid_from_file if File.exist?(pid_file_path) pid = File.read(pid_file_path) pid.length > 0 ? pid.strip.to_i : nil else nil end end
Is the given port available?
# File lib/procodile/instance.rb, line 369 def port_available?(port) case @process.network_protocol when 'tcp' server = TCPServer.new('127.0.0.1', port) server.close true when 'udp' server = UDPSocket.new server.bind('127.0.0.1', port) server.close true else raise Procodile::Error, "Invalid network_protocol '#{@process.network_protocol}'" end rescue Errno::EADDRINUSE => e false end
Return the number of times this process has been respawned in the last hour
# File lib/procodile/instance.rb, line 311 def respawns if @respawns.nil? || @last_respawn.nil? || @last_respawn < (Time.now - @process.respawn_window) 0 else @respawns end end
Retarts the process using the appropriate method from the process configuraiton
# File lib/procodile/instance.rb, line 219 def restart Procodile.log(@process.log_color, description, "Restarting using #{@process.restart_mode} mode") update_pid case @process.restart_mode when 'usr1', 'usr2' if running? ::Process.kill(@process.restart_mode.upcase, @pid) @tag = @supervisor.tag if @supervisor.tag Procodile.log(@process.log_color, description, "Sent #{@process.restart_mode.upcase} signal to process #{@pid}") else Procodile.log(@process.log_color, description, "Process not running already. Starting it.") on_stop new_instance = @process.create_instance(@supervisor) new_instance.port = self.port new_instance.start end self when 'start-term' new_instance = @process.create_instance(@supervisor) new_instance.start stop new_instance when 'term-start' stop new_instance = @process.create_instance(@supervisor) new_instance.port = self.port Thread.new do sleep 0.5 while running? new_instance.start end new_instance end end
Is this process running? Pass an option to check the given PID instead of the instance
# File lib/procodile/instance.rb, line 91 def running? if @pid ::Process.getpgid(@pid) ? true : false else false end rescue Errno::ESRCH false end
Should this process be running?
# File lib/procodile/instance.rb, line 48 def should_be_running? if stopped? || stopping? false else true end end
Start a new instance of this process
# File lib/procodile/instance.rb, line 104 def start if stopping? Procodile.log(@process.log_color, description, "Process is stopped/stopping therefore cannot be started again.") return false end update_pid if running? Procodile.log(@process.log_color, description, "Already running with PID #{@pid}") nil else if @supervisor.run_options[:port_allocations] && chosen_port = @supervisor.run_options[:port_allocations][@process.name] if chosen_port == 0 allocate_port else @port = chosen_port Procodile.log(@process.log_color, description, "Assigned #{chosen_port} to process") end elsif @process.proxy? && @supervisor.tcp_proxy # Allocate a port randomly if a proxy is needed allocate_port elsif @process.allocate_port_from && @process.restart_mode != 'start-term' # Allocate ports to this process sequentially from the starting port allocated_ports = (@supervisor.processes[@process] ? @supervisor.processes[@process].select(&:running?) : []).map(&:port) proposed_port = @process.allocate_port_from until @port unless allocated_ports.include?(proposed_port) @port = proposed_port end proposed_port += 1 end end if self.process.log_path && @supervisor.run_options[:force_single_log] != true log_destination = File.open(self.process.log_path, 'a') io = nil else reader, writer = IO.pipe log_destination = writer io = reader end @tag = @supervisor.tag.dup if @supervisor.tag Dir.chdir(@process.config.root) Rbenv.without do @pid = ::Process.spawn(environment_variables, @process.command, :out => log_destination, :err => log_destination, :pgroup => true) end log_destination.close File.open(pid_file_path, 'w') { |f| f.write(@pid.to_s + "\n") } @supervisor.add_instance(self, io) ::Process.detach(@pid) Procodile.log(@process.log_color, description, "Started with PID #{@pid}" + (@tag ? " (tagged with #{@tag})" : '')) if self.process.log_path && io.nil? Procodile.log(@process.log_color, description, "Logging to #{self.process.log_path}") end @started_at = Time.now end end
Return the status of this instance
# File lib/procodile/instance.rb, line 31 def status if stopped? 'Stopped' elsif stopping? 'Stopping' elsif running? 'Running' elsif failed? 'Failed' else 'Unknown' end end
Send this signal the signal to stop and mark the instance in a state that tells us that we want it to be stopped.
# File lib/procodile/instance.rb, line 187 def stop @stopping = Time.now update_pid if self.running? Procodile.log(@process.log_color, description, "Sending #{@process.term_signal} to #{@pid}") ::Process.kill(@process.term_signal, pid) else Procodile.log(@process.log_color, description, "Process already stopped") end end
Is this stopped?
# File lib/procodile/instance.rb, line 172 def stopped? @stopped || false end
Is this instance supposed to be stopping/be stopped?
# File lib/procodile/instance.rb, line 165 def stopping? @stopping ? true : false end
Tidy up when this process isn't needed any more
# File lib/procodile/instance.rb, line 211 def tidy FileUtils.rm_f(self.pid_file_path) Procodile.log(@process.log_color, description, "Removed PID file") end
Return this instance as a hash
# File lib/procodile/instance.rb, line 334 def to_hash { :description => self.description, :pid => self.pid, :respawns => self.respawns, :status => self.status, :running => self.running?, :started_at => @started_at ? @started_at.to_i : nil, :tag => self.tag, :port => @port } end
Update the locally cached PID from that stored on the file system.
# File lib/procodile/instance.rb, line 256 def update_pid pid_from_file = self.pid_from_file if pid_from_file && pid_from_file != @pid @pid = pid_from_file @started_at = File.mtime(self.pid_file_path) Procodile.log(@process.log_color, description, "PID file changed. Updated pid to #{@pid}") true else false end end