einhorn 0.7.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/Changes.md +10 -0
  3. data/README.md +36 -30
  4. data/bin/einhorn +17 -2
  5. data/einhorn.gemspec +23 -21
  6. data/example/pool_worker.rb +1 -1
  7. data/example/thin_example +8 -8
  8. data/example/time_server +5 -5
  9. data/lib/einhorn/client.rb +8 -9
  10. data/lib/einhorn/command/interface.rb +100 -95
  11. data/lib/einhorn/command.rb +167 -88
  12. data/lib/einhorn/compat.rb +7 -7
  13. data/lib/einhorn/event/abstract_text_descriptor.rb +31 -35
  14. data/lib/einhorn/event/ack_timer.rb +2 -2
  15. data/lib/einhorn/event/command_server.rb +7 -9
  16. data/lib/einhorn/event/connection.rb +1 -3
  17. data/lib/einhorn/event/loop_breaker.rb +2 -1
  18. data/lib/einhorn/event/persistent.rb +2 -2
  19. data/lib/einhorn/event/timer.rb +4 -4
  20. data/lib/einhorn/event.rb +29 -20
  21. data/lib/einhorn/prctl.rb +26 -0
  22. data/lib/einhorn/prctl_linux.rb +48 -0
  23. data/lib/einhorn/safe_yaml.rb +17 -0
  24. data/lib/einhorn/version.rb +1 -1
  25. data/lib/einhorn/worker.rb +67 -49
  26. data/lib/einhorn/worker_pool.rb +9 -9
  27. data/lib/einhorn.rb +155 -126
  28. metadata +42 -137
  29. data/.gitignore +0 -17
  30. data/.travis.yml +0 -10
  31. data/CONTRIBUTORS +0 -6
  32. data/Gemfile +0 -11
  33. data/History.txt +0 -4
  34. data/README.md.in +0 -76
  35. data/Rakefile +0 -27
  36. data/test/_lib.rb +0 -12
  37. data/test/integration/_lib/fixtures/env_printer/env_printer.rb +0 -26
  38. data/test/integration/_lib/fixtures/exit_during_upgrade/exiting_server.rb +0 -22
  39. data/test/integration/_lib/fixtures/exit_during_upgrade/upgrade_reexec.rb +0 -6
  40. data/test/integration/_lib/fixtures/upgrade_project/upgrading_server.rb +0 -22
  41. data/test/integration/_lib/helpers/einhorn_helpers.rb +0 -143
  42. data/test/integration/_lib/helpers.rb +0 -4
  43. data/test/integration/_lib.rb +0 -6
  44. data/test/integration/startup.rb +0 -31
  45. data/test/integration/upgrading.rb +0 -157
  46. data/test/unit/einhorn/client.rb +0 -88
  47. data/test/unit/einhorn/command/interface.rb +0 -49
  48. data/test/unit/einhorn/command.rb +0 -21
  49. data/test/unit/einhorn/event.rb +0 -89
  50. data/test/unit/einhorn/worker_pool.rb +0 -39
  51. data/test/unit/einhorn.rb +0 -58
  52. /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,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 'ehlo' do |conn, request|
294
- <<EOF
295
- Welcome, #{request['user']}! You are speaking to Einhorn Master Process #{$$}#{Einhorn::State.cmd_name ? " (#{Einhorn::State.cmd_name})" : ''}.
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 'help', 'Print out available commands' do
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 'state', "Get a dump of Einhorn's current state" do
308
- 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})
309
314
  end
310
315
 
311
- command 'reload', 'Reload Einhorn' do |conn, request|
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, 'Reloading, as commanded', request['id'], true)
324
+ send_message(conn, "Reloading, as commanded", request["id"], true)
320
325
  Einhorn::Command.reload
321
326
  end
322
327
 
323
- command 'inc', 'Increment the number of Einhorn child processes' do
328
+ command "inc", "Increment the number of Einhorn child processes" do
324
329
  Einhorn::Command.increment
325
330
  end
326
331
 
327
- command 'dec', 'Decrement the number of Einhorn child processes' do
332
+ command "dec", "Decrement the number of Einhorn child processes" do
328
333
  Einhorn::Command.decrement
329
334
  end
330
335
 
331
- command 'set_workers', 'Set the number of Einhorn child processes' do |conn, request|
332
- args = request['args']
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 'quieter', 'Decrease verbosity' do
351
+ command "quieter", "Decrease verbosity" do
347
352
  Einhorn::Command.quieter
348
353
  end
349
354
 
350
- command 'louder', 'Increase verbosity' do
355
+ command "louder", "Increase verbosity" do
351
356
  Einhorn::Command.louder
352
357
  end
353
358
 
354
- 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|
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, 'Upgrading smoothly, as commanded', request['id'])
359
- conn.subscribe(:upgrade, request['id'])
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 '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|
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, 'Upgrading fleet, as commanded', request['id'])
370
- conn.subscribe(:upgrade, request['id'])
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 'signal', 'Send one or more signals to all workers (args: SIG1 [SIG2 ...])' do |conn, request|
377
- args = request['args']
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 '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|
396
401
  # TODO: dedup this code with signal
397
- args = request['args']
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 '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|
417
- args = request['args']
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 'Must pass in a YAML-encoded hash'
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 = YAML.load(args.join(' '))
429
- rescue ArgumentError => e
430
- 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"
431
436
  end
432
437
 
433
- unless parsed.kind_of?(Hash)
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 'No args provided' unless args
444
- 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)
445
450
 
446
451
  args.each do |arg|
447
- 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)
448
453
  end
449
454
 
450
455
  nil