module Eye::Process::Commands

Public Instance Methods

restart_process() click to toggle source
# File lib/eye/process/commands.rb, line 67
def restart_process
  debug { 'restart_process command' }

  switch :restarting

  if self[:restart_command]
    return unless check_identity
    if execute_restart_command
      sleep_grace(:restart_grace)
    end
    result = process_really_running? || (load_external_pid_file == :ok)
    switch(result ? :restarted : :crashed)
  else
    stop_process
    start_process
  end

  true

rescue StateMachines::InvalidTransition, Eye::Process::StateError => e
  warn "wrong switch '#{e.message}'"
  nil
end
start_process() click to toggle source
# File lib/eye/process/commands.rb, line 3
def start_process
  debug { 'start_process command' }

  switch :starting

  unless self[:start_command]
    warn 'no :start_command found, unmonitoring'
    switch :unmonitoring, reason: 'no_start_command'
    return :no_start_command
  end

  result = self[:daemonize] ? daemonize_process : execute_process

  if !result[:error]
    debug { "process <#{self.pid}> started successfully" }
    switch :started
  else
    error "process <#{self.pid}> failed to start (#{result[:error].inspect})"

    if process_really_running?
      warn "killing <#{self.pid}> due to error"
      send_signal(:KILL)
      sleep 0.2 # little grace
    end

    switch :crashed
  end

  result

rescue StateMachines::InvalidTransition, Eye::Process::StateError => e
  warn "wrong switch '#{e.message}'"

  :state_error
end
stop_process() click to toggle source
# File lib/eye/process/commands.rb, line 39
def stop_process
  debug { 'stop_process command' }

  switch :stopping

  return unless check_identity

  kill_process

  if process_really_running?
    warn "process <#{self.pid}> was not stopped; try checking your command/signals or tuning the stop_timeout/stop_grace values"

    switch :unmonitoring, reason: 'not stopped (soft command)'
    nil

  else
    switch :stopped

    clear_pid_file if self[:clear_pid] # by default for all

    true
  end

rescue StateMachines::InvalidTransition, Eye::Process::StateError => e
  warn "wrong switch '#{e.message}'"
  nil
end

Private Instance Methods

check_logs_str() click to toggle source
# File lib/eye/process/commands.rb, line 252
def check_logs_str
  if !self[:stdout] && !self[:stderr]
    'you may want to configure stdout/err/all logs for this process'
  else
    "you should check the process logs #{[self[:stdout], self[:stderr]]}"
  end
end
daemonize_process() click to toggle source
# File lib/eye/process/commands.rb, line 163
def daemonize_process
  res = daemonize(self[:start_command], config)
  info "daemonizing: `#{self[:start_command]}` with start_grace: #{self[:start_grace].to_f}s, env: '#{environment_string}'" \
    ", <#{res[:pid]}> (in #{self[:working_dir]})"

  if res[:error]

    if res[:error].message == 'Permission denied - open'
      error "daemonize failed with #{res[:error].inspect}; make sure #{[self[:stdout], self[:stderr]]} are writable"
    else
      error "daemonize failed with #{res[:error].inspect}"
    end

    return { error: res[:error].inspect }
  end

  self.pid = res[:pid]

  unless self.pid
    error 'no pid was returned'
    return { error: :empty_pid }
  end

  sleep_grace(:start_grace)

  unless process_really_running?
    error "process <#{self.pid}> not found, it may have crashed (#{check_logs_str})"
    return { error: :not_really_running }
  end

  # if we using leaf child stratedy, pid should be used as last child process
  if self[:use_leaf_child]
    if lpid = Eye::SystemResources.leaf_child(self.pid)
      info "leaf child for <#{self.pid}> found: <#{lpid}>, accepting it!"
      self.parent_pid = self.pid
      self.pid = lpid
    else
      warn "leaf child not found for <#{self.pid}>, skipping it"
    end
  end

  if control_pid? && !failsafe_save_pid
    return { error: :cant_write_pid }
  end

  res
end
execute_process() click to toggle source
# File lib/eye/process/commands.rb, line 211
def execute_process
  info "executing: `#{self[:start_command]}` with start_timeout: #{config[:start_timeout].to_f}s" \
    ", start_grace: #{self[:start_grace].to_f}s, env: '#{environment_string}' (in #{self[:working_dir]})"
  res = execute(self[:start_command], config.merge(timeout: config[:start_timeout]))

  if res[:error]

    if res[:error].message == 'Permission denied - open'
      error "execution failed with #{res[:error].inspect}; ensure that #{[self[:stdout], self[:stderr]]} are writable"
    elsif res[:error].class == Timeout::Error
      error "execution failed with #{res[:error].inspect}; try increasing the start_timeout value" \
        "(the current value of #{self[:start_timeout]}s seems too short)"
    else
      error "execution failed with #{res[:error].inspect}"
    end

    return { error: res[:error].inspect }
  end

  sleep_grace(:start_grace)

  case load_res = load_external_pid_file
    when :ok
      res.merge(pid: self.pid)
    when :no_pid_file
      error "exit status #{res[:exitstatus]}, pid_file (#{self[:pid_file_ex]}) did not appear within the " \
        "start_grace period (#{self[:start_grace].to_f}s); check your start_command, or tune the start_grace " \
        'value (eye expect process to create pid_file in self-daemonization mode)'
      { error: :pid_not_found }
    when :not_running
      error "exit status #{res[:exitstatus]}, process <#{@last_loaded_pid}> (from #{self[:pid_file_ex]}) was not found; " \
        "ensure that the pid_file is being updated correctly (#{check_logs_str})"
      { error: :not_really_running }
    else
      # really strange case
      error "exit status #{res[:exitstatus]}, process <#{@last_loaded_pid}> (from #{self[:pid_file_ex]}) #{load_res}; " \
        "this is really strange case, like timestamp of server was updated, may be need to reload eye (#{check_logs_str})"
      { error: load_res }
  end
end
execute_restart_command() click to toggle source
# File lib/eye/process/commands.rb, line 140
def execute_restart_command
  unless self.pid
    error 'cannot restart a process without a pid'
    return
  end

  cmd = prepare_command(self[:restart_command])
  info "executing: `#{cmd}` with restart_timeout: #{self[:restart_timeout].to_f}s and restart_grace: #{self[:restart_grace].to_f}s"

  res = execute(cmd, config.merge(timeout: self[:restart_timeout]))

  if res[:error]

    if res[:error].class == Timeout::Error
      error "restart_command failed with #{res[:error].inspect}; try tuning the restart_timeout value"
    else
      error "restart_command failed with #{res[:error].inspect}"
    end
  end

  res
end
execute_user_command(name, cmd) click to toggle source
# File lib/eye/process/commands.rb, line 274
def execute_user_command(name, cmd)
  return unless check_identity

  info "executing user command #{name} #{cmd.inspect}"

  # cmd is string, or array of signals
  if cmd.is_a?(String)
    cmd = prepare_command(cmd)
    res = execute(cmd, config.merge(timeout: 120))
    error "cmd #{cmd} error #{res.inspect}" if res[:error]
  elsif cmd.is_a?(Array)
    signals = cmd.clone
    signal = signals.shift
    send_signal(signal)

    while signals.present?
      delay = signals.shift
      signal = signals.shift
      if wait_for_condition(delay.to_f, 0.3) { !process_really_running? }
        info 'has terminated'
        break
      end
      send_signal(signal) if signal
    end
  else
    warn "unknown user command #{c}"
  end
end
kill_process() click to toggle source
# File lib/eye/process/commands.rb, line 93
def kill_process
  unless self.pid
    error 'cannot stop a process without a pid'
    return
  end

  if self[:stop_command]
    cmd = prepare_command(self[:stop_command])
    info "executing: `#{cmd}` with stop_timeout: #{self[:stop_timeout].to_f}s and stop_grace: #{self[:stop_grace].to_f}s"
    res = execute(cmd, config.merge(timeout: self[:stop_timeout]))

    if res[:error]
      if res[:error].class == Timeout::Error
        error "stop_command failed with #{res[:error].inspect}; try tuning the stop_timeout value"
      else
        error "stop_command failed with #{res[:error].inspect}"
      end
    end

    sleep_grace(:stop_grace)

  elsif self[:stop_signals]
    info "executing stop_signals #{self[:stop_signals].inspect}"
    stop_signals = self[:stop_signals].clone
    signal = stop_signals.shift
    send_signal(signal)

    while stop_signals.present?
      delay = stop_signals.shift
      signal = stop_signals.shift

      if wait_for_condition(delay.to_f, 0.1) { !process_really_running? }
        info 'has terminated'
        break
      end

      send_signal(signal) if signal
    end

    # little grace
    sleep 0.1
  else
    # TODO, notify here?
    error 'stop_command or stop_signals should be'
  end
end
prepare_command(command) click to toggle source
# File lib/eye/process/commands.rb, line 260
def prepare_command(command)
  if self.pid
    command.to_s.gsub('{{PID}}', self.pid.to_s).gsub('{PID}', self.pid.to_s)
  else
    command
  end
end
sleep_grace(grace_name) click to toggle source
# File lib/eye/process/commands.rb, line 268
def sleep_grace(grace_name)
  grace = self[grace_name].to_f
  info "sleeping for :#{grace_name} #{grace}"
  sleep grace
end