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
|
@@ -1,187 +0,0 @@
|
|
|
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, :name, :env, :status, :variant
|
|
13
|
-
|
|
14
|
-
def initialize(name, env)
|
|
15
|
-
@name = name
|
|
16
|
-
@env = env
|
|
17
|
-
@mutex = Mutex.new
|
|
18
|
-
@state = :running
|
|
19
|
-
@pid = nil
|
|
20
|
-
|
|
21
|
-
@variant = Expedite::Variants.lookup(@name)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def log(message)
|
|
25
|
-
env.log "[application_manager:#{name}] #{message}"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# We're not using @mutex.synchronize to avoid the weird "<internal:prelude>:10"
|
|
29
|
-
# line which messes with backtraces in e.g. rspec
|
|
30
|
-
def synchronize
|
|
31
|
-
@mutex.lock
|
|
32
|
-
yield
|
|
33
|
-
ensure
|
|
34
|
-
@mutex.unlock
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def start
|
|
38
|
-
start_child
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def restart
|
|
42
|
-
return if @state == :stopping
|
|
43
|
-
start_child(true)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def alive?
|
|
47
|
-
@pid
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def with_child
|
|
51
|
-
synchronize do
|
|
52
|
-
if alive?
|
|
53
|
-
begin
|
|
54
|
-
yield child
|
|
55
|
-
rescue Errno::ECONNRESET, Errno::EPIPE
|
|
56
|
-
# The child has died but has not been collected by the wait thread yet,
|
|
57
|
-
# so start a new child and try again.
|
|
58
|
-
log "child dead; starting"
|
|
59
|
-
start
|
|
60
|
-
yield child
|
|
61
|
-
end
|
|
62
|
-
else
|
|
63
|
-
log "child not running; starting"
|
|
64
|
-
start
|
|
65
|
-
yield child
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Returns the pid of the process running the command, or nil if the application process died.
|
|
71
|
-
def run(client)
|
|
72
|
-
@client = client
|
|
73
|
-
with_child do |child|
|
|
74
|
-
child.send_io client
|
|
75
|
-
child.gets or raise Errno::EPIPE
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
pid = child.gets.to_i
|
|
79
|
-
|
|
80
|
-
unless pid.zero?
|
|
81
|
-
log "got worker pid #{pid}"
|
|
82
|
-
pid
|
|
83
|
-
end
|
|
84
|
-
rescue Errno::ECONNRESET, Errno::EPIPE => e
|
|
85
|
-
log "#{e} while reading from child; returning no pid"
|
|
86
|
-
nil
|
|
87
|
-
ensure
|
|
88
|
-
client.close
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def stop
|
|
92
|
-
log "stopping"
|
|
93
|
-
@state = :stopping
|
|
94
|
-
|
|
95
|
-
if pid
|
|
96
|
-
Process.kill('TERM', pid)
|
|
97
|
-
Process.wait(pid)
|
|
98
|
-
end
|
|
99
|
-
rescue Errno::ESRCH, Errno::ECHILD
|
|
100
|
-
# Don't care
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def keep_alive
|
|
104
|
-
variant.keep_alive
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def parent
|
|
108
|
-
variant.parent
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
private
|
|
112
|
-
|
|
113
|
-
def start_child(preload = false)
|
|
114
|
-
if parent
|
|
115
|
-
fork_child(preload)
|
|
116
|
-
else
|
|
117
|
-
spawn_child(preload)
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def fork_child(preload = false)
|
|
122
|
-
@child, child_socket = UNIXSocket.pair
|
|
123
|
-
|
|
124
|
-
# Compose command
|
|
125
|
-
wr, rd = UNIXSocket.pair
|
|
126
|
-
wr.send_io STDOUT
|
|
127
|
-
wr.send_io STDERR
|
|
128
|
-
wr.send_io STDIN
|
|
129
|
-
|
|
130
|
-
send_json wr, 'args' => ['expedite/boot', name], 'env' => {}
|
|
131
|
-
wr.send_io child_socket
|
|
132
|
-
wr.send_io env.log_file
|
|
133
|
-
wr.close
|
|
134
|
-
|
|
135
|
-
@pid = env.applications[parent].run(rd)
|
|
136
|
-
|
|
137
|
-
start_wait_thread(pid, child) if child.gets
|
|
138
|
-
child_socket.close
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def spawn_child(preload = false)
|
|
142
|
-
@child, child_socket = UNIXSocket.pair
|
|
143
|
-
|
|
144
|
-
Bundler.with_original_env do
|
|
145
|
-
bundler_dir = File.expand_path("../..", $LOADED_FEATURES.grep(/bundler\/setup\.rb$/).first)
|
|
146
|
-
@pid = Process.spawn(
|
|
147
|
-
{
|
|
148
|
-
"EXPEDITE_VARIANT" => name,
|
|
149
|
-
"EXPEDITE_ROOT" => env.root,
|
|
150
|
-
},
|
|
151
|
-
"ruby",
|
|
152
|
-
*(bundler_dir != RbConfig::CONFIG["rubylibdir"] ? ["-I", bundler_dir] : []),
|
|
153
|
-
"-I", File.expand_path("../..", __FILE__),
|
|
154
|
-
"-e", "require 'expedite/application/boot'",
|
|
155
|
-
3 => child_socket,
|
|
156
|
-
4 => env.log_file,
|
|
157
|
-
)
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
start_wait_thread(pid, child) if child.gets
|
|
161
|
-
child_socket.close
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def start_wait_thread(pid, child)
|
|
165
|
-
Process.detach(pid)
|
|
166
|
-
|
|
167
|
-
Expedite.failsafe_thread do
|
|
168
|
-
# The recv can raise an ECONNRESET, killing the thread, but that's ok
|
|
169
|
-
# as if it does we're no longer interested in the child
|
|
170
|
-
loop do
|
|
171
|
-
IO.select([child])
|
|
172
|
-
break if child.recv(1, Socket::MSG_PEEK).empty?
|
|
173
|
-
sleep 0.01
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
log "child #{pid} shutdown"
|
|
177
|
-
|
|
178
|
-
synchronize {
|
|
179
|
-
if @pid == pid
|
|
180
|
-
@pid = nil
|
|
181
|
-
restart if keep_alive
|
|
182
|
-
end
|
|
183
|
-
}
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
end
|
data/lib/expedite/client.rb
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
# Based on https://github.com/rails/spring/blob/master/lib/spring/client/run.rb
|
|
3
|
-
|
|
4
|
-
require 'bundler'
|
|
5
|
-
require 'json'
|
|
6
|
-
require 'rbconfig'
|
|
7
|
-
require 'socket'
|
|
8
|
-
|
|
9
|
-
require 'expedite/env'
|
|
10
|
-
require 'expedite/errors'
|
|
11
|
-
require 'expedite/send_json'
|
|
12
|
-
|
|
13
|
-
module Expedite
|
|
14
|
-
class Client
|
|
15
|
-
include SendJson
|
|
16
|
-
|
|
17
|
-
FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO WINCH) & Signal.list.keys
|
|
18
|
-
CONNECT_TIMEOUT = 1
|
|
19
|
-
BOOT_TIMEOUT = 20
|
|
20
|
-
|
|
21
|
-
attr_reader :args, :env, :variant
|
|
22
|
-
attr_reader :server
|
|
23
|
-
|
|
24
|
-
def initialize(env: nil, variant: nil)
|
|
25
|
-
@env = env || Env.new
|
|
26
|
-
@variant = variant
|
|
27
|
-
|
|
28
|
-
@signal_queue = []
|
|
29
|
-
@server_booted = false
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def log(message)
|
|
33
|
-
env.log "[client] #{message}"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def connect
|
|
37
|
-
@server = UNIXSocket.open(env.socket_path)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def call(*args)
|
|
41
|
-
@args = args
|
|
42
|
-
begin
|
|
43
|
-
connect
|
|
44
|
-
rescue Errno::ENOENT, Errno::ECONNRESET, Errno::ECONNREFUSED
|
|
45
|
-
cold_run
|
|
46
|
-
else
|
|
47
|
-
warm_run
|
|
48
|
-
end
|
|
49
|
-
ensure
|
|
50
|
-
server.close if server
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def warm_run
|
|
54
|
-
run
|
|
55
|
-
rescue CommandNotFound
|
|
56
|
-
raise
|
|
57
|
-
require "expedite/command"
|
|
58
|
-
|
|
59
|
-
if Expedite.command(args.first)
|
|
60
|
-
# Command installed since Expedite started
|
|
61
|
-
stop_server
|
|
62
|
-
cold_run
|
|
63
|
-
else
|
|
64
|
-
raise
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def cold_run
|
|
69
|
-
boot_server
|
|
70
|
-
connect
|
|
71
|
-
run
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def run
|
|
75
|
-
verify_server_version
|
|
76
|
-
|
|
77
|
-
application, client = UNIXSocket.pair
|
|
78
|
-
|
|
79
|
-
queue_signals
|
|
80
|
-
connect_to_application(client)
|
|
81
|
-
run_command(client, application)
|
|
82
|
-
rescue Errno::ECONNRESET
|
|
83
|
-
exit 1
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def boot_server
|
|
87
|
-
env.socket_path.unlink if env.socket_path.exist?
|
|
88
|
-
|
|
89
|
-
pid = Process.spawn(gem_env, env.server_command, out: File::NULL)
|
|
90
|
-
timeout = Time.now + BOOT_TIMEOUT
|
|
91
|
-
|
|
92
|
-
@server_booted = true
|
|
93
|
-
|
|
94
|
-
until env.socket_path.exist?
|
|
95
|
-
_, status = Process.waitpid2(pid, Process::WNOHANG)
|
|
96
|
-
|
|
97
|
-
if status
|
|
98
|
-
# Server did not start
|
|
99
|
-
raise ArgumentError, "Server exited: #{status.exitstatus}"
|
|
100
|
-
elsif Time.now > timeout
|
|
101
|
-
$stderr.puts "Starting Expedite server with `#{env.server_command}` " \
|
|
102
|
-
"timed out after #{BOOT_TIMEOUT} seconds"
|
|
103
|
-
exit 1
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
sleep 0.1
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def server_booted?
|
|
111
|
-
@server_booted
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def gem_env
|
|
115
|
-
bundle = Bundler.bundle_path.to_s
|
|
116
|
-
paths = Gem.path + ENV["GEM_PATH"].to_s.split(File::PATH_SEPARATOR)
|
|
117
|
-
|
|
118
|
-
{
|
|
119
|
-
"GEM_PATH" => [bundle, *paths].uniq.join(File::PATH_SEPARATOR),
|
|
120
|
-
"GEM_HOME" => bundle
|
|
121
|
-
}
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def stop_server
|
|
125
|
-
server.close
|
|
126
|
-
@server = nil
|
|
127
|
-
env.stop
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def verify_server_version
|
|
131
|
-
server_version = server.gets.chomp
|
|
132
|
-
if server_version != env.version
|
|
133
|
-
$stderr.puts "There is a version mismatch between the Expedite client " \
|
|
134
|
-
"(#{env.version}) and the server (#{server_version})."
|
|
135
|
-
|
|
136
|
-
if server_booted?
|
|
137
|
-
$stderr.puts "We already tried to reboot the server, but the mismatch is still present."
|
|
138
|
-
exit 1
|
|
139
|
-
else
|
|
140
|
-
$stderr.puts "Restarting to resolve."
|
|
141
|
-
stop_server
|
|
142
|
-
cold_run
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def connect_to_application(client)
|
|
148
|
-
server.send_io client
|
|
149
|
-
|
|
150
|
-
send_json server, "args" => args, "variant" => variant
|
|
151
|
-
|
|
152
|
-
if IO.select([server], [], [], CONNECT_TIMEOUT)
|
|
153
|
-
server.gets or raise CommandNotFound
|
|
154
|
-
else
|
|
155
|
-
raise "Error connecting to Expedite server"
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def run_command(client, application)
|
|
160
|
-
log "sending command"
|
|
161
|
-
|
|
162
|
-
application.send_io STDOUT
|
|
163
|
-
application.send_io STDERR
|
|
164
|
-
application.send_io STDIN
|
|
165
|
-
|
|
166
|
-
send_json application, "args" => args, "env" => ENV.to_hash
|
|
167
|
-
|
|
168
|
-
pid = server.gets
|
|
169
|
-
pid = pid.chomp if pid
|
|
170
|
-
|
|
171
|
-
# We must not close the client socket until we are sure that the application has
|
|
172
|
-
# received the FD. Otherwise the FD can end up getting closed while it's in the server
|
|
173
|
-
# socket buffer on OS X. This doesn't happen on Linux.
|
|
174
|
-
client.close
|
|
175
|
-
|
|
176
|
-
if pid && !pid.empty?
|
|
177
|
-
log "got pid: #{pid}"
|
|
178
|
-
|
|
179
|
-
suspend_resume_on_tstp_cont(pid)
|
|
180
|
-
|
|
181
|
-
forward_signals(application)
|
|
182
|
-
status = application.read.to_i
|
|
183
|
-
|
|
184
|
-
log "got exit status #{status}"
|
|
185
|
-
|
|
186
|
-
exit status
|
|
187
|
-
else
|
|
188
|
-
log "got no pid"
|
|
189
|
-
exit 1
|
|
190
|
-
end
|
|
191
|
-
ensure
|
|
192
|
-
application.close
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def queue_signals
|
|
196
|
-
FORWARDED_SIGNALS.each do |sig|
|
|
197
|
-
trap(sig) { @signal_queue << sig }
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def suspend_resume_on_tstp_cont(pid)
|
|
202
|
-
trap("TSTP") {
|
|
203
|
-
log "suspended"
|
|
204
|
-
Process.kill("STOP", pid.to_i)
|
|
205
|
-
Process.kill("STOP", Process.pid)
|
|
206
|
-
}
|
|
207
|
-
trap("CONT") {
|
|
208
|
-
log "resumed"
|
|
209
|
-
Process.kill("CONT", pid.to_i)
|
|
210
|
-
}
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def forward_signals(application)
|
|
214
|
-
@signal_queue.each { |sig| kill sig, application }
|
|
215
|
-
|
|
216
|
-
FORWARDED_SIGNALS.each do |sig|
|
|
217
|
-
trap(sig) { forward_signal sig, application }
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def forward_signal(sig, application)
|
|
222
|
-
if kill(sig, application) != 0
|
|
223
|
-
# If the application process is gone, then don't block the
|
|
224
|
-
# signal on this process.
|
|
225
|
-
trap(sig, 'DEFAULT')
|
|
226
|
-
Process.kill(sig, Process.pid)
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
def kill(sig, application)
|
|
231
|
-
application.puts(sig)
|
|
232
|
-
application.gets.to_i
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
end
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
module Expedite
|
|
2
|
-
module Command
|
|
3
|
-
class Info
|
|
4
|
-
def call
|
|
5
|
-
puts "1"
|
|
6
|
-
client.puts
|
|
7
|
-
puts "2"
|
|
8
|
-
unix_socket = UNIXSocket.for_fd(app_client.fileno)
|
|
9
|
-
_stdout, stderr, _stdin = streams = 3.times.map do
|
|
10
|
-
puts "4"
|
|
11
|
-
unix_socket.recv_io
|
|
12
|
-
end
|
|
13
|
-
puts "5"
|
|
14
|
-
client.puts Process.pid
|
|
15
|
-
puts "6"
|
|
16
|
-
unix_socket.puts 11 #application_pids.to_json
|
|
17
|
-
puts "7"
|
|
18
|
-
unix_socket.puts 10
|
|
19
|
-
puts "8"
|
|
20
|
-
unix_socket.close
|
|
21
|
-
client.close
|
|
22
|
-
|
|
23
|
-
variant = ARGV[0]
|
|
24
|
-
|
|
25
|
-
require "expedite/application"
|
|
26
|
-
|
|
27
|
-
Expedite::Application.new(
|
|
28
|
-
variant: variant,
|
|
29
|
-
manager: UNIXSocket.for_fd(@child_socket.fileno),
|
|
30
|
-
env: Expedite::Env.new(
|
|
31
|
-
root: ENV['EXPEDITE_ROOT'],
|
|
32
|
-
log_file: @log_file,
|
|
33
|
-
),
|
|
34
|
-
).boot
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def setup(client)
|
|
38
|
-
@child_socket = client.recv_io
|
|
39
|
-
@log_file = client.recv_io
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def runs_in
|
|
43
|
-
:server
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
Expedite::Commands.register("expedite/info", Expedite::Command::Info)
|
data/lib/expedite/send_json.rb
DELETED
data/lib/expedite/server.rb
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
# Based on https://github.com/rails/spring/blob/master/lib/spring/server.rb
|
|
2
|
-
require 'json'
|
|
3
|
-
require 'socket'
|
|
4
|
-
require "expedite/application_manager"
|
|
5
|
-
require "expedite/env"
|
|
6
|
-
require "expedite/signals"
|
|
7
|
-
|
|
8
|
-
module Expedite
|
|
9
|
-
class Server
|
|
10
|
-
include Signals
|
|
11
|
-
|
|
12
|
-
def self.boot(options = {})
|
|
13
|
-
new(options).boot
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
attr_reader :env
|
|
17
|
-
|
|
18
|
-
def initialize(foreground: true, env: nil)
|
|
19
|
-
@foreground = foreground
|
|
20
|
-
@env = env || default_env
|
|
21
|
-
@pidfile = @env.pidfile_path.open('a')
|
|
22
|
-
@mutex = Mutex.new
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def foreground?
|
|
26
|
-
@foreground
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def log(message)
|
|
30
|
-
env.log "[server] #{message}"
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def boot
|
|
34
|
-
env.load_helper
|
|
35
|
-
|
|
36
|
-
write_pidfile
|
|
37
|
-
set_pgid unless foreground?
|
|
38
|
-
ignore_signals unless foreground?
|
|
39
|
-
set_exit_hook
|
|
40
|
-
set_process_title
|
|
41
|
-
start_server
|
|
42
|
-
exit 0
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def pid
|
|
46
|
-
@env.pidfile_path.read.to_i
|
|
47
|
-
rescue Errno::ENOENT
|
|
48
|
-
nil
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def running?
|
|
52
|
-
pidfile = @env.pidfile_path.open('r+')
|
|
53
|
-
!pidfile.flock(File::LOCK_EX | File::LOCK_NB)
|
|
54
|
-
rescue Errno::ENOENT
|
|
55
|
-
false
|
|
56
|
-
ensure
|
|
57
|
-
if pidfile
|
|
58
|
-
pidfile.flock(File::LOCK_UN)
|
|
59
|
-
pidfile.close
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# timeout: Defaults to 2 seconds
|
|
64
|
-
def stop
|
|
65
|
-
if running?
|
|
66
|
-
timeout = Time.now + @env.graceful_termination_timeout
|
|
67
|
-
kill 'TERM'
|
|
68
|
-
sleep 0.1 until !running? || Time.now >= timeout
|
|
69
|
-
|
|
70
|
-
if running?
|
|
71
|
-
kill 'KILL'
|
|
72
|
-
:killed
|
|
73
|
-
else
|
|
74
|
-
:stopped
|
|
75
|
-
end
|
|
76
|
-
else
|
|
77
|
-
:not_running
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def kill(sig)
|
|
82
|
-
pid = self.pid
|
|
83
|
-
Process.kill(sig, pid) if pid
|
|
84
|
-
rescue Errno::ESRCH
|
|
85
|
-
# already dead
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def start_server
|
|
89
|
-
server = UNIXServer.open(env.socket_path)
|
|
90
|
-
log "started on #{env.socket_path}"
|
|
91
|
-
loop { serve server.accept }
|
|
92
|
-
rescue Interrupt
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def serve(client)
|
|
96
|
-
log "accepted client"
|
|
97
|
-
client.puts env.version
|
|
98
|
-
|
|
99
|
-
app_client = client.recv_io
|
|
100
|
-
command = JSON.load(client.read(client.gets.to_i))
|
|
101
|
-
|
|
102
|
-
args, variant = command.values_at('args', 'variant')
|
|
103
|
-
cmd = args.first
|
|
104
|
-
if true #Expedite.command(cmd)
|
|
105
|
-
log "running command #{cmd}"
|
|
106
|
-
client.puts
|
|
107
|
-
|
|
108
|
-
target = env.applications[variant]
|
|
109
|
-
client.puts target.run(app_client)
|
|
110
|
-
else
|
|
111
|
-
log "command not found #{cmd}"
|
|
112
|
-
client.close
|
|
113
|
-
end
|
|
114
|
-
rescue SocketError => e
|
|
115
|
-
raise e unless client.eof?
|
|
116
|
-
ensure
|
|
117
|
-
redirect_output
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Boot the server into the process group of the current session.
|
|
121
|
-
# This will cause it to be automatically killed once the session
|
|
122
|
-
# ends (i.e. when the user closes their terminal).
|
|
123
|
-
def set_pgid
|
|
124
|
-
# Process.setpgid(0, SID.pgid)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Ignore SIGINT and SIGQUIT otherwise the user typing ^C or ^\ on the command line
|
|
128
|
-
# will kill the server/application.
|
|
129
|
-
def ignore_signals
|
|
130
|
-
IGNORE_SIGNALS.each { |sig| trap(sig, "IGNORE") }
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def set_exit_hook
|
|
134
|
-
server_pid = Process.pid
|
|
135
|
-
|
|
136
|
-
# We don't want this hook to run in any forks of the current process
|
|
137
|
-
at_exit { shutdown if Process.pid == server_pid }
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def shutdown
|
|
141
|
-
log "shutting down"
|
|
142
|
-
|
|
143
|
-
[env.socket_path, env.pidfile_path].each do |path|
|
|
144
|
-
if path.exist?
|
|
145
|
-
path.unlink rescue nil
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
env.applications.values.map { |a| Expedite.failsafe_thread { a.stop } }.map(&:join)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def write_pidfile
|
|
153
|
-
if @pidfile.flock(File::LOCK_EX | File::LOCK_NB)
|
|
154
|
-
@pidfile.truncate(0)
|
|
155
|
-
@pidfile.write("#{Process.pid}\n")
|
|
156
|
-
@pidfile.fsync
|
|
157
|
-
else
|
|
158
|
-
raise "Failed to lock #{@env.pidfile_path}"
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# We need to redirect STDOUT and STDERR, otherwise the server will
|
|
163
|
-
# keep the original FDs open which would break piping. (e.g.
|
|
164
|
-
# `spring rake -T | grep db` would hang forever because the server
|
|
165
|
-
# would keep the stdout FD open.)
|
|
166
|
-
def redirect_output
|
|
167
|
-
[STDOUT, STDERR].each { |stream| stream.reopen(env.log_file) }
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def set_process_title
|
|
171
|
-
$0 = "expedite server | #{env.app_name}"
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
private
|
|
175
|
-
|
|
176
|
-
def default_env
|
|
177
|
-
Env.new(log_file: default_log_file)
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def default_log_file
|
|
181
|
-
if foreground? && !ENV["SPRING_LOG"]
|
|
182
|
-
$stdout
|
|
183
|
-
else
|
|
184
|
-
nil
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
end
|