1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
module Spring
ORIGINAL_ENV = ENV.to_hash
end
require "spring/boot"
require "spring/application_manager"
# Must be last, as it requires bundler/setup, which alters the load path
require "spring/commands"
module Spring
class Server
def self.boot
new.boot
end
attr_reader :env
def initialize(env = Env.new)
@env = env
@applications = Hash.new { |h, k| h[k] = ApplicationManager.new(k) }
@pidfile = env.pidfile_path.open('a')
@mutex = Mutex.new
end
def log(message)
env.log "[server] #{message}"
end
def boot
Spring.verify_environment
write_pidfile
set_pgid
ignore_signals
set_exit_hook
set_process_title
start_server
end
def start_server
server = UNIXServer.open(env.socket_name)
log "started on #{env.socket_name}"
loop { serve server.accept }
end
def serve(client)
log "accepted client"
client.puts env.version
app_client = client.recv_io
command = JSON.load(client.read(client.gets.to_i))
args, default_rails_env = command.values_at('args', 'default_rails_env')
if Spring.command?(args.first)
log "running command #{args.first}"
client.puts
client.puts @applications[rails_env_for(args, default_rails_env)].run(app_client)
else
log "command not found #{args.first}"
client.close
end
rescue SocketError => e
raise e unless client.eof?
ensure
redirect_output
end
def rails_env_for(args, default_rails_env)
Spring.command(args.first).env(args.drop(1)) || default_rails_env
end
# Boot the server into the process group of the current session.
# This will cause it to be automatically killed once the session
# ends (i.e. when the user closes their terminal).
def set_pgid
Process.setpgid(0, SID.pgid)
end
# Ignore SIGINT and SIGQUIT otherwise the user typing ^C or ^\ on the command line
# will kill the server/application.
def ignore_signals
IGNORE_SIGNALS.each { |sig| trap(sig, "IGNORE") }
end
def set_exit_hook
server_pid = Process.pid
# We don't want this hook to run in any forks of the current process
at_exit { shutdown if Process.pid == server_pid }
end
def shutdown
log "shutting down"
[env.socket_path, env.pidfile_path].each do |path|
if path.exist?
path.unlink rescue nil
end
end
@applications.values.map { |a| Thread.new { a.stop } }.map(&:join)
end
def write_pidfile
if @pidfile.flock(File::LOCK_EX | File::LOCK_NB)
@pidfile.truncate(0)
@pidfile.write("#{Process.pid}\n")
@pidfile.fsync
else
exit 1
end
end
# We need to redirect STDOUT and STDERR, otherwise the server will
# keep the original FDs open which would break piping. (e.g.
# `spring rake -T | grep db` would hang forever because the server
# would keep the stdout FD open.)
def redirect_output
[STDOUT, STDERR].each { |stream| stream.reopen(env.log_file) }
end
def set_process_title
ProcessTitleUpdater.run { |distance|
"spring server | #{env.app_name} | started #{distance} ago"
}
end
end
end
|