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
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Based on https://github.com/rails/spring/blob/master/lib/spring/server.rb
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'socket'
|
|
4
|
+
require "expedite/env"
|
|
5
|
+
require "expedite/protocol"
|
|
6
|
+
require "expedite/signals"
|
|
7
|
+
|
|
8
|
+
module Expedite
|
|
9
|
+
module Server
|
|
10
|
+
class Controller
|
|
11
|
+
include Signals
|
|
12
|
+
|
|
13
|
+
def self.boot(options = {})
|
|
14
|
+
new(options).boot
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :env
|
|
18
|
+
|
|
19
|
+
def initialize(foreground: true, env: nil)
|
|
20
|
+
@foreground = foreground
|
|
21
|
+
@env = env || default_env
|
|
22
|
+
@pidfile = @env.pidfile_path.open('a')
|
|
23
|
+
@mutex = Mutex.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def foreground?
|
|
27
|
+
@foreground
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def log(message)
|
|
31
|
+
env.log "[server] #{message}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def boot
|
|
35
|
+
env.load_helper
|
|
36
|
+
|
|
37
|
+
write_pidfile
|
|
38
|
+
set_pgid unless foreground?
|
|
39
|
+
ignore_signals unless foreground?
|
|
40
|
+
set_exit_hook
|
|
41
|
+
set_process_title
|
|
42
|
+
start_server
|
|
43
|
+
exit 0
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def pid
|
|
47
|
+
@env.pidfile_path.read.to_i
|
|
48
|
+
rescue Errno::ENOENT
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def running?
|
|
53
|
+
pidfile = @env.pidfile_path.open('r+')
|
|
54
|
+
!pidfile.flock(File::LOCK_EX | File::LOCK_NB)
|
|
55
|
+
rescue Errno::ENOENT
|
|
56
|
+
false
|
|
57
|
+
ensure
|
|
58
|
+
if pidfile
|
|
59
|
+
pidfile.flock(File::LOCK_UN)
|
|
60
|
+
pidfile.close
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# timeout: Defaults to 2 seconds
|
|
65
|
+
def stop
|
|
66
|
+
if running?
|
|
67
|
+
timeout = Time.now + @env.graceful_termination_timeout
|
|
68
|
+
kill 'TERM'
|
|
69
|
+
sleep 0.1 until !running? || Time.now >= timeout
|
|
70
|
+
|
|
71
|
+
if running?
|
|
72
|
+
kill 'KILL'
|
|
73
|
+
:killed
|
|
74
|
+
else
|
|
75
|
+
:stopped
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
:not_running
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def kill(sig)
|
|
83
|
+
pid = self.pid
|
|
84
|
+
Process.kill(sig, pid) if pid
|
|
85
|
+
rescue Errno::ESRCH
|
|
86
|
+
# already dead
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def start_server
|
|
90
|
+
server = UNIXServer.open(env.socket_path)
|
|
91
|
+
log "started on #{env.socket_path}"
|
|
92
|
+
loop { serve server.accept }
|
|
93
|
+
rescue Interrupt
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def serve(client)
|
|
97
|
+
log "accepted client"
|
|
98
|
+
client.puts env.version
|
|
99
|
+
|
|
100
|
+
# Corresponds to Client::Invoke#connect_to_agent
|
|
101
|
+
app_client = client.recv_io
|
|
102
|
+
command = client.recv_object
|
|
103
|
+
|
|
104
|
+
args, agent = command.values_at('args', 'agent')
|
|
105
|
+
cmd = args.first
|
|
106
|
+
|
|
107
|
+
if agent == '__server__'
|
|
108
|
+
case cmd
|
|
109
|
+
when 'application_pids'
|
|
110
|
+
# Corresponds to Client::Invoke#run_command
|
|
111
|
+
client.puts
|
|
112
|
+
|
|
113
|
+
unix_socket = UNIXSocket.for_fd(app_client.fileno)
|
|
114
|
+
_stdout = unix_socket.recv_io
|
|
115
|
+
_stderr = unix_socket.recv_io
|
|
116
|
+
_stdin = unix_socket.recv_io
|
|
117
|
+
|
|
118
|
+
client.puts Process.pid
|
|
119
|
+
|
|
120
|
+
application_pids = []
|
|
121
|
+
env.applications.each do |k, v|
|
|
122
|
+
application_pids << v.pid if v.pid
|
|
123
|
+
end
|
|
124
|
+
unix_socket.send_object("return" => application_pids)
|
|
125
|
+
|
|
126
|
+
unix_socket.close
|
|
127
|
+
client.close
|
|
128
|
+
else
|
|
129
|
+
end
|
|
130
|
+
elsif Expedite::Actions.lookup(cmd)
|
|
131
|
+
# Corresponds to Client::Invoke#run_command
|
|
132
|
+
log "running command #{cmd}: #{args}"
|
|
133
|
+
|
|
134
|
+
client.puts
|
|
135
|
+
|
|
136
|
+
begin
|
|
137
|
+
target = env.applications[agent]
|
|
138
|
+
|
|
139
|
+
client.puts target.run(app_client)
|
|
140
|
+
rescue AgentNotFoundError => e
|
|
141
|
+
unix_socket = UNIXSocket.for_fd(app_client.fileno)
|
|
142
|
+
_stdout = unix_socket.recv_io
|
|
143
|
+
_stderr = unix_socket.recv_io
|
|
144
|
+
_stdin = unix_socket.recv_io
|
|
145
|
+
|
|
146
|
+
args, env = unix_socket.recv_object.values_at("args", "env")
|
|
147
|
+
|
|
148
|
+
client.puts Process.pid
|
|
149
|
+
|
|
150
|
+
# boot only
|
|
151
|
+
#@child_socket = client.recv_io
|
|
152
|
+
#@log_file = client.recv_io
|
|
153
|
+
unix_socket.send_object("exception" => e)
|
|
154
|
+
|
|
155
|
+
unix_socket.close
|
|
156
|
+
client.close
|
|
157
|
+
end
|
|
158
|
+
else
|
|
159
|
+
log "command not found #{cmd}"
|
|
160
|
+
client.close
|
|
161
|
+
end
|
|
162
|
+
rescue AgentNotFoundError => e
|
|
163
|
+
rescue SocketError => e
|
|
164
|
+
raise e unless client.eof?
|
|
165
|
+
ensure
|
|
166
|
+
redirect_output
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Boot the server into the process group of the current session.
|
|
170
|
+
# This will cause it to be automatically killed once the session
|
|
171
|
+
# ends (i.e. when the user closes their terminal).
|
|
172
|
+
def set_pgid
|
|
173
|
+
# Process.setpgid(0, SID.pgid)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Ignore SIGINT and SIGQUIT otherwise the user typing ^C or ^\ on the command line
|
|
177
|
+
# will kill the server/application.
|
|
178
|
+
def ignore_signals
|
|
179
|
+
IGNORE_SIGNALS.each { |sig| trap(sig, "IGNORE") }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def set_exit_hook
|
|
183
|
+
server_pid = Process.pid
|
|
184
|
+
|
|
185
|
+
# We don't want this hook to run in any forks of the current process
|
|
186
|
+
at_exit { shutdown if Process.pid == server_pid }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def shutdown
|
|
190
|
+
log "shutting down"
|
|
191
|
+
|
|
192
|
+
[env.socket_path, env.pidfile_path].each do |path|
|
|
193
|
+
if path.exist?
|
|
194
|
+
path.unlink rescue nil
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
env.applications.values.map { |a| Expedite.failsafe_thread { a.stop } }.map(&:join)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def write_pidfile
|
|
202
|
+
if @pidfile.flock(File::LOCK_EX | File::LOCK_NB)
|
|
203
|
+
@pidfile.truncate(0)
|
|
204
|
+
@pidfile.write("#{Process.pid}\n")
|
|
205
|
+
@pidfile.fsync
|
|
206
|
+
else
|
|
207
|
+
raise "Failed to lock #{@env.pidfile_path}"
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# We need to redirect STDOUT and STDERR, otherwise the server will
|
|
212
|
+
# keep the original FDs open which would break piping. (e.g.
|
|
213
|
+
# `spring rake -T | grep db` would hang forever because the server
|
|
214
|
+
# would keep the stdout FD open.)
|
|
215
|
+
def redirect_output
|
|
216
|
+
[STDOUT, STDERR].each { |stream| stream.reopen(env.log_file) }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def set_process_title
|
|
220
|
+
$0 = "expedite server | #{env.app_name}"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
private
|
|
224
|
+
|
|
225
|
+
def default_env
|
|
226
|
+
Env.new(log_file: default_log_file)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def default_log_file
|
|
230
|
+
if foreground? && !ENV["SPRING_LOG"]
|
|
231
|
+
$stderr
|
|
232
|
+
else
|
|
233
|
+
nil
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Server command
|
|
238
|
+
def application_pids
|
|
239
|
+
pids = []
|
|
240
|
+
env.applications.each do |k, v|
|
|
241
|
+
pids << v.pid if v.pid
|
|
242
|
+
end
|
|
243
|
+
return pids
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'expedite/actions'
|
|
2
|
+
require 'expedite/agents'
|
|
3
|
+
|
|
4
|
+
module Expedite
|
|
5
|
+
module Syntax
|
|
6
|
+
def define(&block)
|
|
7
|
+
DSL.run(&block)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class DSL
|
|
11
|
+
def action(name, &block)
|
|
12
|
+
Expedite::Actions.register(name, &block)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def agent(name, parent:nil, &block)
|
|
16
|
+
Expedite::Agents.register(name, parent: parent, &block)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.run(&block)
|
|
20
|
+
new.instance_eval(&block)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
extend Syntax
|
|
26
|
+
end
|
data/lib/expedite/version.rb
CHANGED
data/lib/expedite.rb
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
|
-
require
|
|
1
|
+
require 'expedite/client/exec'
|
|
2
|
+
require 'expedite/client/invoke'
|
|
3
|
+
require 'expedite/syntax'
|
|
2
4
|
|
|
3
5
|
module Expedite
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
class AgentProxy
|
|
7
|
+
attr_accessor :env, :agent
|
|
8
|
+
|
|
9
|
+
def initialize(env:, agent:)
|
|
10
|
+
self.env = env
|
|
11
|
+
self.agent = agent
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def exec(*args)
|
|
15
|
+
Client::Exec.new(env: env, agent: agent).call(*args)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def invoke(*args)
|
|
19
|
+
Client::Invoke.new(env: env, agent: agent).call(*args)
|
|
9
20
|
end
|
|
10
|
-
@clients[variant]
|
|
11
21
|
end
|
|
22
|
+
end
|
|
12
23
|
|
|
24
|
+
module Expedite
|
|
13
25
|
##
|
|
14
|
-
#
|
|
15
|
-
def self.
|
|
16
|
-
|
|
26
|
+
# Returns a client to dispatch actions to the specified agent
|
|
27
|
+
def self.agent(agent)
|
|
28
|
+
@clients ||= Hash.new do |h, k|
|
|
29
|
+
AgentProxy.new(env: Env.new, agent: agent)
|
|
30
|
+
end
|
|
31
|
+
@clients[agent]
|
|
17
32
|
end
|
|
18
33
|
end
|
|
19
|
-
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: expedite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bing-Chang Lai
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-12-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Manages Ruby processes that can be used to spawn child processes faster.
|
|
14
14
|
email: johnny.lai@me.com
|
|
@@ -20,24 +20,27 @@ files:
|
|
|
20
20
|
- README.md
|
|
21
21
|
- bin/expedite
|
|
22
22
|
- lib/expedite.rb
|
|
23
|
-
- lib/expedite/
|
|
24
|
-
- lib/expedite/
|
|
25
|
-
- lib/expedite/
|
|
23
|
+
- lib/expedite/action/block.rb
|
|
24
|
+
- lib/expedite/action/boot.rb
|
|
25
|
+
- lib/expedite/actions.rb
|
|
26
|
+
- lib/expedite/agents.rb
|
|
26
27
|
- lib/expedite/cli.rb
|
|
27
28
|
- lib/expedite/cli/server.rb
|
|
29
|
+
- lib/expedite/cli/status.rb
|
|
28
30
|
- lib/expedite/cli/stop.rb
|
|
29
|
-
- lib/expedite/client.rb
|
|
30
|
-
- lib/expedite/
|
|
31
|
-
- lib/expedite/command/boot.rb
|
|
32
|
-
- lib/expedite/command/info.rb
|
|
33
|
-
- lib/expedite/commands.rb
|
|
31
|
+
- lib/expedite/client/exec.rb
|
|
32
|
+
- lib/expedite/client/invoke.rb
|
|
34
33
|
- lib/expedite/env.rb
|
|
35
34
|
- lib/expedite/errors.rb
|
|
36
35
|
- lib/expedite/failsafe_thread.rb
|
|
37
|
-
- lib/expedite/
|
|
38
|
-
- lib/expedite/
|
|
36
|
+
- lib/expedite/helper/rails.rb
|
|
37
|
+
- lib/expedite/protocol.rb
|
|
38
|
+
- lib/expedite/server/agent.rb
|
|
39
|
+
- lib/expedite/server/agent_boot.rb
|
|
40
|
+
- lib/expedite/server/agent_manager.rb
|
|
41
|
+
- lib/expedite/server/controller.rb
|
|
39
42
|
- lib/expedite/signals.rb
|
|
40
|
-
- lib/expedite/
|
|
43
|
+
- lib/expedite/syntax.rb
|
|
41
44
|
- lib/expedite/version.rb
|
|
42
45
|
homepage: https://rubygems.org/gems/expedite
|
|
43
46
|
licenses:
|
|
@@ -58,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
58
61
|
- !ruby/object:Gem::Version
|
|
59
62
|
version: '0'
|
|
60
63
|
requirements: []
|
|
61
|
-
rubygems_version: 3.1.
|
|
64
|
+
rubygems_version: 3.1.6
|
|
62
65
|
signing_key:
|
|
63
66
|
specification_version: 4
|
|
64
67
|
summary: Expedite startup of Ruby process
|
data/lib/expedite/application.rb
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
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/signals'
|
|
10
|
-
require 'expedite/variants'
|
|
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 Signals
|
|
26
|
-
|
|
27
|
-
attr_reader :variant
|
|
28
|
-
attr_reader :manager, :env
|
|
29
|
-
|
|
30
|
-
def initialize(variant:, manager:, env:)
|
|
31
|
-
@variant = variant
|
|
32
|
-
@manager = manager
|
|
33
|
-
@env = env
|
|
34
|
-
@mutex = Mutex.new
|
|
35
|
-
@waiting = Set.new
|
|
36
|
-
@preloaded = false
|
|
37
|
-
@state = :initialized
|
|
38
|
-
@interrupt = IO.pipe
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def boot
|
|
42
|
-
# This is necessary for the terminal to work correctly when we reopen stdin.
|
|
43
|
-
Process.setsid rescue Errno::EPERM
|
|
44
|
-
|
|
45
|
-
Expedite.app = self
|
|
46
|
-
|
|
47
|
-
Signal.trap("TERM") { terminate }
|
|
48
|
-
|
|
49
|
-
env.load_helper
|
|
50
|
-
eager_preload if false #if ENV.delete("SPRING_PRELOAD") == "1"
|
|
51
|
-
run
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def state(val)
|
|
55
|
-
return if exiting?
|
|
56
|
-
log "#{@state} -> #{val}"
|
|
57
|
-
@state = val
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def state!(val)
|
|
61
|
-
state val
|
|
62
|
-
@interrupt.last.write "."
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def app_name
|
|
66
|
-
env.app_name
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def log(message)
|
|
70
|
-
env.log "[application:#{variant}] #{message}"
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def preloaded?
|
|
74
|
-
@preloaded
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def preload_failed?
|
|
78
|
-
@preloaded == :failure
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def exiting?
|
|
82
|
-
@state == :exiting
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def terminating?
|
|
86
|
-
@state == :terminating
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def initialized?
|
|
90
|
-
@state == :initialized
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def preload
|
|
94
|
-
log "preloading app"
|
|
95
|
-
|
|
96
|
-
@preloaded = :success
|
|
97
|
-
rescue Exception => e
|
|
98
|
-
@preloaded = :failure
|
|
99
|
-
raise e unless initialized?
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def eager_preload
|
|
103
|
-
with_pty { preload }
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def run
|
|
107
|
-
$0 = "expedite variant | #{app_name} | #{variant}"
|
|
108
|
-
|
|
109
|
-
Expedite::Variants.lookup(variant).after_fork(variant)
|
|
110
|
-
|
|
111
|
-
state :running
|
|
112
|
-
manager.puts
|
|
113
|
-
|
|
114
|
-
loop do
|
|
115
|
-
IO.select [manager, @interrupt.first]
|
|
116
|
-
|
|
117
|
-
if terminating? || preload_failed?
|
|
118
|
-
exit
|
|
119
|
-
else
|
|
120
|
-
serve manager.recv_io(UNIXSocket)
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def serve(client)
|
|
126
|
-
log "got client"
|
|
127
|
-
manager.puts
|
|
128
|
-
|
|
129
|
-
_stdout, stderr, _stdin = streams = 3.times.map { client.recv_io }
|
|
130
|
-
[STDOUT, STDERR, STDIN].zip(streams).each { |a, b| a.reopen(b) }
|
|
131
|
-
|
|
132
|
-
preload unless preloaded?
|
|
133
|
-
|
|
134
|
-
args, env = JSON.load(client.read(client.gets.to_i)).values_at("args", "env")
|
|
135
|
-
|
|
136
|
-
exec_name = args.shift
|
|
137
|
-
command = Expedite::Commands.lookup(exec_name)
|
|
138
|
-
command.setup(client)
|
|
139
|
-
|
|
140
|
-
connect_database
|
|
141
|
-
|
|
142
|
-
pid = fork {
|
|
143
|
-
Process.setsid
|
|
144
|
-
IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
|
|
145
|
-
trap("TERM", "DEFAULT")
|
|
146
|
-
|
|
147
|
-
ARGV.replace(args)
|
|
148
|
-
$0 = exec_name
|
|
149
|
-
|
|
150
|
-
# Load in the current env vars, except those which *were* changed when Spring started
|
|
151
|
-
env.each { |k, v| ENV[k] = v }
|
|
152
|
-
|
|
153
|
-
# requiring is faster, so if config.cache_classes was true in
|
|
154
|
-
# the environment's config file, then we can respect that from
|
|
155
|
-
# here on as we no longer need constant reloading.
|
|
156
|
-
if @original_cache_classes
|
|
157
|
-
ActiveSupport::Dependencies.mechanism = :require
|
|
158
|
-
Rails.application.config.cache_classes = true
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
connect_database
|
|
162
|
-
srand
|
|
163
|
-
|
|
164
|
-
invoke_after_fork_callbacks
|
|
165
|
-
shush_backtraces
|
|
166
|
-
|
|
167
|
-
command.call
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
disconnect_database
|
|
171
|
-
|
|
172
|
-
log "forked #{pid}"
|
|
173
|
-
manager.puts pid
|
|
174
|
-
|
|
175
|
-
# Boot makes a new application, so we don't wait for it
|
|
176
|
-
if command.is_a?(Expedite::Command::Boot)
|
|
177
|
-
Process.detach(pid)
|
|
178
|
-
else
|
|
179
|
-
wait pid, streams, client
|
|
180
|
-
end
|
|
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
|