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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +15 -0
  3. data/README.md +7 -38
  4. data/einhorn.gemspec +23 -21
  5. data/example/pool_worker.rb +2 -2
  6. data/example/thin_example +8 -8
  7. data/example/time_server +5 -5
  8. data/lib/einhorn/client.rb +8 -8
  9. data/lib/einhorn/command/interface.rb +92 -98
  10. data/lib/einhorn/command.rb +75 -85
  11. data/lib/einhorn/compat.rb +7 -7
  12. data/lib/einhorn/event/abstract_text_descriptor.rb +32 -36
  13. data/lib/einhorn/event/ack_timer.rb +2 -2
  14. data/lib/einhorn/event/command_server.rb +7 -9
  15. data/lib/einhorn/event/connection.rb +1 -3
  16. data/lib/einhorn/event/loop_breaker.rb +2 -1
  17. data/lib/einhorn/event/persistent.rb +2 -2
  18. data/lib/einhorn/event/timer.rb +4 -4
  19. data/lib/einhorn/event.rb +20 -20
  20. data/lib/einhorn/prctl.rb +2 -2
  21. data/lib/einhorn/prctl_linux.rb +13 -14
  22. data/lib/einhorn/safe_yaml.rb +17 -0
  23. data/lib/einhorn/version.rb +1 -1
  24. data/lib/einhorn/worker.rb +26 -30
  25. data/lib/einhorn/worker_pool.rb +9 -9
  26. data/lib/einhorn.rb +120 -125
  27. metadata +37 -110
  28. data/.gitignore +0 -17
  29. data/.travis.yml +0 -10
  30. data/CONTRIBUTORS +0 -6
  31. data/Gemfile +0 -11
  32. data/History.txt +0 -4
  33. data/README.md.in +0 -94
  34. data/Rakefile +0 -27
  35. data/test/_lib.rb +0 -12
  36. data/test/integration/_lib/fixtures/env_printer/env_printer.rb +0 -26
  37. data/test/integration/_lib/fixtures/exit_during_upgrade/exiting_server.rb +0 -23
  38. data/test/integration/_lib/fixtures/exit_during_upgrade/upgrade_reexec.rb +0 -6
  39. data/test/integration/_lib/fixtures/pdeathsig_printer/pdeathsig_printer.rb +0 -29
  40. data/test/integration/_lib/fixtures/signal_timeout/sleepy_server.rb +0 -23
  41. data/test/integration/_lib/fixtures/upgrade_project/upgrading_server.rb +0 -24
  42. data/test/integration/_lib/helpers/einhorn_helpers.rb +0 -148
  43. data/test/integration/_lib/helpers.rb +0 -4
  44. data/test/integration/_lib.rb +0 -6
  45. data/test/integration/pdeathsig.rb +0 -26
  46. data/test/integration/startup.rb +0 -31
  47. data/test/integration/upgrading.rb +0 -204
  48. data/test/unit/_lib/bad_worker.rb +0 -7
  49. data/test/unit/_lib/sleep_worker.rb +0 -5
  50. data/test/unit/einhorn/client.rb +0 -88
  51. data/test/unit/einhorn/command/interface.rb +0 -49
  52. data/test/unit/einhorn/command.rb +0 -135
  53. data/test/unit/einhorn/event.rb +0 -89
  54. data/test/unit/einhorn/worker_pool.rb +0 -39
  55. data/test/unit/einhorn.rb +0 -96
  56. /data/{LICENSE → LICENSE.txt} +0 -0
@@ -1,5 +1,5 @@
1
- require 'tmpdir'
2
- require 'socket'
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
- begin
27
- File.unlink(file)
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, 'w', 0600) do |f|
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.open(file, 'w') {|f| f.write($$)}
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
- filename = "einhorn-#{cmd_name}.sock"
110
+ filename = if cmd_name
111
+ "einhorn-#{cmd_name}.sock"
114
112
  else
115
- filename = "einhorn.sock"
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
- filename = "einhorn-#{cmd_name}.lock"
124
+ filename = if cmd_name
125
+ "einhorn-#{cmd_name}.lock"
128
126
  else
129
- filename = "einhorn.lock"
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
- filename = "einhorn-#{cmd_name}.pid"
138
+ filename = if cmd_name
139
+ "einhorn-#{cmd_name}.pid"
142
140
  else
143
- filename = "einhorn.pid"
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{INT TERM QUIT HUP ALRM CHLD USR2}.each do |signal|
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] = {:description => description, :code => code}
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.kind_of?(Hash)
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['id'], true)
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
- self.send_message(conn, message, id, last)
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 = {'message' => message, 'request_id' => request_id }
234
- response['wait'] = true unless last
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 = {'message' => message}
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['command']
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
- return command_spec[:code].call(conn, request)
251
- rescue StandardError => e
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
- return msg
248
+ msg
255
249
  end
256
250
  else
257
251
  conn.log_debug("Received unrecognized command: #{request.inspect}")
258
- return unrecognized_command(conn, request)
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
- <<EOF
274
- Unrecognized command: #{request['command'].inspect}
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 'worker:ack' do |conn, request|
282
- if pid = request['pid']
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 'worker:ping' do |conn, request|
293
- if pid = request['pid']
294
- Einhorn::Command.register_ping(pid, request['request_id'])
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 'ehlo' do |conn, request|
305
- <<EOF
306
- Welcome, #{request['user']}! You are speaking to Einhorn Master Process #{$$}#{Einhorn::State.cmd_name ? " (#{Einhorn::State.cmd_name})" : ''}.
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 'help', 'Print out available commands' do
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 'state', "Get a dump of Einhorn's current state" do
319
- YAML.dump({:state => Einhorn::State.dumpable_state})
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 'reload', 'Reload Einhorn' do |conn, request|
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, 'Reloading, as commanded', request['id'], true)
324
+ send_message(conn, "Reloading, as commanded", request["id"], true)
331
325
  Einhorn::Command.reload
332
326
  end
333
327
 
334
- command 'inc', 'Increment the number of Einhorn child processes' do
328
+ command "inc", "Increment the number of Einhorn child processes" do
335
329
  Einhorn::Command.increment
336
330
  end
337
331
 
338
- command 'dec', 'Decrement the number of Einhorn child processes' do
332
+ command "dec", "Decrement the number of Einhorn child processes" do
339
333
  Einhorn::Command.decrement
340
334
  end
341
335
 
342
- command 'set_workers', 'Set the number of Einhorn child processes' do |conn, request|
343
- args = request['args']
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 'quieter', 'Decrease verbosity' do
351
+ command "quieter", "Decrease verbosity" do
358
352
  Einhorn::Command.quieter
359
353
  end
360
354
 
361
- command 'louder', 'Increase verbosity' do
355
+ command "louder", "Increase verbosity" do
362
356
  Einhorn::Command.louder
363
357
  end
364
358
 
365
- command 'upgrade', 'Upgrade all Einhorn workers smoothly. This causes Einhorn to reload its own code as well.' do |conn, request|
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, 'Upgrading smoothly, as commanded', request['id'])
370
- conn.subscribe(:upgrade, request['id'])
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 'upgrade_fleet', 'Upgrade all Einhorn workers a fleet at a time. This causes Einhorn to reload its own code as well.' do |conn, request|
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, 'Upgrading fleet, as commanded', request['id'])
381
- conn.subscribe(:upgrade, request['id'])
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 'signal', 'Send one or more signals to all workers (args: SIG1 [SIG2 ...])' do |conn, request|
388
- args = request['args']
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 'die', 'Send SIGNAL (default: SIGUSR2) to all workers, stop spawning new ones, and exit once all workers die (args: [SIGNAL])' do |conn, request|
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['args']
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 '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|
428
- args = request['args']
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 'Must pass in a YAML-encoded hash'
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 = YAML.load(args.join(' '))
440
- rescue ArgumentError => e
441
- next 'Could not parse argument. Must be a YAML-encoded hash'
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.kind_of?(Hash)
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 'No args provided' unless args
455
- return 'Args must be an array' unless args.kind_of?(Array)
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.kind_of?(String)
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