class Procodile::Instance

Attributes

id[R]
pid[RW]
port[RW]
process[RW]
tag[R]

Public Class Methods

new(supervisor, process, id) click to toggle source
# 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

add_respawn() click to toggle source

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
allocate_port(max_attempts = 10) click to toggle source

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_respawn?() click to toggle source

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(options = {}) click to toggle source

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
description() click to toggle source

Return a description for this instance

# File lib/procodile/instance.rb, line 24
def description
  "#{@process.name}.#{@id}"
end
environment_variables() click to toggle source

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
failed?() click to toggle source

Has this failed?

# File lib/procodile/instance.rb, line 179
def failed?
  @failed ? true : false
end
on_stop() click to toggle source

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
pid_file_path() click to toggle source

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
pid_from_file() click to toggle source

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
port_available?(port) click to toggle source

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
respawns() click to toggle source

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
restart() click to toggle source

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
running?() click to toggle source

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_be_running?() click to toggle source

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() click to toggle source

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
status() click to toggle source

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
stop() click to toggle source

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
stopped?() click to toggle source

Is this stopped?

# File lib/procodile/instance.rb, line 172
def stopped?
  @stopped || false
end
stopping?() click to toggle source

Is this instance supposed to be stopping/be stopped?

# File lib/procodile/instance.rb, line 165
def stopping?
  @stopping ? true : false
end
tidy() click to toggle source

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
to_hash() click to toggle source

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_pid() click to toggle source

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