expedite 0.0.2 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +37 -30
- data/lib/expedite/{command/basic.rb → action/block.rb} +2 -2
- data/lib/expedite/{command → action}/boot.rb +6 -6
- data/lib/expedite/{commands.rb → actions.rb} +13 -13
- data/lib/expedite/agents.rb +101 -0
- data/lib/expedite/cli/server.rb +2 -2
- data/lib/expedite/cli/status.rb +34 -0
- data/lib/expedite/cli/stop.rb +3 -3
- data/lib/expedite/cli.rb +8 -2
- data/lib/expedite/client/exec.rb +124 -0
- data/lib/expedite/client/invoke.rb +155 -0
- data/lib/expedite/env.rb +2 -1
- data/lib/expedite/errors.rb +6 -0
- data/lib/expedite/helper/rails.rb +14 -0
- data/lib/expedite/protocol.rb +20 -0
- data/lib/expedite/server/agent.rb +322 -0
- data/lib/expedite/{application/boot.rb → server/agent_boot.rb} +3 -3
- data/lib/expedite/server/agent_manager.rb +192 -0
- data/lib/expedite/server/controller.rb +247 -0
- data/lib/expedite/syntax.rb +26 -0
- data/lib/expedite/version.rb +1 -1
- data/lib/expedite.rb +25 -11
- metadata +17 -14
- data/lib/expedite/application.rb +0 -316
- data/lib/expedite/application_manager.rb +0 -187
- data/lib/expedite/client.rb +0 -235
- data/lib/expedite/command/info.rb +0 -49
- data/lib/expedite/send_json.rb +0 -10
- data/lib/expedite/server.rb +0 -188
- data/lib/expedite/variants.rb +0 -99
data/lib/expedite/env.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require 'digest'
|
|
2
2
|
require 'pathname'
|
|
3
3
|
require 'expedite/version'
|
|
4
|
+
require 'expedite/server/agent_manager'
|
|
4
5
|
|
|
5
6
|
module Expedite
|
|
6
7
|
class Env
|
|
@@ -18,7 +19,7 @@ module Expedite
|
|
|
18
19
|
|
|
19
20
|
env = self
|
|
20
21
|
@applications = Hash.new do |h, k|
|
|
21
|
-
h[k] =
|
|
22
|
+
h[k] = Server::AgentManager.new(k, env)
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
|
data/lib/expedite/errors.rb
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
Expedite.define do
|
|
3
|
+
agent :rails_environment do
|
|
4
|
+
app_root = Dir.pwd
|
|
5
|
+
|
|
6
|
+
require "#{app_root}/config/boot.rb"
|
|
7
|
+
|
|
8
|
+
require "rack"
|
|
9
|
+
rackup_file = "#{app_root}/config.ru"
|
|
10
|
+
Rack::Builder.load_file(rackup_file)
|
|
11
|
+
|
|
12
|
+
Rails.application.eager_load!
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
|
|
3
|
+
module Expedite
|
|
4
|
+
module Protocol
|
|
5
|
+
def send_object(object)
|
|
6
|
+
data = Marshal.dump(object)
|
|
7
|
+
|
|
8
|
+
self.puts data.bytesize.to_i
|
|
9
|
+
self.write data
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def recv_object
|
|
13
|
+
len = self.gets.to_i
|
|
14
|
+
data = self.read(len)
|
|
15
|
+
Marshal.load(data)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
IO.include ::Expedite::Protocol
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# Based on https://github.com/rails/spring/blob/master/lib/spring/application.rb
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'pty'
|
|
4
|
+
require 'set'
|
|
5
|
+
require 'socket'
|
|
6
|
+
require 'expedite/actions'
|
|
7
|
+
require 'expedite/env'
|
|
8
|
+
require 'expedite/failsafe_thread'
|
|
9
|
+
require 'expedite/protocol'
|
|
10
|
+
require 'expedite/signals'
|
|
11
|
+
require 'expedite/agents'
|
|
12
|
+
|
|
13
|
+
module Expedite
|
|
14
|
+
def self.agent
|
|
15
|
+
app.agent
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.app=(app)
|
|
19
|
+
@app = app
|
|
20
|
+
end
|
|
21
|
+
def self.app
|
|
22
|
+
@app
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module Server
|
|
26
|
+
class Agent
|
|
27
|
+
include Signals
|
|
28
|
+
|
|
29
|
+
attr_reader :agent
|
|
30
|
+
attr_reader :manager, :env
|
|
31
|
+
|
|
32
|
+
def initialize(agent:, manager:, env:)
|
|
33
|
+
@agent = agent
|
|
34
|
+
@manager = manager
|
|
35
|
+
@env = env
|
|
36
|
+
@mutex = Mutex.new
|
|
37
|
+
@waiting = Set.new
|
|
38
|
+
@preloaded = false
|
|
39
|
+
@state = :initialized
|
|
40
|
+
@interrupt = IO.pipe
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def boot
|
|
44
|
+
# This is necessary for the terminal to work correctly when we reopen stdin.
|
|
45
|
+
Process.setsid rescue Errno::EPERM
|
|
46
|
+
|
|
47
|
+
Expedite.app = self
|
|
48
|
+
|
|
49
|
+
Signal.trap("TERM") { terminate }
|
|
50
|
+
|
|
51
|
+
env.load_helper
|
|
52
|
+
eager_preload if false #if ENV.delete("SPRING_PRELOAD") == "1"
|
|
53
|
+
run
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def state(val)
|
|
57
|
+
return if exiting?
|
|
58
|
+
log "#{@state} -> #{val}"
|
|
59
|
+
@state = val
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def state!(val)
|
|
63
|
+
state val
|
|
64
|
+
@interrupt.last.write "."
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def app_name
|
|
68
|
+
env.app_name
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def log(message)
|
|
72
|
+
env.log "[application:#{agent}] #{message}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def preloaded?
|
|
76
|
+
@preloaded
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def preload_failed?
|
|
80
|
+
@preloaded == :failure
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def exiting?
|
|
84
|
+
@state == :exiting
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def terminating?
|
|
88
|
+
@state == :terminating
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def initialized?
|
|
92
|
+
@state == :initialized
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def preload
|
|
96
|
+
log "preloading app"
|
|
97
|
+
|
|
98
|
+
@preloaded = :success
|
|
99
|
+
rescue Exception => e
|
|
100
|
+
@preloaded = :failure
|
|
101
|
+
raise e unless initialized?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def eager_preload
|
|
105
|
+
with_pty { preload }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def run
|
|
109
|
+
$0 = "expedite agent | #{app_name} | #{agent}"
|
|
110
|
+
|
|
111
|
+
Expedite::Agents.lookup(agent).after_fork(agent)
|
|
112
|
+
|
|
113
|
+
state :running
|
|
114
|
+
manager.puts
|
|
115
|
+
|
|
116
|
+
loop do
|
|
117
|
+
IO.select [manager, @interrupt.first]
|
|
118
|
+
|
|
119
|
+
if terminating? || preload_failed?
|
|
120
|
+
exit
|
|
121
|
+
else
|
|
122
|
+
serve manager.recv_io(UNIXSocket)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def serve(client)
|
|
128
|
+
log "got client"
|
|
129
|
+
manager.puts
|
|
130
|
+
|
|
131
|
+
_stdout, stderr, _stdin = streams = 3.times.map { client.recv_io }
|
|
132
|
+
[STDOUT, STDERR, STDIN].zip(streams).each { |a, b| a.reopen(b) }
|
|
133
|
+
|
|
134
|
+
preload unless preloaded?
|
|
135
|
+
|
|
136
|
+
args, env = client.recv_object.values_at("args", "env")
|
|
137
|
+
|
|
138
|
+
exec_name = args.shift
|
|
139
|
+
action = Expedite::Actions.lookup(exec_name)
|
|
140
|
+
action.setup(client)
|
|
141
|
+
|
|
142
|
+
connect_database
|
|
143
|
+
|
|
144
|
+
pid = fork do
|
|
145
|
+
Process.setsid
|
|
146
|
+
IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
|
|
147
|
+
trap("TERM", "DEFAULT")
|
|
148
|
+
|
|
149
|
+
# Load in the current env vars, except those which *were* changed when Spring started
|
|
150
|
+
env.each { |k, v| ENV[k] = v }
|
|
151
|
+
|
|
152
|
+
# requiring is faster, so if config.cache_classes was true in
|
|
153
|
+
# the environment's config file, then we can respect that from
|
|
154
|
+
# here on as we no longer need constant reloading.
|
|
155
|
+
if @original_cache_classes
|
|
156
|
+
ActiveSupport::Dependencies.mechanism = :require
|
|
157
|
+
Rails.application.config.cache_classes = true
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
connect_database
|
|
161
|
+
srand
|
|
162
|
+
|
|
163
|
+
invoke_after_fork_callbacks
|
|
164
|
+
shush_backtraces
|
|
165
|
+
|
|
166
|
+
begin
|
|
167
|
+
ret = action.call(*args)
|
|
168
|
+
rescue => e
|
|
169
|
+
client.send_object("exception" => e)
|
|
170
|
+
else
|
|
171
|
+
client.send_object("return" => ret )
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
disconnect_database
|
|
176
|
+
|
|
177
|
+
log "forked #{pid}"
|
|
178
|
+
manager.puts pid
|
|
179
|
+
|
|
180
|
+
# Boot makes a new application, so we don't wait for it
|
|
181
|
+
if action.is_a?(Expedite::Action::Boot)
|
|
182
|
+
Process.detach(pid)
|
|
183
|
+
else
|
|
184
|
+
wait pid, streams, client
|
|
185
|
+
end
|
|
186
|
+
rescue Exception => e
|
|
187
|
+
log "exception: #{e} at #{e.backtrace.join("\n")}"
|
|
188
|
+
manager.puts unless pid
|
|
189
|
+
|
|
190
|
+
if streams && !e.is_a?(SystemExit)
|
|
191
|
+
print_exception(stderr, e)
|
|
192
|
+
streams.each(&:close)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
client.puts(1) if pid
|
|
196
|
+
client.close
|
|
197
|
+
ensure
|
|
198
|
+
# Redirect STDOUT and STDERR to prevent from keeping the original FDs
|
|
199
|
+
# (i.e. to prevent `spring rake -T | grep db` from hanging forever),
|
|
200
|
+
# even when exception is raised before forking (i.e. preloading).
|
|
201
|
+
reset_streams
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def terminate
|
|
205
|
+
if exiting?
|
|
206
|
+
# Ensure that we do not ignore subsequent termination attempts
|
|
207
|
+
log "forced exit"
|
|
208
|
+
@waiting.each { |pid| Process.kill("TERM", pid) }
|
|
209
|
+
Kernel.exit
|
|
210
|
+
else
|
|
211
|
+
state! :terminating
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def exit
|
|
216
|
+
state :exiting
|
|
217
|
+
manager.shutdown(:RDWR)
|
|
218
|
+
exit_if_finished
|
|
219
|
+
sleep
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def exit_if_finished
|
|
223
|
+
@mutex.synchronize {
|
|
224
|
+
Kernel.exit if exiting? && @waiting.empty?
|
|
225
|
+
}
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def invoke_after_fork_callbacks
|
|
229
|
+
# TODO:
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def disconnect_database
|
|
233
|
+
ActiveRecord::Base.remove_connection if active_record_configured?
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def connect_database
|
|
237
|
+
ActiveRecord::Base.establish_connection if active_record_configured?
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# This feels very naughty
|
|
241
|
+
def shush_backtraces
|
|
242
|
+
Kernel.module_eval do
|
|
243
|
+
old_raise = Kernel.method(:raise)
|
|
244
|
+
remove_method :raise
|
|
245
|
+
define_method :raise do |*args|
|
|
246
|
+
begin
|
|
247
|
+
old_raise.call(*args)
|
|
248
|
+
ensure
|
|
249
|
+
if $!
|
|
250
|
+
lib = File.expand_path("..", __FILE__)
|
|
251
|
+
$!.backtrace.reject! { |line| line.start_with?(lib) }
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
private :raise
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def print_exception(stream, error)
|
|
260
|
+
first, rest = error.backtrace.first, error.backtrace.drop(1)
|
|
261
|
+
stream.puts("#{first}: #{error} (#{error.class})")
|
|
262
|
+
rest.each { |line| stream.puts("\tfrom #{line}") }
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def with_pty
|
|
266
|
+
PTY.open do |master, slave|
|
|
267
|
+
[STDOUT, STDERR, STDIN].each { |s| s.reopen slave }
|
|
268
|
+
reader_thread = Expedite.failsafe_thread { master.read }
|
|
269
|
+
begin
|
|
270
|
+
yield
|
|
271
|
+
ensure
|
|
272
|
+
reader_thread.kill
|
|
273
|
+
reset_streams
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def reset_streams
|
|
279
|
+
[STDOUT, STDERR].each do |stream|
|
|
280
|
+
stream.reopen(env.log_file)
|
|
281
|
+
end
|
|
282
|
+
STDIN.reopen("/dev/null")
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def wait(pid, streams, client)
|
|
286
|
+
@mutex.synchronize { @waiting << pid }
|
|
287
|
+
|
|
288
|
+
# Wait in a separate thread so we can run multiple actions at once
|
|
289
|
+
Expedite.failsafe_thread {
|
|
290
|
+
begin
|
|
291
|
+
_, status = Process.wait2 pid
|
|
292
|
+
log "#{pid} exited with #{status.exitstatus}"
|
|
293
|
+
|
|
294
|
+
streams.each(&:close)
|
|
295
|
+
client.puts(status.exitstatus)
|
|
296
|
+
client.close
|
|
297
|
+
ensure
|
|
298
|
+
@mutex.synchronize { @waiting.delete pid }
|
|
299
|
+
exit_if_finished
|
|
300
|
+
end
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
Expedite.failsafe_thread {
|
|
304
|
+
while signal = client.gets.chomp
|
|
305
|
+
begin
|
|
306
|
+
Process.kill(signal, -Process.getpgid(pid))
|
|
307
|
+
client.puts(0)
|
|
308
|
+
rescue Errno::ESRCH
|
|
309
|
+
client.puts(1)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
}
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
private
|
|
316
|
+
|
|
317
|
+
def active_record_configured?
|
|
318
|
+
defined?(ActiveRecord::Base) && ActiveRecord::Base.configurations.any?
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
require "expedite/
|
|
1
|
+
require "expedite/server/agent"
|
|
2
2
|
|
|
3
|
-
app = Expedite::
|
|
4
|
-
|
|
3
|
+
app = Expedite::Server::Agent.new(
|
|
4
|
+
agent: ENV['EXPEDITE_VARIANT'],
|
|
5
5
|
manager: UNIXSocket.for_fd(3),
|
|
6
6
|
env: Expedite::Env.new(
|
|
7
7
|
root: ENV['EXPEDITE_ROOT'],
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Based on https://github.com/rails/spring/blob/master/lib/spring/application_manager.rb
|
|
2
|
+
|
|
3
|
+
require 'bundler'
|
|
4
|
+
require 'expedite/failsafe_thread'
|
|
5
|
+
require 'expedite/protocol'
|
|
6
|
+
require 'expedite/agents'
|
|
7
|
+
|
|
8
|
+
module Expedite
|
|
9
|
+
module Server
|
|
10
|
+
class AgentManager
|
|
11
|
+
attr_reader :pid, :child, :name, :env, :status, :agent
|
|
12
|
+
|
|
13
|
+
def initialize(name, env)
|
|
14
|
+
@name = name.to_s
|
|
15
|
+
@env = env
|
|
16
|
+
@mutex = Mutex.new
|
|
17
|
+
@state = :running
|
|
18
|
+
@pid = nil
|
|
19
|
+
|
|
20
|
+
@agent = Expedite::Agents.lookup(@name)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def log(message)
|
|
24
|
+
env.log "[application_manager:#{name}] #{message}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# We're not using @mutex.synchronize to avoid the weird "<internal:prelude>:10"
|
|
28
|
+
# line which messes with backtraces in e.g. rspec
|
|
29
|
+
def synchronize
|
|
30
|
+
@mutex.lock
|
|
31
|
+
yield
|
|
32
|
+
ensure
|
|
33
|
+
@mutex.unlock
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def start
|
|
37
|
+
start_child
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def restart
|
|
41
|
+
return if @state == :stopping
|
|
42
|
+
start_child(true)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def alive?
|
|
46
|
+
@pid
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def with_child
|
|
50
|
+
synchronize do
|
|
51
|
+
if alive?
|
|
52
|
+
begin
|
|
53
|
+
yield child
|
|
54
|
+
rescue Errno::ECONNRESET, Errno::EPIPE
|
|
55
|
+
# The child has died but has not been collected by the wait thread yet,
|
|
56
|
+
# so start a new child and try again.
|
|
57
|
+
log "child dead; starting"
|
|
58
|
+
start
|
|
59
|
+
yield child
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
log "child not running; starting"
|
|
63
|
+
start
|
|
64
|
+
yield child
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns the pid of the process running the command, or nil if the application process died.
|
|
70
|
+
def run(client)
|
|
71
|
+
with_child do |child|
|
|
72
|
+
child.send_io client
|
|
73
|
+
child.gets or raise Errno::EPIPE
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
pid = child.gets.to_i
|
|
77
|
+
|
|
78
|
+
unless pid.zero?
|
|
79
|
+
log "got worker pid #{pid}"
|
|
80
|
+
pid
|
|
81
|
+
end
|
|
82
|
+
rescue Exception => e
|
|
83
|
+
# NotImplementedError is an Exception, not StandardError
|
|
84
|
+
client.send_object("exception" => e)
|
|
85
|
+
return Process.pid
|
|
86
|
+
rescue Errno::ECONNRESET, Errno::EPIPE => e
|
|
87
|
+
log "#{e} while reading from child; returning no pid"
|
|
88
|
+
nil
|
|
89
|
+
ensure
|
|
90
|
+
client.close
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def stop
|
|
94
|
+
log "stopping"
|
|
95
|
+
@state = :stopping
|
|
96
|
+
|
|
97
|
+
if pid
|
|
98
|
+
Process.kill('TERM', pid)
|
|
99
|
+
Process.wait(pid)
|
|
100
|
+
end
|
|
101
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
|
102
|
+
# Don't care
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def keep_alive
|
|
106
|
+
agent.keep_alive
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def parent
|
|
110
|
+
agent.parent
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def start_child(preload = false)
|
|
116
|
+
if parent
|
|
117
|
+
fork_child(preload)
|
|
118
|
+
else
|
|
119
|
+
spawn_child(preload)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def fork_child(preload = false)
|
|
124
|
+
@child, child_socket = UNIXSocket.pair
|
|
125
|
+
|
|
126
|
+
# Compose command
|
|
127
|
+
wr, rd = UNIXSocket.pair
|
|
128
|
+
wr.send_io STDOUT
|
|
129
|
+
wr.send_io STDERR
|
|
130
|
+
wr.send_io STDIN
|
|
131
|
+
|
|
132
|
+
wr.send_object(
|
|
133
|
+
'args' => ['expedite/boot', name],
|
|
134
|
+
'env' => {}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
wr.send_io child_socket
|
|
138
|
+
wr.send_io env.log_file
|
|
139
|
+
wr.close
|
|
140
|
+
|
|
141
|
+
@pid = env.applications[parent].run(rd)
|
|
142
|
+
|
|
143
|
+
start_wait_thread(pid, child) if child.gets
|
|
144
|
+
child_socket.close
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def spawn_child(preload = false)
|
|
148
|
+
@child, child_socket = UNIXSocket.pair
|
|
149
|
+
|
|
150
|
+
bundler_dir = File.expand_path("../..", $LOADED_FEATURES.grep(/bundler\/setup\.rb$/).first)
|
|
151
|
+
@pid = Process.spawn(
|
|
152
|
+
{
|
|
153
|
+
"EXPEDITE_VARIANT" => name,
|
|
154
|
+
"EXPEDITE_ROOT" => env.root,
|
|
155
|
+
},
|
|
156
|
+
"ruby",
|
|
157
|
+
*(bundler_dir != RbConfig::CONFIG["rubylibdir"] ? ["-I", bundler_dir] : []),
|
|
158
|
+
"-I", File.expand_path("../../..", __FILE__),
|
|
159
|
+
"-e", "require 'expedite/server/agent_boot'",
|
|
160
|
+
3 => child_socket,
|
|
161
|
+
4 => env.log_file,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
start_wait_thread(@pid, child) if child.gets
|
|
165
|
+
child_socket.close
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def start_wait_thread(pid, child)
|
|
169
|
+
Process.detach(pid)
|
|
170
|
+
|
|
171
|
+
Expedite.failsafe_thread do
|
|
172
|
+
# The recv can raise an ECONNRESET, killing the thread, but that's ok
|
|
173
|
+
# as if it does we're no longer interested in the child
|
|
174
|
+
loop do
|
|
175
|
+
IO.select([child])
|
|
176
|
+
break if child.recv(1, Socket::MSG_PEEK).empty?
|
|
177
|
+
sleep 0.01
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
log "child #{pid} shutdown"
|
|
181
|
+
|
|
182
|
+
synchronize {
|
|
183
|
+
if @pid == pid
|
|
184
|
+
@pid = nil
|
|
185
|
+
restart if keep_alive
|
|
186
|
+
end
|
|
187
|
+
}
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|