einhorn 0.7.0 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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