einhorn 0.7.0 → 0.8.2

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.
@@ -148,17 +148,17 @@ module Einhorn::Command
148
148
  ## Signals
149
149
  def self.install_handlers
150
150
  trap_async("INT") do
151
- Einhorn::Command.signal_all("USR2", Einhorn::WorkerPool.workers)
151
+ Einhorn::Command.signal_all("USR2")
152
152
  Einhorn::Command.stop_respawning
153
153
  end
154
154
  trap_async("TERM") do
155
- Einhorn::Command.signal_all("TERM", Einhorn::WorkerPool.workers)
155
+ Einhorn::Command.signal_all("TERM")
156
156
  Einhorn::Command.stop_respawning
157
157
  end
158
158
  # Note that quit is a bit different, in that it will actually
159
159
  # make Einhorn quit without waiting for children to exit.
160
160
  trap_async("QUIT") do
161
- Einhorn::Command.signal_all("QUIT", Einhorn::WorkerPool.workers)
161
+ Einhorn::Command.signal_all("QUIT")
162
162
  Einhorn::Command.stop_respawning
163
163
  exit(1)
164
164
  end
@@ -169,12 +169,12 @@ module Einhorn::Command
169
169
  end
170
170
  trap_async("CHLD") {}
171
171
  trap_async("USR2") do
172
- Einhorn::Command.signal_all("USR2", Einhorn::WorkerPool.workers)
172
+ Einhorn::Command.signal_all("USR2")
173
173
  Einhorn::Command.stop_respawning
174
174
  end
175
175
  at_exit do
176
176
  if Einhorn::State.kill_children_on_exit && Einhorn::TransientState.whatami == :master
177
- Einhorn::Command.signal_all("USR2", Einhorn::WorkerPool.workers)
177
+ Einhorn::Command.signal_all("USR2")
178
178
  Einhorn::Command.stop_respawning
179
179
  end
180
180
  end
@@ -289,6 +289,17 @@ EOF
289
289
  nil
290
290
  end
291
291
 
292
+ command 'worker:ping' do |conn, request|
293
+ if pid = request['pid']
294
+ Einhorn::Command.register_ping(pid, request['request_id'])
295
+ else
296
+ conn.log_error("Invalid request (no pid): #{request.inspect}")
297
+ end
298
+ # Throw away this connection in case the application forgets to
299
+ conn.close
300
+ nil
301
+ end
302
+
292
303
  # Used by einhornsh
293
304
  command 'ehlo' do |conn, request|
294
305
  <<EOF
@@ -305,7 +316,7 @@ EOF
305
316
  end
306
317
 
307
318
  command 'state', "Get a dump of Einhorn's current state" do
308
- YAML.dump(Einhorn::Command.dumpable_state)
319
+ YAML.dump({:state => Einhorn::State.dumpable_state})
309
320
  end
310
321
 
311
322
  command 'reload', 'Reload Einhorn' do |conn, request|
@@ -335,9 +346,9 @@ EOF
335
346
  end
336
347
 
337
348
  count = args[0].to_i
338
- if count < 1 || count > 100
339
- # sancheck. 100 is kinda arbitrary.
340
- next "Invalid count: '#{args[0]}'. Must be an integer in [1,100)."
349
+ if count < 1 || count > 1000
350
+ # sancheck. 1000 is kinda arbitrary.
351
+ next "Invalid count: '#{args[0]}'. Must be an integer in [1,1000)."
341
352
  end
342
353
 
343
354
  Einhorn::Command.set_workers(count)
@@ -407,7 +418,7 @@ EOF
407
418
 
408
419
  signal = args[0] || "USR2"
409
420
 
410
- response = Einhorn::Command.signal_all(signal, Einhorn::WorkerPool.workers)
421
+ response = Einhorn::Command.signal_all(signal)
411
422
  Einhorn::Command.stop_respawning
412
423
 
413
424
  "Einhorn is going down! #{response}"
@@ -4,6 +4,7 @@ module Einhorn
4
4
  module Event
5
5
  @@loopbreak_reader = nil
6
6
  @@loopbreak_writer = nil
7
+ @@default_timeout = nil
7
8
  @@signal_actions = []
8
9
  @@readable = {}
9
10
  @@writeable = {}
@@ -120,7 +121,7 @@ module Einhorn
120
121
  if expires_at = @@timers.keys.sort[0]
121
122
  expires_at - Time.now
122
123
  else
123
- nil
124
+ @@default_timeout
124
125
  end
125
126
  end
126
127
 
@@ -165,6 +166,14 @@ module Einhorn
165
166
  Einhorn.log_error("Loop break pipe is full -- probably means that we are quite backlogged")
166
167
  end
167
168
  end
169
+
170
+ def self.default_timeout=(val)
171
+ @@default_timeout = val.to_i == 0 ? nil : val.to_i
172
+ end
173
+
174
+ def self.default_timeout
175
+ @@default_timeout
176
+ end
168
177
  end
169
178
  end
170
179
 
@@ -57,13 +57,13 @@ module Einhorn::Event
57
57
  end
58
58
 
59
59
  def register!
60
- log_info("client connected")
60
+ log_debug("client connected")
61
61
  Einhorn::Event.register_connection(self, @socket.fileno)
62
62
  super
63
63
  end
64
64
 
65
65
  def deregister!
66
- log_info("client disconnected") if Einhorn::TransientState.whatami == :master
66
+ log_debug("client disconnected") if Einhorn::TransientState.whatami == :master
67
67
  Einhorn::Event.deregister_connection(@socket.fileno)
68
68
  super
69
69
  end
@@ -0,0 +1,26 @@
1
+ module Einhorn
2
+ class PrctlAbstract
3
+ def self.get_pdeathsig
4
+ raise NotImplementedError
5
+ end
6
+
7
+ def self.set_pdeathsig(signal)
8
+ raise NotImplementedError
9
+ end
10
+ end
11
+
12
+ class PrctlUnimplemented < PrctlAbstract
13
+ # Deliberately empty; NotImplementedError is intended
14
+ end
15
+
16
+ if RUBY_PLATFORM =~ /linux/ then
17
+ begin
18
+ require 'einhorn/prctl_linux'
19
+ Prctl = PrctlLinux
20
+ rescue LoadError
21
+ Prctl = PrctlUnimplemented
22
+ end
23
+ else
24
+ Prctl = PrctlUnimplemented
25
+ end
26
+ end
@@ -0,0 +1,49 @@
1
+ require 'fiddle'
2
+ require 'fiddle/import'
3
+
4
+ module Einhorn
5
+ module PrctlRaw
6
+ extend Fiddle::Importer
7
+ dlload Fiddle.dlopen(nil) # libc
8
+ extern 'int prctl(int, unsigned long, unsigned long, unsigned long, unsigned long)'
9
+
10
+ # From linux/prctl.h
11
+ SET_PDEATHSIG = 1
12
+ GET_PDEATHSIG = 2
13
+ end
14
+
15
+ class PrctlLinux < PrctlAbstract
16
+ # Reading integers is hard with fiddle. :(
17
+ IntStruct = Fiddle::CStructBuilder.create(Fiddle::CStruct, [Fiddle::TYPE_INT], ['i'])
18
+
19
+ def self.get_pdeathsig
20
+ out = IntStruct.malloc
21
+ out.i = 0
22
+ if PrctlRaw.prctl(PrctlRaw::GET_PDEATHSIG, out.to_i, 0, 0, 0) != 0 then
23
+ raise SystemCallError.new("get_pdeathsig", Fiddle.last_error)
24
+ end
25
+
26
+ signo = out.i
27
+ if signo == 0 then
28
+ return nil
29
+ end
30
+
31
+ return Signal.signame(signo)
32
+ end
33
+
34
+ def self.set_pdeathsig(signal)
35
+ case
36
+ when signal == nil
37
+ signo = 0
38
+ when signal.instance_of?(String)
39
+ signo = Signal.list.fetch(signal)
40
+ else
41
+ signo = signal
42
+ end
43
+
44
+ if PrctlRaw.prctl(PrctlRaw::SET_PDEATHSIG, signo, 0, 0, 0) != 0 then
45
+ raise SystemCallError.new("set_pdeathsig(#{signal})", Fiddle.last_error)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,3 +1,3 @@
1
1
  module Einhorn
2
- VERSION = '0.7.0'
2
+ VERSION = '0.8.2'
3
3
  end
@@ -66,33 +66,28 @@ module Einhorn
66
66
  # be useful for anything. Maybe if it's always fd 3, because then
67
67
  # the user wouldn't have to provide an arg.
68
68
  def self.ack!(discovery=:env, arg=nil)
69
- ensure_worker!
70
- close_after_use = true
71
-
72
- case discovery
73
- when :env
74
- socket = ENV['EINHORN_SOCK_PATH']
75
- client = Einhorn::Client.for_path(socket)
76
- when :fd
77
- raise "No EINHORN_SOCK_FD provided in environment. Did you run einhorn with the -g flag?" unless fd_str = ENV['EINHORN_SOCK_FD']
78
-
79
- fd = Integer(fd_str)
80
- client = Einhorn::Client.for_fd(fd)
81
- close_after_use = false if arg
82
- when :direct
83
- socket = arg
84
- client = Einhorn::Client.for_path(socket)
85
- else
86
- raise "Unrecognized socket discovery mechanism: #{discovery.inspect}. Must be one of :filesystem, :argv, or :direct"
69
+ handle_command_socket(discovery, arg) do |client|
70
+ client.send_command('command' => 'worker:ack', 'pid' => $$)
87
71
  end
72
+ end
88
73
 
89
- client.send_command({
90
- 'command' => 'worker:ack',
91
- 'pid' => $$
92
- })
93
-
94
- client.close if close_after_use
95
- true
74
+ # Call this to indicate your child process is up and in a healthy state.
75
+ # Arguments:
76
+ #
77
+ # @request_id: Identifies the request ID of the worker, can be used to debug wedged workers.
78
+ #
79
+ # @discovery: How to discover the master process's command socket.
80
+ # :env: Discover the path from ENV['EINHORN_SOCK_PATH']
81
+ # :fd: Just use the file descriptor in ENV['EINHORN_SOCK_FD'].
82
+ # Must run the master with the -g flag. This is mostly
83
+ # useful if you don't have a nice library like Einhorn::Worker.
84
+ # Then @arg being true causes the FD to be left open after ACK;
85
+ # otherwise it is closed.
86
+ # :direct: Provide the path to the command socket in @arg.
87
+ def self.ping!(request_id, discovery=:env, arg=nil)
88
+ handle_command_socket(discovery, arg) do |client|
89
+ client.send_command('command' => 'worker:ping', 'pid' => $$, 'request_id' => request_id)
90
+ end
96
91
  end
97
92
 
98
93
  def self.socket(number=nil)
@@ -139,6 +134,33 @@ module Einhorn
139
134
 
140
135
  private
141
136
 
137
+ def self.handle_command_socket(discovery, contextual_arg)
138
+ ensure_worker!
139
+ close_after_use = true
140
+
141
+ case discovery
142
+ when :env
143
+ socket = ENV['EINHORN_SOCK_PATH']
144
+ client = Einhorn::Client.for_path(socket)
145
+ when :fd
146
+ raise "No EINHORN_SOCK_FD provided in environment. Did you run einhorn with the -g flag?" unless fd_str = ENV['EINHORN_SOCK_FD']
147
+
148
+ fd = Integer(fd_str)
149
+ client = Einhorn::Client.for_fd(fd)
150
+ close_after_use = false if contextual_arg
151
+ when :direct
152
+ socket = contextual_arg
153
+ client = Einhorn::Client.for_path(socket)
154
+ else
155
+ raise "Unrecognized socket discovery mechanism: #{discovery.inspect}. Must be one of :filesystem, :argv, or :direct"
156
+ end
157
+
158
+ yield client
159
+ client.close if close_after_use
160
+
161
+ true
162
+ end
163
+
142
164
  def self.socket_from_filesystem(cmd_name)
143
165
  ppid = Process.ppid
144
166
  socket_path_file = Einhorn::Command::Interface.socket_path_file(ppid)
@@ -5,6 +5,7 @@ require 'einhorn/worker'
5
5
  def einhorn_main
6
6
  serv = Socket.for_fd(Einhorn::Worker.socket!)
7
7
  Einhorn::Worker.ack!
8
+ Einhorn::Worker.ping!("id-1")
8
9
 
9
10
  Signal.trap('USR2') do
10
11
  sleep 3
@@ -0,0 +1,29 @@
1
+ require 'bundler/setup'
2
+ require 'socket'
3
+ require 'einhorn/worker'
4
+ require 'einhorn/prctl'
5
+
6
+ def einhorn_main
7
+ serv = Socket.for_fd(Einhorn::Worker.socket!)
8
+
9
+ Signal.trap("USR2") { exit }
10
+
11
+ begin
12
+ output = Einhorn::Prctl.get_pdeathsig
13
+ if output == nil then
14
+ output = "nil"
15
+ end
16
+ rescue NotImplementedError
17
+ output = "not implemented"
18
+ end
19
+
20
+ Einhorn::Worker.ack!
21
+ while true
22
+ s, _ = serv.accept
23
+ s.write(output)
24
+ s.flush
25
+ s.close
26
+ end
27
+ end
28
+
29
+ einhorn_main if $0 == __FILE__
@@ -0,0 +1,23 @@
1
+ require 'bundler/setup'
2
+ require 'socket'
3
+ require 'einhorn/worker'
4
+
5
+ def einhorn_main
6
+ serv = Socket.for_fd(Einhorn::Worker.socket!)
7
+ Einhorn::Worker.ack!
8
+ Einhorn::Worker.ping!("id-1")
9
+
10
+ Signal.trap('USR2') do
11
+ sleep ENV.fetch("TRAP_SLEEP").to_i
12
+ exit
13
+ end
14
+
15
+ while true
16
+ s, _ = serv.accept
17
+ s.write($$)
18
+ s.flush
19
+ s.close
20
+ end
21
+ end
22
+
23
+ einhorn_main if $0 == __FILE__
@@ -9,6 +9,8 @@ def einhorn_main
9
9
  $stderr.puts "Worker has a socket"
10
10
  Einhorn::Worker.ack!
11
11
  $stderr.puts "Worker sent ack to einhorn"
12
+ Einhorn::Worker.ping!("id-1")
13
+ $stderr.puts "Worker has sent a ping to einhorn"
12
14
  while true
13
15
  s, addrinfo = serv.accept
14
16
  $stderr.puts "Worker got a socket!"
@@ -40,28 +40,28 @@ module Helpers
40
40
  begin
41
41
  stdout, stderr = process.communicate
42
42
  rescue Errno::ECHILD
43
- # It's dead, and we're not getting anything. This is
44
- # peaceful.
43
+ # It's dead, and we're not getting anything. This is peaceful.
45
44
  end
46
45
  end
47
46
  yield(process) if block_given?
48
- rescue Exception => e
47
+ rescue
49
48
  unless (status = process.poll) && status.exited?
50
49
  process.terminate
51
50
  end
52
51
  raise
53
52
  ensure
54
53
  unless (status = process.poll) && status.exited?
55
- begin
56
- Timeout.timeout(10) do # (Argh, I'm so sorry)
57
- status = process.wait
54
+ for i in 1..10 do
55
+ status = process.poll
56
+ if status && status.exited?
57
+ break
58
58
  end
59
- rescue Timeout::Error
59
+ sleep(1)
60
+ end
61
+ unless status && status.exited?
60
62
  $stderr.puts "Could not get Einhorn to quit within 10 seconds, killing it forcefully..."
61
63
  process.send_signal("KILL")
62
64
  status = process.wait
63
- rescue Errno::ECHILD
64
- # Process is dead!
65
65
  end
66
66
  end
67
67
  communicator.join
@@ -106,6 +106,11 @@ module Helpers
106
106
  open_port.close
107
107
  end
108
108
 
109
+ def get_state(client)
110
+ client.send_command('command' => 'state')
111
+ YAML.load(client.receive_message['message'])[:state]
112
+ end
113
+
109
114
  def wait_for_open_port
110
115
  max_retries = 50
111
116
  begin
@@ -0,0 +1,26 @@
1
+ require(File.expand_path('_lib', File.dirname(__FILE__)))
2
+
3
+ class PdeathsigTest < EinhornIntegrationTestCase
4
+ include Helpers::EinhornHelpers
5
+
6
+ describe 'when run with -k' do
7
+ before do
8
+ @dir = prepare_fixture_directory('pdeathsig_printer')
9
+ @port = find_free_port
10
+ @server_program = File.join(@dir, 'pdeathsig_printer.rb')
11
+ @socket_path = File.join(@dir, 'einhorn.sock')
12
+ end
13
+ after { cleanup_fixtured_directories }
14
+
15
+ it 'sets pdeathsig to USR2 in the child process' do
16
+ with_running_einhorn(%W{einhorn -m manual -b 127.0.0.1:#{@port} -d #{@socket_path} -k -- ruby #{@server_program}}) do |process|
17
+ wait_for_open_port
18
+ output = read_from_port
19
+ if output != "not implemented" then
20
+ assert_equal("USR2", output)
21
+ end
22
+ process.terminate
23
+ end
24
+ end
25
+ end
26
+ end