einhorn 0.8.2 → 1.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 +4 -4
- data/Changes.md +15 -0
- data/README.md +7 -38
- data/einhorn.gemspec +23 -21
- data/example/pool_worker.rb +2 -2
- data/example/thin_example +8 -8
- data/example/time_server +5 -5
- data/lib/einhorn/client.rb +8 -8
- data/lib/einhorn/command/interface.rb +92 -98
- data/lib/einhorn/command.rb +75 -85
- data/lib/einhorn/compat.rb +7 -7
- data/lib/einhorn/event/abstract_text_descriptor.rb +32 -36
- 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 +20 -20
- data/lib/einhorn/prctl.rb +2 -2
- data/lib/einhorn/prctl_linux.rb +13 -14
- data/lib/einhorn/safe_yaml.rb +17 -0
- data/lib/einhorn/version.rb +1 -1
- data/lib/einhorn/worker.rb +26 -30
- data/lib/einhorn/worker_pool.rb +9 -9
- data/lib/einhorn.rb +120 -125
- metadata +37 -110
- 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 -94
- 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 -23
- data/test/integration/_lib/fixtures/exit_during_upgrade/upgrade_reexec.rb +0 -6
- data/test/integration/_lib/fixtures/pdeathsig_printer/pdeathsig_printer.rb +0 -29
- data/test/integration/_lib/fixtures/signal_timeout/sleepy_server.rb +0 -23
- data/test/integration/_lib/fixtures/upgrade_project/upgrading_server.rb +0 -24
- data/test/integration/_lib/helpers/einhorn_helpers.rb +0 -148
- data/test/integration/_lib/helpers.rb +0 -4
- data/test/integration/_lib.rb +0 -6
- data/test/integration/pdeathsig.rb +0 -26
- data/test/integration/startup.rb +0 -31
- data/test/integration/upgrading.rb +0 -204
- data/test/unit/_lib/bad_worker.rb +0 -7
- data/test/unit/_lib/sleep_worker.rb +0 -5
- data/test/unit/einhorn/client.rb +0 -88
- data/test/unit/einhorn/command/interface.rb +0 -49
- data/test/unit/einhorn/command.rb +0 -135
- data/test/unit/einhorn/event.rb +0 -89
- data/test/unit/einhorn/worker_pool.rb +0 -39
- data/test/unit/einhorn.rb +0 -96
- /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,9 +283,9 @@ EOF
|
|
289
283
|
nil
|
290
284
|
end
|
291
285
|
|
292
|
-
command
|
293
|
-
if pid = request[
|
294
|
-
Einhorn::Command.register_ping(pid, request[
|
286
|
+
command "worker:ping" do |conn, request|
|
287
|
+
if (pid = request["pid"])
|
288
|
+
Einhorn::Command.register_ping(pid, request["request_id"])
|
295
289
|
else
|
296
290
|
conn.log_error("Invalid request (no pid): #{request.inspect}")
|
297
291
|
end
|
@@ -301,25 +295,25 @@ EOF
|
|
301
295
|
end
|
302
296
|
|
303
297
|
# Used by einhornsh
|
304
|
-
command
|
305
|
-
|
306
|
-
Welcome, #{request[
|
307
|
-
This is Einhorn #{Einhorn::VERSION}.
|
308
|
-
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
|
309
303
|
end
|
310
304
|
|
311
|
-
command
|
312
|
-
"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:
|
313
307
|
|
314
308
|
#{command_descriptions}
|
315
309
|
"
|
316
310
|
end
|
317
311
|
|
318
|
-
command
|
319
|
-
YAML.dump({:
|
312
|
+
command "state", "Get a dump of Einhorn's current state" do
|
313
|
+
YAML.dump({state: Einhorn::State.dumpable_state})
|
320
314
|
end
|
321
315
|
|
322
|
-
command
|
316
|
+
command "reload", "Reload Einhorn" do |conn, request|
|
323
317
|
# TODO: make reload actually work (command socket reopening is
|
324
318
|
# an issue). Would also be nice if user got a confirmation that
|
325
319
|
# the reload completed, though that's not strictly necessary.
|
@@ -327,21 +321,21 @@ EOF
|
|
327
321
|
# In the normal case, this will do a write
|
328
322
|
# synchronously. Otherwise, the bytes will be stuck into the
|
329
323
|
# buffer and lost upon reload.
|
330
|
-
send_message(conn,
|
324
|
+
send_message(conn, "Reloading, as commanded", request["id"], true)
|
331
325
|
Einhorn::Command.reload
|
332
326
|
end
|
333
327
|
|
334
|
-
command
|
328
|
+
command "inc", "Increment the number of Einhorn child processes" do
|
335
329
|
Einhorn::Command.increment
|
336
330
|
end
|
337
331
|
|
338
|
-
command
|
332
|
+
command "dec", "Decrement the number of Einhorn child processes" do
|
339
333
|
Einhorn::Command.decrement
|
340
334
|
end
|
341
335
|
|
342
|
-
command
|
343
|
-
args = request[
|
344
|
-
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))
|
345
339
|
next message
|
346
340
|
end
|
347
341
|
|
@@ -354,45 +348,45 @@ EOF
|
|
354
348
|
Einhorn::Command.set_workers(count)
|
355
349
|
end
|
356
350
|
|
357
|
-
command
|
351
|
+
command "quieter", "Decrease verbosity" do
|
358
352
|
Einhorn::Command.quieter
|
359
353
|
end
|
360
354
|
|
361
|
-
command
|
355
|
+
command "louder", "Increase verbosity" do
|
362
356
|
Einhorn::Command.louder
|
363
357
|
end
|
364
358
|
|
365
|
-
command
|
359
|
+
command "upgrade", "Upgrade all Einhorn workers smoothly. This causes Einhorn to reload its own code as well." do |conn, request|
|
366
360
|
# send first message directly for old clients that don't support request
|
367
361
|
# ids or subscriptions. Everything else is sent tagged with request id
|
368
362
|
# for new clients.
|
369
|
-
send_message(conn,
|
370
|
-
conn.subscribe(:upgrade, request[
|
363
|
+
send_message(conn, "Upgrading smoothly, as commanded", request["id"])
|
364
|
+
conn.subscribe(:upgrade, request["id"])
|
371
365
|
# If the app is preloaded this doesn't return.
|
372
366
|
Einhorn::Command.full_upgrade_smooth
|
373
367
|
nil
|
374
368
|
end
|
375
369
|
|
376
|
-
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|
|
377
371
|
# send first message directly for old clients that don't support request
|
378
372
|
# ids or subscriptions. Everything else is sent tagged with request id
|
379
373
|
# for new clients.
|
380
|
-
send_message(conn,
|
381
|
-
conn.subscribe(:upgrade, request[
|
374
|
+
send_message(conn, "Upgrading fleet, as commanded", request["id"])
|
375
|
+
conn.subscribe(:upgrade, request["id"])
|
382
376
|
# If the app is preloaded this doesn't return.
|
383
377
|
Einhorn::Command.full_upgrade_fleet
|
384
378
|
nil
|
385
379
|
end
|
386
380
|
|
387
|
-
command
|
388
|
-
args = request[
|
389
|
-
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))
|
390
384
|
next message
|
391
385
|
end
|
392
386
|
|
393
387
|
args = normalize_signals(args)
|
394
388
|
|
395
|
-
if message = validate_signals(args)
|
389
|
+
if (message = validate_signals(args))
|
396
390
|
next message
|
397
391
|
end
|
398
392
|
|
@@ -403,16 +397,16 @@ EOF
|
|
403
397
|
results.join("\n")
|
404
398
|
end
|
405
399
|
|
406
|
-
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|
|
407
401
|
# TODO: dedup this code with signal
|
408
|
-
args = request[
|
409
|
-
if message = validate_args(args)
|
402
|
+
args = request["args"]
|
403
|
+
if (message = validate_args(args))
|
410
404
|
next message
|
411
405
|
end
|
412
406
|
|
413
407
|
args = normalize_signals(args)
|
414
408
|
|
415
|
-
if message = validate_signals(args)
|
409
|
+
if (message = validate_signals(args))
|
416
410
|
next message
|
417
411
|
end
|
418
412
|
|
@@ -424,24 +418,24 @@ EOF
|
|
424
418
|
"Einhorn is going down! #{response}"
|
425
419
|
end
|
426
420
|
|
427
|
-
command
|
428
|
-
args = request[
|
429
|
-
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))
|
430
424
|
next message
|
431
425
|
end
|
432
426
|
|
433
427
|
unless args.length > 0
|
434
|
-
next
|
428
|
+
next "Must pass in a YAML-encoded hash"
|
435
429
|
end
|
436
430
|
|
437
431
|
begin
|
438
432
|
# We do the joining so people don't need to worry about quoting
|
439
|
-
parsed =
|
440
|
-
rescue ArgumentError
|
441
|
-
next
|
433
|
+
parsed = SafeYAML.load(args.join(" "))
|
434
|
+
rescue ArgumentError
|
435
|
+
next "Could not parse argument. Must be a YAML-encoded hash"
|
442
436
|
end
|
443
437
|
|
444
|
-
unless parsed.
|
438
|
+
unless parsed.is_a?(Hash)
|
445
439
|
next "Parsed argument is a #{parsed.class}, not a hash"
|
446
440
|
end
|
447
441
|
|
@@ -451,11 +445,11 @@ EOF
|
|
451
445
|
end
|
452
446
|
|
453
447
|
def self.validate_args(args)
|
454
|
-
return
|
455
|
-
return
|
448
|
+
return "No args provided" unless args
|
449
|
+
return "Args must be an array" unless args.is_a?(Array)
|
456
450
|
|
457
451
|
args.each do |arg|
|
458
|
-
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)
|
459
453
|
end
|
460
454
|
|
461
455
|
nil
|