einhorn 0.7.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Changes.md +10 -0
- data/README.md +36 -30
- data/bin/einhorn +17 -2
- data/einhorn.gemspec +23 -21
- data/example/pool_worker.rb +1 -1
- data/example/thin_example +8 -8
- data/example/time_server +5 -5
- data/lib/einhorn/client.rb +8 -9
- data/lib/einhorn/command/interface.rb +100 -95
- data/lib/einhorn/command.rb +167 -88
- data/lib/einhorn/compat.rb +7 -7
- data/lib/einhorn/event/abstract_text_descriptor.rb +31 -35
- data/lib/einhorn/event/ack_timer.rb +2 -2
- data/lib/einhorn/event/command_server.rb +7 -9
- data/lib/einhorn/event/connection.rb +1 -3
- data/lib/einhorn/event/loop_breaker.rb +2 -1
- data/lib/einhorn/event/persistent.rb +2 -2
- data/lib/einhorn/event/timer.rb +4 -4
- data/lib/einhorn/event.rb +29 -20
- data/lib/einhorn/prctl.rb +26 -0
- data/lib/einhorn/prctl_linux.rb +48 -0
- data/lib/einhorn/safe_yaml.rb +17 -0
- data/lib/einhorn/version.rb +1 -1
- data/lib/einhorn/worker.rb +67 -49
- data/lib/einhorn/worker_pool.rb +9 -9
- data/lib/einhorn.rb +155 -126
- metadata +42 -137
- data/.gitignore +0 -17
- data/.travis.yml +0 -10
- data/CONTRIBUTORS +0 -6
- data/Gemfile +0 -11
- data/History.txt +0 -4
- data/README.md.in +0 -76
- data/Rakefile +0 -27
- data/test/_lib.rb +0 -12
- data/test/integration/_lib/fixtures/env_printer/env_printer.rb +0 -26
- data/test/integration/_lib/fixtures/exit_during_upgrade/exiting_server.rb +0 -22
- data/test/integration/_lib/fixtures/exit_during_upgrade/upgrade_reexec.rb +0 -6
- data/test/integration/_lib/fixtures/upgrade_project/upgrading_server.rb +0 -22
- data/test/integration/_lib/helpers/einhorn_helpers.rb +0 -143
- data/test/integration/_lib/helpers.rb +0 -4
- data/test/integration/_lib.rb +0 -6
- data/test/integration/startup.rb +0 -31
- data/test/integration/upgrading.rb +0 -157
- data/test/unit/einhorn/client.rb +0 -88
- data/test/unit/einhorn/command/interface.rb +0 -49
- data/test/unit/einhorn/command.rb +0 -21
- data/test/unit/einhorn/event.rb +0 -89
- data/test/unit/einhorn/worker_pool.rb +0 -39
- data/test/unit/einhorn.rb +0 -58
- /data/{LICENSE → LICENSE.txt} +0 -0
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "tmpdir"
|
2
|
+
require "socket"
|
3
3
|
|
4
4
|
module Einhorn::Command
|
5
5
|
module Interface
|
@@ -23,10 +23,8 @@ module Einhorn::Command
|
|
23
23
|
# Don't nuke socket_path if we never successfully acquired it
|
24
24
|
to_remove << socket_path if @@command_server
|
25
25
|
to_remove.each do |file|
|
26
|
-
|
27
|
-
|
28
|
-
rescue Errno::ENOENT
|
29
|
-
end
|
26
|
+
File.unlink(file)
|
27
|
+
rescue Errno::ENOENT
|
30
28
|
end
|
31
29
|
end
|
32
30
|
end
|
@@ -57,8 +55,8 @@ module Einhorn::Command
|
|
57
55
|
# this lockfile lying around forever.
|
58
56
|
def self.with_file_lock(&blk)
|
59
57
|
path = lockfile
|
60
|
-
File.open(path,
|
61
|
-
unless f.flock(File::LOCK_EX|File::LOCK_NB)
|
58
|
+
File.open(path, "w", 0o600) do |f|
|
59
|
+
unless f.flock(File::LOCK_EX | File::LOCK_NB)
|
62
60
|
raise "File lock already acquired by another Einhorn process. This likely indicates you tried to run Einhorn masters with the same cmd_name at the same time. This is a pretty rare race condition."
|
63
61
|
end
|
64
62
|
|
@@ -96,7 +94,7 @@ module Einhorn::Command
|
|
96
94
|
def self.write_pidfile
|
97
95
|
file = pidfile
|
98
96
|
Einhorn.log_info("Writing PID to #{file}")
|
99
|
-
File.
|
97
|
+
File.write(file, $$)
|
100
98
|
end
|
101
99
|
|
102
100
|
def self.uninit
|
@@ -107,12 +105,12 @@ module Einhorn::Command
|
|
107
105
|
Einhorn::State.socket_path || default_socket_path
|
108
106
|
end
|
109
107
|
|
110
|
-
def self.default_socket_path(cmd_name=nil)
|
108
|
+
def self.default_socket_path(cmd_name = nil)
|
111
109
|
cmd_name ||= Einhorn::State.cmd_name
|
112
|
-
if cmd_name
|
113
|
-
|
110
|
+
filename = if cmd_name
|
111
|
+
"einhorn-#{cmd_name}.sock"
|
114
112
|
else
|
115
|
-
|
113
|
+
"einhorn.sock"
|
116
114
|
end
|
117
115
|
File.join(Dir.tmpdir, filename)
|
118
116
|
end
|
@@ -121,12 +119,12 @@ module Einhorn::Command
|
|
121
119
|
Einhorn::State.lockfile || default_lockfile_path
|
122
120
|
end
|
123
121
|
|
124
|
-
def self.default_lockfile_path(cmd_name=nil)
|
122
|
+
def self.default_lockfile_path(cmd_name = nil)
|
125
123
|
cmd_name ||= Einhorn::State.cmd_name
|
126
|
-
if cmd_name
|
127
|
-
|
124
|
+
filename = if cmd_name
|
125
|
+
"einhorn-#{cmd_name}.lock"
|
128
126
|
else
|
129
|
-
|
127
|
+
"einhorn.lock"
|
130
128
|
end
|
131
129
|
File.join(Dir.tmpdir, filename)
|
132
130
|
end
|
@@ -135,12 +133,12 @@ module Einhorn::Command
|
|
135
133
|
Einhorn::State.pidfile || default_pidfile
|
136
134
|
end
|
137
135
|
|
138
|
-
def self.default_pidfile(cmd_name=nil)
|
136
|
+
def self.default_pidfile(cmd_name = nil)
|
139
137
|
cmd_name ||= Einhorn::State.cmd_name
|
140
|
-
if cmd_name
|
141
|
-
|
138
|
+
filename = if cmd_name
|
139
|
+
"einhorn-#{cmd_name}.pid"
|
142
140
|
else
|
143
|
-
|
141
|
+
"einhorn.pid"
|
144
142
|
end
|
145
143
|
File.join(Dir.tmpdir, filename)
|
146
144
|
end
|
@@ -162,11 +160,7 @@ module Einhorn::Command
|
|
162
160
|
Einhorn::Command.stop_respawning
|
163
161
|
exit(1)
|
164
162
|
end
|
165
|
-
trap_async("HUP") {Einhorn::Command.full_upgrade_smooth}
|
166
|
-
trap_async("ALRM") do
|
167
|
-
Einhorn.log_error("Upgrading using SIGALRM is deprecated. Please switch to SIGHUP")
|
168
|
-
Einhorn::Command.full_upgrade_smooth
|
169
|
-
end
|
163
|
+
trap_async("HUP") { Einhorn::Command.full_upgrade_smooth }
|
170
164
|
trap_async("CHLD") {}
|
171
165
|
trap_async("USR2") do
|
172
166
|
Einhorn::Command.signal_all("USR2")
|
@@ -191,14 +185,14 @@ module Einhorn::Command
|
|
191
185
|
end
|
192
186
|
|
193
187
|
def self.remove_handlers
|
194
|
-
%w
|
188
|
+
%w[INT TERM QUIT HUP ALRM CHLD USR2].each do |signal|
|
195
189
|
Signal.trap(signal, "DEFAULT")
|
196
190
|
end
|
197
191
|
end
|
198
192
|
|
199
193
|
## Commands
|
200
|
-
def self.command(name, description=nil, &code)
|
201
|
-
@@commands[name] = {:
|
194
|
+
def self.command(name, description = nil, &code)
|
195
|
+
@@commands[name] = {description: description, code: code}
|
202
196
|
end
|
203
197
|
|
204
198
|
def self.process_command(conn, command)
|
@@ -206,63 +200,63 @@ module Einhorn::Command
|
|
206
200
|
request = Einhorn::Client::Transport.deserialize_message(command)
|
207
201
|
rescue Einhorn::Client::Transport::ParseError
|
208
202
|
end
|
209
|
-
unless request.
|
203
|
+
unless request.is_a?(Hash)
|
210
204
|
send_message(conn, "Could not parse command")
|
211
205
|
return
|
212
206
|
end
|
213
207
|
|
214
208
|
message = generate_message(conn, request)
|
215
209
|
if !message.nil?
|
216
|
-
send_message(conn, message, request[
|
210
|
+
send_message(conn, message, request["id"], true)
|
217
211
|
else
|
218
212
|
conn.log_debug("Got back nil response, so not responding to command.")
|
219
213
|
end
|
220
214
|
end
|
221
215
|
|
222
|
-
def self.send_tagged_message(tag, message, last=false)
|
216
|
+
def self.send_tagged_message(tag, message, last = false)
|
223
217
|
Einhorn::Event.connections.each do |conn|
|
224
|
-
if id = conn.subscription(tag)
|
225
|
-
|
218
|
+
if (id = conn.subscription(tag))
|
219
|
+
send_message(conn, message, id, last)
|
226
220
|
conn.unsubscribe(tag) if last
|
227
221
|
end
|
228
222
|
end
|
229
223
|
end
|
230
224
|
|
231
|
-
def self.send_message(conn, message, request_id=nil, last=false)
|
225
|
+
def self.send_message(conn, message, request_id = nil, last = false)
|
232
226
|
if request_id
|
233
|
-
response = {
|
234
|
-
response[
|
227
|
+
response = {"message" => message, "request_id" => request_id}
|
228
|
+
response["wait"] = true unless last
|
235
229
|
else
|
236
230
|
# support old-style protocol
|
237
|
-
response = {
|
231
|
+
response = {"message" => message}
|
238
232
|
end
|
239
233
|
Einhorn::Client::Transport.send_message(conn, response)
|
240
234
|
end
|
241
235
|
|
242
236
|
def self.generate_message(conn, request)
|
243
|
-
unless command_name = request[
|
237
|
+
unless (command_name = request["command"])
|
244
238
|
return 'No "command" parameter provided; not sure what you want me to do.'
|
245
239
|
end
|
246
240
|
|
247
|
-
if command_spec = @@commands[command_name]
|
241
|
+
if (command_spec = @@commands[command_name])
|
248
242
|
conn.log_debug("Received command: #{request.inspect}")
|
249
243
|
begin
|
250
|
-
|
251
|
-
rescue
|
244
|
+
command_spec[:code].call(conn, request)
|
245
|
+
rescue => e
|
252
246
|
msg = "Error while processing command #{command_name.inspect}: #{e} (#{e.class})\n #{e.backtrace.join("\n ")}"
|
253
247
|
conn.log_error(msg)
|
254
|
-
|
248
|
+
msg
|
255
249
|
end
|
256
250
|
else
|
257
251
|
conn.log_debug("Received unrecognized command: #{request.inspect}")
|
258
|
-
|
252
|
+
unrecognized_command(conn, request)
|
259
253
|
end
|
260
254
|
end
|
261
255
|
|
262
256
|
def self.command_descriptions
|
263
257
|
command_specs = @@commands.select do |_, spec|
|
264
258
|
spec[:description]
|
265
|
-
end.sort_by {|name, _| name}
|
259
|
+
end.sort_by { |name, _| name }
|
266
260
|
|
267
261
|
command_specs.map do |name, spec|
|
268
262
|
"#{name}: #{spec[:description]}"
|
@@ -270,16 +264,16 @@ module Einhorn::Command
|
|
270
264
|
end
|
271
265
|
|
272
266
|
def self.unrecognized_command(conn, request)
|
273
|
-
|
274
|
-
Unrecognized command: #{request[
|
275
|
-
|
276
|
-
#{command_descriptions}
|
277
|
-
EOF
|
267
|
+
<<~EOF
|
268
|
+
Unrecognized command: #{request["command"].inspect}
|
269
|
+
|
270
|
+
#{command_descriptions}
|
271
|
+
EOF
|
278
272
|
end
|
279
273
|
|
280
274
|
# Used by workers
|
281
|
-
command
|
282
|
-
if pid = request[
|
275
|
+
command "worker:ack" do |conn, request|
|
276
|
+
if (pid = request["pid"])
|
283
277
|
Einhorn::Command.register_manual_ack(pid)
|
284
278
|
else
|
285
279
|
conn.log_error("Invalid request (no pid): #{request.inspect}")
|
@@ -289,26 +283,37 @@ EOF
|
|
289
283
|
nil
|
290
284
|
end
|
291
285
|
|
286
|
+
command "worker:ping" do |conn, request|
|
287
|
+
if (pid = request["pid"])
|
288
|
+
Einhorn::Command.register_ping(pid, request["request_id"])
|
289
|
+
else
|
290
|
+
conn.log_error("Invalid request (no pid): #{request.inspect}")
|
291
|
+
end
|
292
|
+
# Throw away this connection in case the application forgets to
|
293
|
+
conn.close
|
294
|
+
nil
|
295
|
+
end
|
296
|
+
|
292
297
|
# Used by einhornsh
|
293
|
-
command
|
294
|
-
|
295
|
-
Welcome, #{request[
|
296
|
-
This is Einhorn #{Einhorn::VERSION}.
|
297
|
-
EOF
|
298
|
+
command "ehlo" do |conn, request|
|
299
|
+
<<~EOF
|
300
|
+
Welcome, #{request["user"]}! You are speaking to Einhorn Master Process #{$$}#{Einhorn::State.cmd_name ? " (#{Einhorn::State.cmd_name})" : ""}.
|
301
|
+
This is Einhorn #{Einhorn::VERSION}.
|
302
|
+
EOF
|
298
303
|
end
|
299
304
|
|
300
|
-
command
|
301
|
-
"You are speaking to the Einhorn command socket. You can run the following commands:
|
305
|
+
command "help", "Print out available commands" do
|
306
|
+
"You are speaking to the Einhorn command socket. You can run the following commands:
|
302
307
|
|
303
308
|
#{command_descriptions}
|
304
309
|
"
|
305
310
|
end
|
306
311
|
|
307
|
-
command
|
308
|
-
YAML.dump({:
|
312
|
+
command "state", "Get a dump of Einhorn's current state" do
|
313
|
+
YAML.dump({state: Einhorn::State.dumpable_state})
|
309
314
|
end
|
310
315
|
|
311
|
-
command
|
316
|
+
command "reload", "Reload Einhorn" do |conn, request|
|
312
317
|
# TODO: make reload actually work (command socket reopening is
|
313
318
|
# an issue). Would also be nice if user got a confirmation that
|
314
319
|
# the reload completed, though that's not strictly necessary.
|
@@ -316,21 +321,21 @@ EOF
|
|
316
321
|
# In the normal case, this will do a write
|
317
322
|
# synchronously. Otherwise, the bytes will be stuck into the
|
318
323
|
# buffer and lost upon reload.
|
319
|
-
send_message(conn,
|
324
|
+
send_message(conn, "Reloading, as commanded", request["id"], true)
|
320
325
|
Einhorn::Command.reload
|
321
326
|
end
|
322
327
|
|
323
|
-
command
|
328
|
+
command "inc", "Increment the number of Einhorn child processes" do
|
324
329
|
Einhorn::Command.increment
|
325
330
|
end
|
326
331
|
|
327
|
-
command
|
332
|
+
command "dec", "Decrement the number of Einhorn child processes" do
|
328
333
|
Einhorn::Command.decrement
|
329
334
|
end
|
330
335
|
|
331
|
-
command
|
332
|
-
args = request[
|
333
|
-
if message = validate_args(args)
|
336
|
+
command "set_workers", "Set the number of Einhorn child processes" do |conn, request|
|
337
|
+
args = request["args"]
|
338
|
+
if (message = validate_args(args))
|
334
339
|
next message
|
335
340
|
end
|
336
341
|
|
@@ -343,45 +348,45 @@ EOF
|
|
343
348
|
Einhorn::Command.set_workers(count)
|
344
349
|
end
|
345
350
|
|
346
|
-
command
|
351
|
+
command "quieter", "Decrease verbosity" do
|
347
352
|
Einhorn::Command.quieter
|
348
353
|
end
|
349
354
|
|
350
|
-
command
|
355
|
+
command "louder", "Increase verbosity" do
|
351
356
|
Einhorn::Command.louder
|
352
357
|
end
|
353
358
|
|
354
|
-
command
|
359
|
+
command "upgrade", "Upgrade all Einhorn workers smoothly. This causes Einhorn to reload its own code as well." do |conn, request|
|
355
360
|
# send first message directly for old clients that don't support request
|
356
361
|
# ids or subscriptions. Everything else is sent tagged with request id
|
357
362
|
# for new clients.
|
358
|
-
send_message(conn,
|
359
|
-
conn.subscribe(:upgrade, request[
|
363
|
+
send_message(conn, "Upgrading smoothly, as commanded", request["id"])
|
364
|
+
conn.subscribe(:upgrade, request["id"])
|
360
365
|
# If the app is preloaded this doesn't return.
|
361
366
|
Einhorn::Command.full_upgrade_smooth
|
362
367
|
nil
|
363
368
|
end
|
364
369
|
|
365
|
-
command
|
370
|
+
command "upgrade_fleet", "Upgrade all Einhorn workers a fleet at a time. This causes Einhorn to reload its own code as well." do |conn, request|
|
366
371
|
# send first message directly for old clients that don't support request
|
367
372
|
# ids or subscriptions. Everything else is sent tagged with request id
|
368
373
|
# for new clients.
|
369
|
-
send_message(conn,
|
370
|
-
conn.subscribe(:upgrade, request[
|
374
|
+
send_message(conn, "Upgrading fleet, as commanded", request["id"])
|
375
|
+
conn.subscribe(:upgrade, request["id"])
|
371
376
|
# If the app is preloaded this doesn't return.
|
372
377
|
Einhorn::Command.full_upgrade_fleet
|
373
378
|
nil
|
374
379
|
end
|
375
380
|
|
376
|
-
command
|
377
|
-
args = request[
|
378
|
-
if message = validate_args(args)
|
381
|
+
command "signal", "Send one or more signals to all workers (args: SIG1 [SIG2 ...])" do |conn, request|
|
382
|
+
args = request["args"]
|
383
|
+
if (message = validate_args(args))
|
379
384
|
next message
|
380
385
|
end
|
381
386
|
|
382
387
|
args = normalize_signals(args)
|
383
388
|
|
384
|
-
if message = validate_signals(args)
|
389
|
+
if (message = validate_signals(args))
|
385
390
|
next message
|
386
391
|
end
|
387
392
|
|
@@ -392,16 +397,16 @@ EOF
|
|
392
397
|
results.join("\n")
|
393
398
|
end
|
394
399
|
|
395
|
-
command
|
400
|
+
command "die", "Send SIGNAL (default: SIGUSR2) to all workers, stop spawning new ones, and exit once all workers die (args: [SIGNAL])" do |conn, request|
|
396
401
|
# TODO: dedup this code with signal
|
397
|
-
args = request[
|
398
|
-
if message = validate_args(args)
|
402
|
+
args = request["args"]
|
403
|
+
if (message = validate_args(args))
|
399
404
|
next message
|
400
405
|
end
|
401
406
|
|
402
407
|
args = normalize_signals(args)
|
403
408
|
|
404
|
-
if message = validate_signals(args)
|
409
|
+
if (message = validate_signals(args))
|
405
410
|
next message
|
406
411
|
end
|
407
412
|
|
@@ -413,24 +418,24 @@ EOF
|
|
413
418
|
"Einhorn is going down! #{response}"
|
414
419
|
end
|
415
420
|
|
416
|
-
command
|
417
|
-
args = request[
|
418
|
-
if message = validate_args(args)
|
421
|
+
command "config", "Merge in a new set of config options. (Note: this will likely be subsumed by config file reloading at some point.)" do |conn, request|
|
422
|
+
args = request["args"]
|
423
|
+
if (message = validate_args(args))
|
419
424
|
next message
|
420
425
|
end
|
421
426
|
|
422
427
|
unless args.length > 0
|
423
|
-
next
|
428
|
+
next "Must pass in a YAML-encoded hash"
|
424
429
|
end
|
425
430
|
|
426
431
|
begin
|
427
432
|
# We do the joining so people don't need to worry about quoting
|
428
|
-
parsed =
|
429
|
-
rescue ArgumentError
|
430
|
-
next
|
433
|
+
parsed = SafeYAML.load(args.join(" "))
|
434
|
+
rescue ArgumentError
|
435
|
+
next "Could not parse argument. Must be a YAML-encoded hash"
|
431
436
|
end
|
432
437
|
|
433
|
-
unless parsed.
|
438
|
+
unless parsed.is_a?(Hash)
|
434
439
|
next "Parsed argument is a #{parsed.class}, not a hash"
|
435
440
|
end
|
436
441
|
|
@@ -440,11 +445,11 @@ EOF
|
|
440
445
|
end
|
441
446
|
|
442
447
|
def self.validate_args(args)
|
443
|
-
return
|
444
|
-
return
|
448
|
+
return "No args provided" unless args
|
449
|
+
return "Args must be an array" unless args.is_a?(Array)
|
445
450
|
|
446
451
|
args.each do |arg|
|
447
|
-
return "Argument is a #{arg.class}, not a string: #{arg.inspect}" unless arg.
|
452
|
+
return "Argument is a #{arg.class}, not a string: #{arg.inspect}" unless arg.is_a?(String)
|
448
453
|
end
|
449
454
|
|
450
455
|
nil
|