class Spring::ApplicationManager

def alive?

def alive?
  @pid
end

def initialize(app_env)

def initialize(app_env)
  @app_env    = app_env
  @spring_env = Env.new
  @mutex      = Mutex.new
  @state      = :running
end

def log(message)

def log(message)
  spring_env.log "[application_manager:#{app_env}] #{message}"
end

def restart

def restart
  return if @state == :stopping
  start_child(true)
end

def run(client)

Returns the pid of the process running the command, or nil if the application process died.
def run(client)
  with_child do
    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

def start

def start
  start_child
end

def start_child(preload = false)

def start_child(preload = false)
  @child, child_socket = UNIXSocket.pair
  Bundler.with_clean_env do
    @pid = Process.spawn(
      {
        "RAILS_ENV"           => app_env,
        "RACK_ENV"            => app_env,
        "SPRING_ORIGINAL_ENV" => JSON.dump(Spring::ORIGINAL_ENV),
        "SPRING_PRELOAD"      => preload ? "1" : "0"
      },
      "ruby",
      "-I", File.expand_path("../..", __FILE__),
      "-e", "require 'spring/application/boot'",
      3 => child_socket
    )
  end
  start_wait_thread(pid, child) if child.gets
  child_socket.close
end

def start_wait_thread(pid, child)

def start_wait_thread(pid, child)
  Process.detach(pid)
  Thread.new {
    # 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
      end
    }
  }
end

def stop

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

def synchronize

line which messes with backtraces in e.g. rspec
We're not using @mutex.synchronize to avoid the weird ":10"
def synchronize
  @mutex.lock
  yield
ensure
  @mutex.unlock
end

def with_child

def with_child
  synchronize do
    if alive?
      begin
        yield
      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
      end
    else
      log "child not running; starting"
      start
      yield
    end
  end
end