class Expedite::ApplicationManager

Attributes

child[R]
env[R]
name[R]
pid[R]
status[R]
variant[R]

Public Class Methods

new(name, env) click to toggle source
# File lib/expedite/application_manager.rb, line 14
def initialize(name, env)
  @name  = name
  @env   = env
  @mutex = Mutex.new
  @state = :running
  @pid   = nil

  @variant = Expedite::Variants.lookup(@name)
end

Public Instance Methods

alive?() click to toggle source
# File lib/expedite/application_manager.rb, line 46
def alive?
  @pid
end
keep_alive() click to toggle source
# File lib/expedite/application_manager.rb, line 103
def keep_alive
  variant.keep_alive
end
log(message) click to toggle source
# File lib/expedite/application_manager.rb, line 24
def log(message)
  env.log "[application_manager:#{name}] #{message}"
end
parent() click to toggle source
# File lib/expedite/application_manager.rb, line 107
def parent
  variant.parent
end
restart() click to toggle source
# File lib/expedite/application_manager.rb, line 41
def restart
  return if @state == :stopping
  start_child(true)
end
run(client) click to toggle source

Returns the pid of the process running the command, or nil if the application process died.

# File lib/expedite/application_manager.rb, line 71
def run(client)
  @client = client
  with_child do |child|
    child.send_io client
    child.gets or raise Errno::EPIPE
  end

  pid = child.gets.to_i

  unless pid.zero?
    log "got worker pid #{pid}"
    pid
  end
rescue Errno::ECONNRESET, Errno::EPIPE => e
  log "#{e} while reading from child; returning no pid"
  nil
ensure
  client.close
end
start() click to toggle source
# File lib/expedite/application_manager.rb, line 37
def start
  start_child
end
stop() click to toggle source
# File lib/expedite/application_manager.rb, line 91
def stop
  log "stopping"
  @state = :stopping

  if pid
    Process.kill('TERM', pid)
    Process.wait(pid)
  end
rescue Errno::ESRCH, Errno::ECHILD
  # Don't care
end
synchronize() { || ... } click to toggle source

We're not using @mutex.synchronize to avoid the weird “<internal:prelude>:10” line which messes with backtraces in e.g. rspec

# File lib/expedite/application_manager.rb, line 30
def synchronize
  @mutex.lock
  yield
ensure
  @mutex.unlock
end
with_child() { |child| ... } click to toggle source
# File lib/expedite/application_manager.rb, line 50
def with_child
  synchronize do
    if alive?
      begin
        yield child
      rescue Errno::ECONNRESET, Errno::EPIPE
        # The child has died but has not been collected by the wait thread yet,
        # so start a new child and try again.
        log "child dead; starting"
        start
        yield child
      end
    else
      log "child not running; starting"
      start
      yield child
    end
  end
end

Private Instance Methods

fork_child(preload = false) click to toggle source
# File lib/expedite/application_manager.rb, line 121
def fork_child(preload = false)
  @child, child_socket = UNIXSocket.pair

  # Compose command
  wr, rd = UNIXSocket.pair
  wr.send_io STDOUT
  wr.send_io STDERR
  wr.send_io STDIN

  send_json wr, 'args' => ['expedite/boot', name], 'env' => {}
  wr.send_io child_socket
  wr.send_io env.log_file
  wr.close

  @pid = env.applications[parent].run(rd)

  start_wait_thread(pid, child) if child.gets
  child_socket.close
end
spawn_child(preload = false) click to toggle source
# File lib/expedite/application_manager.rb, line 141
def spawn_child(preload = false)
  @child, child_socket = UNIXSocket.pair

  Bundler.with_original_env do
    bundler_dir = File.expand_path("../..", $LOADED_FEATURES.grep(/bundler\/setup\.rb$/).first)
    @pid = Process.spawn(
      {
        "EXPEDITE_VARIANT" => name,
        "EXPEDITE_ROOT" => env.root,
      },
      "ruby",
      *(bundler_dir != RbConfig::CONFIG["rubylibdir"] ? ["-I", bundler_dir] : []),
      "-I", File.expand_path("../..", __FILE__),
      "-e", "require 'expedite/application/boot'",
      3 => child_socket,
      4 => env.log_file,
    )
  end

  start_wait_thread(pid, child) if child.gets
  child_socket.close
end
start_child(preload = false) click to toggle source
# File lib/expedite/application_manager.rb, line 113
def start_child(preload = false)
  if parent
    fork_child(preload)
  else
    spawn_child(preload)
  end
end
start_wait_thread(pid, child) click to toggle source
# File lib/expedite/application_manager.rb, line 164
def start_wait_thread(pid, child)
  Process.detach(pid)

  Expedite.failsafe_thread do
    # The recv can raise an ECONNRESET, killing the thread, but that's ok
    # as if it does we're no longer interested in the child
    loop do
      IO.select([child])
      break if child.recv(1, Socket::MSG_PEEK).empty?
      sleep 0.01
    end

    log "child #{pid} shutdown"

    synchronize {
      if @pid == pid
        @pid = nil
        restart if keep_alive
      end
    }
  end
end