class Spring::ApplicationManager

Attributes

app_env[R]
child[R]
pid[R]
spring_env[R]
status[R]

Public Class Methods

new(app_env) click to toggle source
# File lib/spring/application_manager.rb, line 5
def initialize(app_env)
  @app_env    = app_env
  @spring_env = Env.new
  @mutex      = Mutex.new
  @state      = :running
end

Public Instance Methods

alive?() click to toggle source
# File lib/spring/application_manager.rb, line 34
def alive?
  @pid
end
log(message) click to toggle source
# File lib/spring/application_manager.rb, line 12
def log(message)
  spring_env.log "[application_manager:#{app_env}] #{message}"
end
restart() click to toggle source
# File lib/spring/application_manager.rb, line 29
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/spring/application_manager.rb, line 59
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
start() click to toggle source
# File lib/spring/application_manager.rb, line 25
def start
  start_child
end
stop() click to toggle source
# File lib/spring/application_manager.rb, line 78
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/spring/application_manager.rb, line 18
def synchronize
  @mutex.lock
  yield
ensure
  @mutex.unlock
end
with_child() { || ... } click to toggle source
# File lib/spring/application_manager.rb, line 38
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

Private Instance Methods

start_child(preload = false) click to toggle source
# File lib/spring/application_manager.rb, line 92
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
start_wait_thread(pid, child) click to toggle source
# File lib/spring/application_manager.rb, line 114
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