expedite 0.0.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 +7 -0
- data/README.md +43 -0
- data/bin/expedite +7 -0
- data/lib/expedite.rb +19 -0
- data/lib/expedite/application.rb +316 -0
- data/lib/expedite/application/boot.rb +9 -0
- data/lib/expedite/application_manager.rb +180 -0
- data/lib/expedite/cli.rb +42 -0
- data/lib/expedite/cli/server.rb +16 -0
- data/lib/expedite/cli/stop.rb +16 -0
- data/lib/expedite/client.rb +235 -0
- data/lib/expedite/command/basic.rb +20 -0
- data/lib/expedite/command/boot.rb +27 -0
- data/lib/expedite/command/info.rb +47 -0
- data/lib/expedite/commands.rb +62 -0
- data/lib/expedite/env.rb +60 -0
- data/lib/expedite/errors.rb +8 -0
- data/lib/expedite/failsafe_thread.rb +15 -0
- data/lib/expedite/load_helper.rb +11 -0
- data/lib/expedite/send_json.rb +10 -0
- data/lib/expedite/server.rb +190 -0
- data/lib/expedite/signals.rb +5 -0
- data/lib/expedite/variants.rb +89 -0
- data/lib/expedite/version.rb +3 -0
- metadata +66 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 492f096e30142c23ad6849f79864355a96fdb042990d43aaaaa85516643a6338
|
|
4
|
+
data.tar.gz: 29d32100e9df804748a684fdce1636907fe844996adb702e193369c434ffcd8a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d5f5fce18a565bbc3ea616bf1a5db621a1708eae3f6d1ce6ae6bb5c32d266859983ed35fefc8905184241602bc1554128627adf4ff3281f256056147351c9d2e
|
|
7
|
+
data.tar.gz: f85be9f1dfe698b1875fd0008d9a0c4ed324f5ddeb1b9562b83431864c4eaa664e1c83e748b680fb6877ecdd00c71a8160226613d52ae6f5338e7ec2195d7809
|
data/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Expedite
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Expedite is a Ruby preloader manager that allows commands to be executed against
|
|
6
|
+
preloaded Ruby applications. Preloader applications can derive from other preloaders, allowing
|
|
7
|
+
derivatives to start faster.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Register variants and commands in `expedite_helper.rb`. For example:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Expedite::Variants.register('base' do |name|
|
|
15
|
+
puts "Base started"
|
|
16
|
+
end
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
You can register variants that are based on other variants, and you can also have wildcard
|
|
20
|
+
matchers.
|
|
21
|
+
```
|
|
22
|
+
Expedite::Variants.register('development/*', parent: 'base') do |name|
|
|
23
|
+
customer = File.basename(name)
|
|
24
|
+
puts "Starting development for #{customer}"
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
You register commands by creating classes in the `Expedite::Command` module. For example,
|
|
29
|
+
this defines a `custom` command.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Expedite::Commands.register("custom") do
|
|
33
|
+
puts "[#{Expedite.variant}] sleeping for 5"
|
|
34
|
+
puts "$sleep_parent = #{$sleep_parent}"
|
|
35
|
+
puts "$sleep_child = #{$sleep_child}"
|
|
36
|
+
puts "[#{Expedite.variant}] done"
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then you can execute a command in the variant using:
|
|
41
|
+
```
|
|
42
|
+
Expedite.v("development/abc").call("custom")
|
|
43
|
+
```
|
data/bin/expedite
ADDED
data/lib/expedite.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require "expedite/client"
|
|
2
|
+
|
|
3
|
+
module Expedite
|
|
4
|
+
##
|
|
5
|
+
# Returns a client to dispatch actions to the specified variant
|
|
6
|
+
def self.variant(variant)
|
|
7
|
+
@clients ||= Hash.new do |h, k|
|
|
8
|
+
Client.new(env: Env.new, variant: variant)
|
|
9
|
+
end
|
|
10
|
+
@clients[variant]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Alias for self.variant
|
|
15
|
+
def self.v(variant)
|
|
16
|
+
self.variant(variant)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
@@ -0,0 +1,316 @@
|
|
|
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/commands'
|
|
7
|
+
require 'expedite/env'
|
|
8
|
+
require 'expedite/failsafe_thread'
|
|
9
|
+
require 'expedite/load_helper'
|
|
10
|
+
require 'expedite/signals'
|
|
11
|
+
|
|
12
|
+
module Expedite
|
|
13
|
+
def self.variant
|
|
14
|
+
app.variant
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.app=(app)
|
|
18
|
+
@app = app
|
|
19
|
+
end
|
|
20
|
+
def self.app
|
|
21
|
+
@app
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Application
|
|
25
|
+
include LoadHelper
|
|
26
|
+
include Signals
|
|
27
|
+
|
|
28
|
+
attr_reader :variant
|
|
29
|
+
attr_reader :manager, :env, :original_env
|
|
30
|
+
|
|
31
|
+
def initialize(variant, manager, original_env, env = Env.new)
|
|
32
|
+
@variant = variant
|
|
33
|
+
@manager = manager
|
|
34
|
+
@original_env = original_env
|
|
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
|
+
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:#{variant}] #{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 variant | #{app_name} | #{variant}"
|
|
110
|
+
|
|
111
|
+
Expedite::Variants.lookup(variant).after_fork(variant)
|
|
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 = JSON.load(client.read(client.gets.to_i)).values_at("args", "env")
|
|
137
|
+
|
|
138
|
+
exec_name = args.shift
|
|
139
|
+
command = Expedite::Commands.lookup(exec_name)
|
|
140
|
+
command.setup(client)
|
|
141
|
+
|
|
142
|
+
connect_database
|
|
143
|
+
|
|
144
|
+
pid = fork {
|
|
145
|
+
Process.setsid
|
|
146
|
+
IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
|
|
147
|
+
trap("TERM", "DEFAULT")
|
|
148
|
+
|
|
149
|
+
ARGV.replace(args)
|
|
150
|
+
$0 = exec_name
|
|
151
|
+
|
|
152
|
+
# Delete all env vars which are unchanged from before Spring started
|
|
153
|
+
original_env.each { |k, v| ENV.delete k if ENV[k] == v }
|
|
154
|
+
|
|
155
|
+
# Load in the current env vars, except those which *were* changed when Spring started
|
|
156
|
+
env.each { |k, v| ENV[k] = v }
|
|
157
|
+
|
|
158
|
+
# requiring is faster, so if config.cache_classes was true in
|
|
159
|
+
# the environment's config file, then we can respect that from
|
|
160
|
+
# here on as we no longer need constant reloading.
|
|
161
|
+
if @original_cache_classes
|
|
162
|
+
ActiveSupport::Dependencies.mechanism = :require
|
|
163
|
+
Rails.application.config.cache_classes = true
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
connect_database
|
|
167
|
+
srand
|
|
168
|
+
|
|
169
|
+
invoke_after_fork_callbacks
|
|
170
|
+
shush_backtraces
|
|
171
|
+
|
|
172
|
+
command.call
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
disconnect_database
|
|
176
|
+
|
|
177
|
+
log "forked #{pid}"
|
|
178
|
+
manager.puts pid
|
|
179
|
+
|
|
180
|
+
wait pid, streams, client
|
|
181
|
+
rescue Exception => e
|
|
182
|
+
log "exception: #{e} at #{e.backtrace.join("\n")}"
|
|
183
|
+
manager.puts unless pid
|
|
184
|
+
|
|
185
|
+
if streams && !e.is_a?(SystemExit)
|
|
186
|
+
print_exception(stderr, e)
|
|
187
|
+
streams.each(&:close)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
client.puts(1) if pid
|
|
191
|
+
client.close
|
|
192
|
+
ensure
|
|
193
|
+
# Redirect STDOUT and STDERR to prevent from keeping the original FDs
|
|
194
|
+
# (i.e. to prevent `spring rake -T | grep db` from hanging forever),
|
|
195
|
+
# even when exception is raised before forking (i.e. preloading).
|
|
196
|
+
reset_streams
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def terminate
|
|
200
|
+
if exiting?
|
|
201
|
+
# Ensure that we do not ignore subsequent termination attempts
|
|
202
|
+
log "forced exit"
|
|
203
|
+
@waiting.each { |pid| Process.kill("TERM", pid) }
|
|
204
|
+
Kernel.exit
|
|
205
|
+
else
|
|
206
|
+
state! :terminating
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def exit
|
|
211
|
+
state :exiting
|
|
212
|
+
manager.shutdown(:RDWR)
|
|
213
|
+
exit_if_finished
|
|
214
|
+
sleep
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def exit_if_finished
|
|
218
|
+
@mutex.synchronize {
|
|
219
|
+
Kernel.exit if exiting? && @waiting.empty?
|
|
220
|
+
}
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def invoke_after_fork_callbacks
|
|
224
|
+
# TODO:
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def disconnect_database
|
|
228
|
+
ActiveRecord::Base.remove_connection if active_record_configured?
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def connect_database
|
|
232
|
+
ActiveRecord::Base.establish_connection if active_record_configured?
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# This feels very naughty
|
|
236
|
+
def shush_backtraces
|
|
237
|
+
Kernel.module_eval do
|
|
238
|
+
old_raise = Kernel.method(:raise)
|
|
239
|
+
remove_method :raise
|
|
240
|
+
define_method :raise do |*args|
|
|
241
|
+
begin
|
|
242
|
+
old_raise.call(*args)
|
|
243
|
+
ensure
|
|
244
|
+
if $!
|
|
245
|
+
lib = File.expand_path("..", __FILE__)
|
|
246
|
+
$!.backtrace.reject! { |line| line.start_with?(lib) }
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
private :raise
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def print_exception(stream, error)
|
|
255
|
+
first, rest = error.backtrace.first, error.backtrace.drop(1)
|
|
256
|
+
stream.puts("#{first}: #{error} (#{error.class})")
|
|
257
|
+
rest.each { |line| stream.puts("\tfrom #{line}") }
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def with_pty
|
|
261
|
+
PTY.open do |master, slave|
|
|
262
|
+
[STDOUT, STDERR, STDIN].each { |s| s.reopen slave }
|
|
263
|
+
reader_thread = Expedite.failsafe_thread { master.read }
|
|
264
|
+
begin
|
|
265
|
+
yield
|
|
266
|
+
ensure
|
|
267
|
+
reader_thread.kill
|
|
268
|
+
reset_streams
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def reset_streams
|
|
274
|
+
[STDOUT, STDERR].each do |stream|
|
|
275
|
+
stream.reopen(env.log_file)
|
|
276
|
+
end
|
|
277
|
+
STDIN.reopen("/dev/null")
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def wait(pid, streams, client)
|
|
281
|
+
@mutex.synchronize { @waiting << pid }
|
|
282
|
+
|
|
283
|
+
# Wait in a separate thread so we can run multiple commands at once
|
|
284
|
+
Expedite.failsafe_thread {
|
|
285
|
+
begin
|
|
286
|
+
_, status = Process.wait2 pid
|
|
287
|
+
log "#{pid} exited with #{status.exitstatus}"
|
|
288
|
+
|
|
289
|
+
streams.each(&:close)
|
|
290
|
+
client.puts(status.exitstatus)
|
|
291
|
+
client.close
|
|
292
|
+
ensure
|
|
293
|
+
@mutex.synchronize { @waiting.delete pid }
|
|
294
|
+
exit_if_finished
|
|
295
|
+
end
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
Expedite.failsafe_thread {
|
|
299
|
+
while signal = client.gets.chomp
|
|
300
|
+
begin
|
|
301
|
+
Process.kill(signal, -Process.getpgid(pid))
|
|
302
|
+
client.puts(0)
|
|
303
|
+
rescue Errno::ESRCH
|
|
304
|
+
client.puts(1)
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
}
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
private
|
|
311
|
+
|
|
312
|
+
def active_record_configured?
|
|
313
|
+
defined?(ActiveRecord::Base) && ActiveRecord::Base.configurations.any?
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
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/send_json'
|
|
6
|
+
require 'expedite/variants'
|
|
7
|
+
|
|
8
|
+
module Expedite
|
|
9
|
+
class ApplicationManager
|
|
10
|
+
include SendJson
|
|
11
|
+
|
|
12
|
+
attr_reader :pid, :child, :variant, :env, :status
|
|
13
|
+
|
|
14
|
+
def initialize(variant, env)
|
|
15
|
+
@variant = variant
|
|
16
|
+
@env = env
|
|
17
|
+
@mutex = Mutex.new
|
|
18
|
+
@state = :running
|
|
19
|
+
@pid = nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def log(message)
|
|
23
|
+
env.log "[application_manager:#{variant}] #{message}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# We're not using @mutex.synchronize to avoid the weird "<internal:prelude>:10"
|
|
27
|
+
# line which messes with backtraces in e.g. rspec
|
|
28
|
+
def synchronize
|
|
29
|
+
@mutex.lock
|
|
30
|
+
yield
|
|
31
|
+
ensure
|
|
32
|
+
@mutex.unlock
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def start
|
|
36
|
+
start_child
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def restart
|
|
40
|
+
return if @state == :stopping
|
|
41
|
+
start_child(true)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def alive?
|
|
45
|
+
@pid
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def with_child
|
|
49
|
+
synchronize do
|
|
50
|
+
if alive?
|
|
51
|
+
begin
|
|
52
|
+
yield child
|
|
53
|
+
rescue Errno::ECONNRESET, Errno::EPIPE
|
|
54
|
+
# The child has died but has not been collected by the wait thread yet,
|
|
55
|
+
# so start a new child and try again.
|
|
56
|
+
log "child dead; starting"
|
|
57
|
+
start
|
|
58
|
+
yield child
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
log "child not running; starting"
|
|
62
|
+
start
|
|
63
|
+
yield child
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns the pid of the process running the command, or nil if the application process died.
|
|
69
|
+
def run(client)
|
|
70
|
+
@client = 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 Errno::ECONNRESET, Errno::EPIPE => e
|
|
83
|
+
log "#{e} while reading from child; returning no pid"
|
|
84
|
+
nil
|
|
85
|
+
ensure
|
|
86
|
+
client.close
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def stop
|
|
90
|
+
log "stopping"
|
|
91
|
+
@state = :stopping
|
|
92
|
+
|
|
93
|
+
if pid
|
|
94
|
+
Process.kill('TERM', pid)
|
|
95
|
+
Process.wait(pid)
|
|
96
|
+
end
|
|
97
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
|
98
|
+
# Don't care
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def parent
|
|
102
|
+
Expedite::Variants.lookup(variant).parent
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def start_child(preload = false)
|
|
108
|
+
if parent
|
|
109
|
+
fork_child(preload)
|
|
110
|
+
else
|
|
111
|
+
spawn_child(preload)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def fork_child(preload = false)
|
|
116
|
+
@child, child_socket = UNIXSocket.pair
|
|
117
|
+
|
|
118
|
+
# Compose command
|
|
119
|
+
wr, rd = UNIXSocket.pair
|
|
120
|
+
wr.send_io STDOUT
|
|
121
|
+
wr.send_io STDERR
|
|
122
|
+
wr.send_io STDIN
|
|
123
|
+
|
|
124
|
+
send_json wr, 'args' => ['expedite/boot', variant], 'env' => {}
|
|
125
|
+
wr.send_io child_socket
|
|
126
|
+
wr.send_io env.log_file
|
|
127
|
+
wr.close
|
|
128
|
+
|
|
129
|
+
@pid = env.applications[parent].run(rd)
|
|
130
|
+
|
|
131
|
+
start_wait_thread(pid, child) if child.gets
|
|
132
|
+
child_socket.close
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def spawn_child(preload = false)
|
|
136
|
+
@child, child_socket = UNIXSocket.pair
|
|
137
|
+
|
|
138
|
+
Bundler.with_original_env do
|
|
139
|
+
bundler_dir = File.expand_path("../..", $LOADED_FEATURES.grep(/bundler\/setup\.rb$/).first)
|
|
140
|
+
@pid = Process.spawn(
|
|
141
|
+
{
|
|
142
|
+
"EXPEDITE_VARIANT" => variant,
|
|
143
|
+
},
|
|
144
|
+
"ruby",
|
|
145
|
+
*(bundler_dir != RbConfig::CONFIG["rubylibdir"] ? ["-I", bundler_dir] : []),
|
|
146
|
+
"-I", File.expand_path("../..", __FILE__),
|
|
147
|
+
"-e", "require 'expedite/application/boot'",
|
|
148
|
+
3 => child_socket,
|
|
149
|
+
4 => env.log_file,
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
start_wait_thread(pid, child) if child.gets
|
|
154
|
+
child_socket.close
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def start_wait_thread(pid, child)
|
|
158
|
+
Process.detach(pid)
|
|
159
|
+
|
|
160
|
+
Expedite.failsafe_thread do
|
|
161
|
+
# The recv can raise an ECONNRESET, killing the thread, but that's ok
|
|
162
|
+
# as if it does we're no longer interested in the child
|
|
163
|
+
loop do
|
|
164
|
+
IO.select([child])
|
|
165
|
+
break if child.recv(1, Socket::MSG_PEEK).empty?
|
|
166
|
+
sleep 0.01
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
log "child #{pid} shutdown"
|
|
170
|
+
|
|
171
|
+
synchronize {
|
|
172
|
+
if @pid == pid
|
|
173
|
+
@pid = nil
|
|
174
|
+
restart
|
|
175
|
+
end
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|