class Invoker::ProcessManager

Class is responsible for managing all the processes Invoker is supposed to manage. Takes care of starting, stopping and restarting processes.

Constants

LABEL_COLORS

Attributes

open_pipes[RW]
workers[RW]

Public Class Methods

new() click to toggle source
# File lib/invoker/process_manager.rb, line 8
def initialize
  @open_pipes = {}
  @workers = {}
  @worker_mutex = Mutex.new
  @thread_group = ThreadGroup.new
end

Public Instance Methods

get_worker_from_fd(fd) click to toggle source

Given a file descriptor returns the worker object

@param fd [IO] an IO object with valid file descriptor @return [Invoker::CommandWorker] The worker object which is associated with this fd

# File lib/invoker/process_manager.rb, line 85
def get_worker_from_fd(fd)
  open_pipes[fd.fileno]
end
kill_workers() click to toggle source
# File lib/invoker/process_manager.rb, line 111
def kill_workers
  @workers.each do |key, worker|
    kill_or_remove_process(worker.pid, "INT", worker.command_label)
  end
  @workers = {}
end
load_env(directory = nil) click to toggle source
# File lib/invoker/process_manager.rb, line 89
def load_env(directory = nil)
  directory ||= ENV['PWD']

  if !directory || directory.empty? || !Dir.exist?(directory)
    return {}
  end

  default_env = File.join(directory, '.env')
  local_env = File.join(directory, '.env.local')
  env = {}

  if File.exist?(default_env)
    env.merge!(Dotenv::Environment.new(default_env))
  end

  if File.exist?(local_env)
    env.merge!(Dotenv::Environment.new(local_env))
  end

  env
end
process_list() click to toggle source

List currently running commands

# File lib/invoker/process_manager.rb, line 119
def process_list
  Invoker::IPC::Message::ListResponse.from_workers(workers)
end
restart_process(reload_message) click to toggle source

Receive a message from user to restart a Process @param [Invoker::IPC::Message::Reload]

# File lib/invoker/process_manager.rb, line 58
def restart_process(reload_message)
  command_label = reload_message.process_name
  if stop_process(reload_message.remove_message)
    Invoker.commander.schedule_event(command_label, :worker_removed) do
      start_process_by_name(command_label)
    end
  else
    start_process_by_name(command_label)
  end
end
run_power_server() click to toggle source
# File lib/invoker/process_manager.rb, line 69
def run_power_server
  return unless Invoker.can_run_balancer?(false)

  powerup_id = Invoker::Power::Powerup.fork_and_start
  wait_on_pid("powerup_manager", powerup_id)
  at_exit do
    begin
      Process.kill("INT", powerup_id)
    rescue Errno::ESRCH; end
  end
end
start_process(process_info) click to toggle source
# File lib/invoker/process_manager.rb, line 15
def start_process(process_info)
  m, s = PTY.open
  s.raw! # disable newline conversion.

  pid = run_command(process_info, s)

  s.close

  worker = CommandWorker.new(process_info.label, m, pid, select_color)

  add_worker(worker)
  wait_on_pid(process_info.label, pid)
end
start_process_by_name(process_name) click to toggle source

Start a process given their name @param process_name [String] Command label of process specified in config file.

# File lib/invoker/process_manager.rb, line 31
def start_process_by_name(process_name)
  if process_running?(process_name)
    Invoker::Logger.puts "\nProcess '#{process_name}' is already running".color(:red)
    return false
  end

  process_info = Invoker.config.process(process_name)
  start_process(process_info) if process_info
end
stop_process(remove_message) click to toggle source

Remove a process from list of processes managed by invoker supervisor.It also kills the process before removing it from the list.

@param remove_message [Invoker::IPC::Message::Remove] @return [Boolean] if process existed and was removed else false

# File lib/invoker/process_manager.rb, line 46
def stop_process(remove_message)
  worker = workers[remove_message.process_name]
  command_label = remove_message.process_name
  return false unless worker
  signal_to_use = remove_message.signal || 'INT'

  Invoker::Logger.puts("Removing #{command_label} with signal #{signal_to_use}".color(:red))
  kill_or_remove_process(worker.pid, signal_to_use, command_label)
end

Private Instance Methods

add_worker(worker) click to toggle source

add worker to global collections

# File lib/invoker/process_manager.rb, line 178
def add_worker(worker)
  @open_pipes[worker.pipe_end.fileno] = worker
  @workers[worker.command_label] = worker
  Invoker.commander.watch_for_read(worker.pipe_end)
end
kill_or_remove_process(pid, signal_to_use, command_label) click to toggle source
# File lib/invoker/process_manager.rb, line 148
def kill_or_remove_process(pid, signal_to_use, command_label)
  process_kill(pid, signal_to_use)
  true
rescue Errno::ESRCH
  Invoker::Logger.puts("Killing process with #{pid} and name #{command_label} failed".color(:red))
  remove_worker(command_label, false)
  false
end
process_kill(pid, signal_to_use) click to toggle source
# File lib/invoker/process_manager.rb, line 157
def process_kill(pid, signal_to_use)
  if signal_to_use.to_i == 0
    Process.kill(signal_to_use, -Process.getpgid(pid))
  else
    Process.kill(signal_to_use.to_i, -Process.getpgid(pid))
  end
end
process_running?(command_label) click to toggle source
# File lib/invoker/process_manager.rb, line 144
def process_running?(command_label)
  !!workers[command_label]
end
remove_worker(command_label, trigger_event = true) click to toggle source

Remove worker from all collections

# File lib/invoker/process_manager.rb, line 166
def remove_worker(command_label, trigger_event = true)
  worker = @workers[command_label]
  if worker
    @open_pipes.delete(worker.pipe_end.fileno)
    @workers.delete(command_label)
  end
  if trigger_event
    Invoker.commander.trigger(command_label, :worker_removed)
  end
end
run_command(process_info, write_pipe) click to toggle source
# File lib/invoker/process_manager.rb, line 184
def run_command(process_info, write_pipe)
  command_label = process_info.label

  Invoker.commander.schedule_event(command_label, :exit) { remove_worker(command_label) }

  env_options = load_env(process_info.dir)

  spawn_options = {
    :chdir => process_info.dir || ENV['PWD'], :out => write_pipe, :err => write_pipe,
    :pgroup => true, :close_others => true, :in => :close
  }
  Invoker.run_without_bundler { spawn(env_options, process_info.cmd, spawn_options) }
end
select_color() click to toggle source
# File lib/invoker/process_manager.rb, line 138
def select_color
  selected_color = LABEL_COLORS.shift
  LABEL_COLORS.push(selected_color)
  selected_color
end
wait_on_pid(command_label, pid) click to toggle source
# File lib/invoker/process_manager.rb, line 125
def wait_on_pid(command_label, pid)
  raise Invoker::Errors::ToomanyOpenConnections if @thread_group.enclosed?

  thread = Thread.new do
    Process.wait(pid)
    message = "Process with command #{command_label} exited with status #{$?.exitstatus}"
    Invoker::Logger.puts("\n#{message}".color(:red))
    Invoker.notify_user(message)
    Invoker.commander.trigger(command_label, :exit)
  end
  @thread_group.add(thread)
end