forked from rails/spring
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapplication_manager.rb
144 lines (124 loc) · 3.45 KB
/
application_manager.rb
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
module Spring
class ApplicationManager
attr_reader :pid, :child, :app_env, :spawn_env, :spring_env, :status
def initialize(app_env, spawn_env, spring_env)
@app_env = app_env
@spawn_env = spawn_env
@spring_env = spring_env
@mutex = Mutex.new
@state = :running
@pid = nil
end
def log(message)
spring_env.log "[application_manager:#{app_env}] #{message}"
end
# We're not using @mutex.synchronize to avoid the weird "<internal:prelude>:10"
# line which messes with backtraces in e.g. rspec
def synchronize
@mutex.lock
yield
ensure
@mutex.unlock
end
def start
start_child
end
def restart
return if @state == :stopping
start_child(true)
end
def alive?
@pid
end
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
# 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 stop
log "stopping"
@state = :stopping
if pid
Process.kill('TERM', pid)
Process.wait(pid)
end
rescue Errno::ESRCH, Errno::ECHILD
# Don't care
end
private
def start_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(
{
"RAILS_ENV" => app_env,
"RACK_ENV" => app_env,
"SPRING_ORIGINAL_ENV" => JSON.dump(Spring::ORIGINAL_ENV),
"SPRING_PRELOAD" => preload ? "1" : "0",
"SPRING_SPAWN_ENV" => JSON.dump(spawn_env),
**spawn_env,
},
"ruby",
*(bundler_dir != RbConfig::CONFIG["rubylibdir"] ? ["-I", bundler_dir] : []),
"-I", File.expand_path("../..", __FILE__),
"-e", "require 'spring/application/boot'",
3 => child_socket,
4 => spring_env.log_file,
)
end
start_wait_thread(pid, child) if child.gets
child_socket.close
end
def start_wait_thread(pid, child)
Process.detach(pid)
Spring.failsafe_thread {
# 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
end
end