resqued 0.8.6 → 0.10.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGES.md +23 -0
- data/exe/resqued +41 -22
- data/lib/resqued.rb +5 -5
- data/lib/resqued/config.rb +7 -7
- data/lib/resqued/config/after_fork.rb +1 -1
- data/lib/resqued/config/base.rb +1 -1
- data/lib/resqued/config/before_fork.rb +1 -1
- data/lib/resqued/config/worker.rb +13 -13
- data/lib/resqued/daemon.rb +1 -0
- data/lib/resqued/exec_on_hup.rb +43 -0
- data/lib/resqued/listener.rb +51 -49
- data/lib/resqued/listener_pool.rb +97 -0
- data/lib/resqued/listener_proxy.rb +40 -31
- data/lib/resqued/listener_state.rb +8 -0
- data/lib/resqued/logging.rb +15 -8
- data/lib/resqued/master.rb +94 -98
- data/lib/resqued/master_state.rb +73 -0
- data/lib/resqued/procline_version.rb +2 -2
- data/lib/resqued/sleepy.rb +6 -4
- data/lib/resqued/test_case.rb +3 -3
- data/lib/resqued/version.rb +1 -1
- data/lib/resqued/worker.rb +16 -12
- data/spec/fixtures/test_case_after_fork_raises.rb +5 -2
- data/spec/fixtures/test_case_before_fork_raises.rb +4 -1
- data/spec/fixtures/test_case_environment.rb +3 -1
- data/spec/integration/listener_still_starting_spec.rb +23 -0
- data/spec/integration/master_inherits_child_spec.rb +85 -0
- data/spec/integration/restart_spec.rb +21 -0
- data/spec/resqued/backoff_spec.rb +27 -27
- data/spec/resqued/config/fork_event_spec.rb +8 -8
- data/spec/resqued/config/worker_spec.rb +63 -50
- data/spec/resqued/config_spec.rb +6 -6
- data/spec/resqued/sleepy_spec.rb +10 -11
- data/spec/resqued/test_case_spec.rb +7 -7
- data/spec/spec_helper.rb +6 -1
- data/spec/support/custom_matchers.rb +10 -2
- data/spec/support/extra-child-shim +6 -0
- data/spec/support/resqued_integration_helpers.rb +50 -0
- data/spec/support/resqued_path.rb +11 -0
- metadata +48 -27
- data/exe/resqued-listener +0 -6
@@ -0,0 +1,97 @@
|
|
1
|
+
require "resqued/listener_proxy"
|
2
|
+
require "resqued/listener_state"
|
3
|
+
|
4
|
+
module Resqued
|
5
|
+
class ListenerPool
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# Public: Initialize a new pool, and store state in the given master's state.
|
9
|
+
def initialize(master_state)
|
10
|
+
@master_state = master_state
|
11
|
+
@listener_proxies = {}
|
12
|
+
# If this master is replacing an old one, there will be listeners in the state already.
|
13
|
+
@master_state.listener_states.each do |pid, ls|
|
14
|
+
@listener_proxies[pid] = ListenerProxy.new(ls)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: Iterate through all active ListenerProxy instances.
|
19
|
+
def each(&block)
|
20
|
+
@listener_proxies.values.each(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Number of active listeners.
|
24
|
+
def size
|
25
|
+
@listener_proxies.size
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Are the listeners all gone?
|
29
|
+
def empty?
|
30
|
+
@listener_proxies.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: Initialize a new listener, run it, and record it as the current listener. Returns its ListenerProxy.
|
34
|
+
def start!
|
35
|
+
listener_state = ListenerState.new
|
36
|
+
listener_state.options = {
|
37
|
+
config_paths: @master_state.config_paths,
|
38
|
+
old_workers: map { |l| l.running_workers }.flatten,
|
39
|
+
listener_id: next_listener_id,
|
40
|
+
}
|
41
|
+
listener = ListenerProxy.new(listener_state)
|
42
|
+
listener.run
|
43
|
+
@master_state.listener_states[listener.pid] = listener_state
|
44
|
+
@listener_proxies[listener.pid] = listener
|
45
|
+
@master_state.current_listener_pid = listener.pid
|
46
|
+
return listener
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: Remove the given pid from the set of known listeners, and return its ListenerProxy.
|
50
|
+
def delete(pid)
|
51
|
+
@master_state.listener_states.delete(pid)
|
52
|
+
return @listener_proxies.delete(pid)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: The current ListenerProxy, if available.
|
56
|
+
def current
|
57
|
+
@listener_proxies[current_pid]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: The pid of the current listener, if available.
|
61
|
+
def current_pid
|
62
|
+
@master_state.current_listener_pid
|
63
|
+
end
|
64
|
+
|
65
|
+
# Public: Don't consider the current listener to be current anymore.
|
66
|
+
def clear_current!
|
67
|
+
@master_state.current_listener_pid = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
# Public: Change the current listener into the last good listener.
|
71
|
+
def cycle_current
|
72
|
+
@master_state.last_good_listener_pid = @master_state.current_listener_pid
|
73
|
+
@master_state.current_listener_pid = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
# Public: The last good (previous current) ListenerProxy, if available.
|
77
|
+
def last_good
|
78
|
+
@listener_proxies[last_good_pid]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Public: The pid of the last good listener, if available.
|
82
|
+
def last_good_pid
|
83
|
+
@master_state.last_good_listener_pid
|
84
|
+
end
|
85
|
+
|
86
|
+
# Public: Forget which listener was the last good one.
|
87
|
+
def clear_last_good!
|
88
|
+
@master_state.last_good_listener_pid = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def next_listener_id
|
94
|
+
@master_state.listeners_created += 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "fcntl"
|
2
|
+
require "socket"
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "resqued/listener"
|
5
|
+
require "resqued/logging"
|
6
6
|
|
7
7
|
module Resqued
|
8
8
|
# Controls a listener process from the master process.
|
@@ -10,41 +10,46 @@ module Resqued
|
|
10
10
|
include Resqued::Logging
|
11
11
|
|
12
12
|
# Public.
|
13
|
-
def initialize(
|
14
|
-
@
|
13
|
+
def initialize(state)
|
14
|
+
@state = state
|
15
15
|
end
|
16
16
|
|
17
|
+
attr_reader :state
|
18
|
+
|
17
19
|
# Public: wrap up all the things, this object is going home.
|
18
20
|
def dispose
|
19
|
-
if @master_socket
|
20
|
-
@master_socket.close
|
21
|
-
@master_socket = nil
|
21
|
+
if @state.master_socket
|
22
|
+
@state.master_socket.close
|
23
|
+
@state.master_socket = nil
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
25
27
|
# Public: An IO to select on to check if there is incoming data available.
|
26
28
|
def read_pipe
|
27
|
-
@master_socket
|
29
|
+
@state.master_socket
|
28
30
|
end
|
29
31
|
|
30
32
|
# Public: The pid of the running listener process.
|
31
|
-
|
33
|
+
def pid
|
34
|
+
@state.pid
|
35
|
+
end
|
32
36
|
|
33
37
|
# Public: Start the listener process.
|
34
38
|
def run
|
35
39
|
return if pid
|
40
|
+
|
36
41
|
listener_socket, master_socket = UNIXSocket.pair
|
37
|
-
if @pid = fork
|
42
|
+
if @state.pid = fork
|
38
43
|
# master
|
39
44
|
listener_socket.close
|
40
45
|
master_socket.close_on_exec = true
|
41
|
-
log "Started listener #{@pid}"
|
42
|
-
@master_socket = master_socket
|
46
|
+
log "Started listener #{@state.pid}"
|
47
|
+
@state.master_socket = master_socket
|
43
48
|
else
|
44
49
|
# listener
|
45
50
|
master_socket.close
|
46
|
-
Master::TRAPS.each { |signal| trap(signal,
|
47
|
-
Listener.new(@options.merge(:
|
51
|
+
Master::TRAPS.each { |signal| trap(signal, "DEFAULT") rescue nil }
|
52
|
+
Listener.new(@state.options.merge(socket: listener_socket)).exec
|
48
53
|
exit
|
49
54
|
end
|
50
55
|
end
|
@@ -57,46 +62,50 @@ module Resqued
|
|
57
62
|
|
58
63
|
# Public: Get the list of workers running from this listener.
|
59
64
|
def running_workers
|
60
|
-
worker_pids.map { |pid,
|
65
|
+
worker_pids.map { |pid, queue_key| { pid: pid, queue_key: queue_key } }
|
61
66
|
end
|
62
67
|
|
63
68
|
# Private: Map worker pids to queue names
|
64
69
|
def worker_pids
|
65
|
-
@worker_pids ||= {}
|
70
|
+
@state.worker_pids ||= {}
|
66
71
|
end
|
67
72
|
|
68
73
|
# Public: Check for updates on running worker information.
|
69
74
|
def read_worker_status(options)
|
70
75
|
on_activity = options[:on_activity]
|
71
|
-
until @master_socket.nil?
|
72
|
-
IO.select([@master_socket], nil, nil, 0) or return
|
73
|
-
case line = @master_socket.readline
|
76
|
+
until @state.master_socket.nil?
|
77
|
+
IO.select([@state.master_socket], nil, nil, 0) or return
|
78
|
+
case line = @state.master_socket.readline
|
74
79
|
when /^\+(\d+),(.*)$/
|
75
80
|
worker_pids[$1] = $2
|
76
|
-
on_activity
|
81
|
+
on_activity&.worker_started($1)
|
77
82
|
when /^-(\d+)$/
|
78
83
|
worker_pids.delete($1)
|
79
|
-
on_activity
|
84
|
+
on_activity&.worker_finished($1)
|
80
85
|
when /^RUNNING/
|
81
|
-
on_activity
|
82
|
-
when
|
86
|
+
on_activity&.listener_running(self)
|
87
|
+
when ""
|
83
88
|
break
|
84
89
|
else
|
85
90
|
log "Malformed data from listener: #{line.inspect}"
|
86
91
|
end
|
87
92
|
end
|
88
93
|
rescue EOFError, Errno::ECONNRESET
|
89
|
-
@master_socket.close
|
90
|
-
@master_socket = nil
|
94
|
+
@state.master_socket.close
|
95
|
+
@state.master_socket = nil
|
91
96
|
end
|
92
97
|
|
93
98
|
# Public: Tell the listener process that a worker finished.
|
94
99
|
def worker_finished(pid)
|
95
|
-
return if @master_socket.nil?
|
96
|
-
|
100
|
+
return if @state.master_socket.nil?
|
101
|
+
|
102
|
+
@state.master_socket.write_nonblock("#{pid}\n")
|
103
|
+
rescue IO::WaitWritable
|
104
|
+
log "Couldn't tell #{@state.pid} that #{pid} exited!"
|
105
|
+
# Ignore it, maybe the next time it'll work.
|
97
106
|
rescue Errno::EPIPE
|
98
|
-
@master_socket.close
|
99
|
-
@master_socket = nil
|
107
|
+
@state.master_socket.close
|
108
|
+
@state.master_socket = nil
|
100
109
|
end
|
101
110
|
end
|
102
111
|
end
|
data/lib/resqued/logging.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "mono_logger"
|
2
2
|
|
3
3
|
module Resqued
|
4
4
|
# Mixin for any class that wants to write messages to the log file.
|
@@ -18,27 +18,34 @@ module Resqued
|
|
18
18
|
|
19
19
|
class ResquedLogFormatter < ::Logger::Formatter
|
20
20
|
def call(severity, time, progname, msg)
|
21
|
-
"[%s#%6d] %5s %s -- %s\n"
|
21
|
+
sprintf "[%s#%6d] %5s %s -- %s\n",
|
22
|
+
format_datetime(time),
|
23
|
+
$$,
|
24
|
+
severity,
|
25
|
+
progname,
|
26
|
+
msg2str(msg)
|
22
27
|
end
|
23
28
|
end
|
24
29
|
|
25
30
|
# Private: Lets our logger reopen its logfile without monologger EVEN KNOWING.
|
26
31
|
class ResquedLoggingIOWrapper
|
32
|
+
# rubocop: disable Style/MethodMissingSuper
|
27
33
|
def method_missing(*args)
|
28
34
|
::Resqued::Logging.logging_io.send(*args)
|
29
35
|
end
|
36
|
+
# rubocop: enable Style/MethodMissingSuper
|
30
37
|
|
31
|
-
def
|
32
|
-
::Resqued::Logging.logging_io.respond_to?(
|
38
|
+
def respond_to_missing?(method, *)
|
39
|
+
::Resqued::Logging.logging_io.respond_to?(method)
|
33
40
|
end
|
34
41
|
end
|
35
42
|
|
36
43
|
# Private: Get an IO to write log messages to.
|
37
44
|
def logging_io
|
38
|
-
@logging_io = nil if @logging_io
|
45
|
+
@logging_io = nil if @logging_io&.closed?
|
39
46
|
@logging_io ||=
|
40
47
|
if path = Resqued::Logging.log_file
|
41
|
-
File.open(path,
|
48
|
+
File.open(path, "a").tap do |f|
|
42
49
|
f.sync = true
|
43
50
|
f.close_on_exec = true
|
44
51
|
# Make sure we're not holding onto a stale filehandle.
|
@@ -60,13 +67,13 @@ module Resqued
|
|
60
67
|
|
61
68
|
# Public.
|
62
69
|
def log_file=(path)
|
63
|
-
ENV[
|
70
|
+
ENV["RESQUED_LOGFILE"] = File.expand_path(path)
|
64
71
|
close_log
|
65
72
|
end
|
66
73
|
|
67
74
|
# Public.
|
68
75
|
def log_file
|
69
|
-
ENV[
|
76
|
+
ENV["RESQUED_LOGFILE"]
|
70
77
|
end
|
71
78
|
end
|
72
79
|
|
data/lib/resqued/master.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
1
|
+
require "resqued/backoff"
|
2
|
+
require "resqued/exec_on_hup"
|
3
|
+
require "resqued/listener_pool"
|
4
|
+
require "resqued/logging"
|
5
|
+
require "resqued/master_state"
|
6
|
+
require "resqued/pidfile"
|
7
|
+
require "resqued/procline_version"
|
8
|
+
require "resqued/sleepy"
|
7
9
|
|
8
10
|
module Resqued
|
9
11
|
# The master process.
|
@@ -16,19 +18,17 @@ module Resqued
|
|
16
18
|
include Resqued::ProclineVersion
|
17
19
|
include Resqued::Sleepy
|
18
20
|
|
19
|
-
def initialize(options)
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@
|
23
|
-
@fast_exit = options.fetch(:fast_exit) { false }
|
21
|
+
def initialize(state, options = {})
|
22
|
+
@state = state
|
23
|
+
@status_pipe = options.fetch(:status_pipe, nil)
|
24
|
+
@listeners = ListenerPool.new(state)
|
24
25
|
@listener_backoff = Backoff.new
|
25
|
-
@listeners_created = 0
|
26
26
|
end
|
27
27
|
|
28
28
|
# Public: Starts the master process.
|
29
29
|
def run(ready_pipe = nil)
|
30
30
|
report_unexpected_exits
|
31
|
-
with_pidfile(@pidfile) do
|
31
|
+
with_pidfile(@state.pidfile) do
|
32
32
|
write_procline
|
33
33
|
install_signal_handlers
|
34
34
|
if ready_pipe
|
@@ -42,32 +42,39 @@ module Resqued
|
|
42
42
|
|
43
43
|
# Private: dat main loop.
|
44
44
|
def go_ham
|
45
|
+
# If we're resuming, we'll want to recycle the existing listener now.
|
46
|
+
prepare_new_listener
|
47
|
+
|
45
48
|
loop do
|
46
49
|
read_listeners
|
47
50
|
reap_all_listeners(Process::WNOHANG)
|
48
|
-
start_listener unless @paused
|
51
|
+
start_listener unless @state.paused
|
49
52
|
case signal = SIGNAL_QUEUE.shift
|
50
53
|
when nil
|
51
54
|
yawn(@listener_backoff.how_long? || 30.0)
|
52
55
|
when :INFO
|
53
56
|
dump_object_counts
|
54
57
|
when :HUP
|
58
|
+
if @state.exec_on_hup
|
59
|
+
log "Execing a new master"
|
60
|
+
ExecOnHUP.exec!(@state)
|
61
|
+
end
|
55
62
|
reopen_logs
|
56
63
|
log "Restarting listener with new configuration and application."
|
57
64
|
prepare_new_listener
|
58
65
|
when :USR2
|
59
66
|
log "Pause job processing"
|
60
|
-
@paused = true
|
61
|
-
kill_listener(:QUIT, @
|
62
|
-
@
|
67
|
+
@state.paused = true
|
68
|
+
kill_listener(:QUIT, @listeners.current)
|
69
|
+
@listeners.clear_current!
|
63
70
|
when :CONT
|
64
71
|
log "Resume job processing"
|
65
|
-
@paused = false
|
72
|
+
@state.paused = false
|
66
73
|
kill_all_listeners(:CONT)
|
67
74
|
when :INT, :TERM, :QUIT
|
68
75
|
log "Shutting down..."
|
69
76
|
kill_all_listeners(signal)
|
70
|
-
wait_for_workers unless @fast_exit
|
77
|
+
wait_for_workers unless @state.fast_exit
|
71
78
|
break
|
72
79
|
end
|
73
80
|
end
|
@@ -85,14 +92,14 @@ module Resqued
|
|
85
92
|
end
|
86
93
|
top = 10
|
87
94
|
log "#{total} objects. top #{top}:"
|
88
|
-
counts.sort_by { |
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
log " #{count} #{name}#{diff}"
|
95
|
+
counts.sort_by { |_, count| -count }.each_with_index do |(name, count), i|
|
96
|
+
next unless i < top
|
97
|
+
|
98
|
+
diff = ""
|
99
|
+
if last = @last_counts && @last_counts[name]
|
100
|
+
diff = sprintf(" (%+d)", (count - last))
|
95
101
|
end
|
102
|
+
log " #{count} #{name}#{diff}"
|
96
103
|
end
|
97
104
|
@last_counts = counts
|
98
105
|
log GC.stat.inspect
|
@@ -100,59 +107,45 @@ module Resqued
|
|
100
107
|
log "Error while counting objects: #{e}"
|
101
108
|
end
|
102
109
|
|
103
|
-
# Private: Map listener pids to ListenerProxy objects.
|
104
|
-
def listener_pids
|
105
|
-
@listener_pids ||= {}
|
106
|
-
end
|
107
|
-
|
108
|
-
# Private: All the ListenerProxy objects.
|
109
|
-
def all_listeners
|
110
|
-
listener_pids.values
|
111
|
-
end
|
112
|
-
|
113
110
|
def start_listener
|
114
|
-
return if @
|
115
|
-
|
116
|
-
@
|
117
|
-
listener_status
|
111
|
+
return if @listeners.current || @listener_backoff.wait?
|
112
|
+
|
113
|
+
listener = @listeners.start!
|
114
|
+
listener_status listener, "start"
|
118
115
|
@listener_backoff.started
|
119
|
-
listener_pids[@current_listener.pid] = @current_listener
|
120
116
|
write_procline
|
121
117
|
end
|
122
118
|
|
123
|
-
def next_listener_id
|
124
|
-
@listeners_created += 1
|
125
|
-
end
|
126
|
-
|
127
119
|
def read_listeners
|
128
|
-
|
129
|
-
l.read_worker_status(:
|
120
|
+
@listeners.each do |l|
|
121
|
+
l.read_worker_status(on_activity: self)
|
130
122
|
end
|
131
123
|
end
|
132
124
|
|
133
125
|
# Listener message: A worker just started working.
|
134
126
|
def worker_started(pid)
|
135
|
-
worker_status(pid,
|
127
|
+
worker_status(pid, "start")
|
136
128
|
end
|
137
129
|
|
138
130
|
# Listener message: A worker just stopped working.
|
139
131
|
#
|
140
132
|
# Forwards the message to the other listeners.
|
141
133
|
def worker_finished(pid)
|
142
|
-
worker_status(pid,
|
143
|
-
|
134
|
+
worker_status(pid, "stop")
|
135
|
+
@listeners.each do |other|
|
144
136
|
other.worker_finished(pid)
|
145
137
|
end
|
146
138
|
end
|
147
139
|
|
148
|
-
# Listener message: A listener finished booting, and is ready to
|
140
|
+
# Listener message: A listener finished booting, and is ready to
|
141
|
+
# start workers.
|
149
142
|
#
|
150
143
|
# Promotes a booting listener to be the current listener.
|
151
144
|
def listener_running(listener)
|
152
|
-
listener_status(listener,
|
153
|
-
if listener == @
|
154
|
-
kill_listener(:QUIT, @
|
155
|
-
@
|
145
|
+
listener_status(listener, "ready")
|
146
|
+
if listener == @listeners.current
|
147
|
+
kill_listener(:QUIT, @listeners.last_good)
|
148
|
+
@listeners.clear_last_good!
|
156
149
|
else
|
157
150
|
# This listener didn't receive the last SIGQUIT we sent.
|
158
151
|
# (It was probably sent before the listener had set up its traps.)
|
@@ -165,23 +158,26 @@ module Resqued
|
|
165
158
|
#
|
166
159
|
# The old one will be killed when the new one is ready for workers.
|
167
160
|
def prepare_new_listener
|
168
|
-
if @
|
169
|
-
# The
|
170
|
-
#
|
171
|
-
|
161
|
+
if @listeners.last_good
|
162
|
+
# The last good listener is still running because we got another
|
163
|
+
# HUP before the new listener finished booting.
|
164
|
+
# Keep the last_good_listener (where all the workers are) and
|
165
|
+
# kill the booting current_listener. We'll start a new one.
|
166
|
+
kill_listener(:QUIT, @listeners.current)
|
167
|
+
# Indicate to `start_listener` that it should start a new
|
168
|
+
# listener.
|
169
|
+
@listeners.clear_current!
|
172
170
|
else
|
173
|
-
@
|
171
|
+
@listeners.cycle_current
|
174
172
|
end
|
175
|
-
# Indicate to `start_listener` that it should start a new listener.
|
176
|
-
@current_listener = nil
|
177
173
|
end
|
178
174
|
|
179
175
|
def kill_listener(signal, listener)
|
180
|
-
listener
|
176
|
+
listener&.kill(signal)
|
181
177
|
end
|
182
178
|
|
183
179
|
def kill_all_listeners(signal)
|
184
|
-
|
180
|
+
@listeners.each do |l|
|
185
181
|
l.kill(signal)
|
186
182
|
end
|
187
183
|
end
|
@@ -191,80 +187,80 @@ module Resqued
|
|
191
187
|
end
|
192
188
|
|
193
189
|
def reap_all_listeners(waitpid_flags = 0)
|
194
|
-
|
195
|
-
|
196
|
-
|
190
|
+
until @listeners.empty?
|
191
|
+
begin
|
192
|
+
lpid, status = Process.waitpid2(-1, waitpid_flags)
|
193
|
+
return unless lpid
|
194
|
+
|
197
195
|
log "Listener exited #{status}"
|
198
|
-
|
196
|
+
|
197
|
+
if @listeners.current_pid == lpid
|
199
198
|
@listener_backoff.died
|
200
|
-
@
|
199
|
+
@listeners.clear_current!
|
200
|
+
end
|
201
|
+
|
202
|
+
if @listeners.last_good_pid == lpid
|
203
|
+
@listeners.clear_last_good!
|
201
204
|
end
|
202
205
|
|
203
|
-
if
|
204
|
-
|
206
|
+
if dead_listener = @listeners.delete(lpid)
|
207
|
+
listener_status dead_listener, "stop"
|
208
|
+
dead_listener.dispose
|
205
209
|
end
|
206
|
-
|
207
|
-
listener_status dead_listener, 'stop'
|
208
|
-
dead_listener.dispose
|
210
|
+
|
209
211
|
write_procline
|
210
|
-
|
212
|
+
rescue Errno::ECHILD
|
211
213
|
return
|
212
214
|
end
|
213
|
-
|
214
|
-
return
|
215
|
-
end while true
|
215
|
+
end
|
216
216
|
end
|
217
217
|
|
218
|
-
SIGNALS = [
|
219
|
-
OPTIONAL_SIGNALS = [
|
220
|
-
OTHER_SIGNALS = [:CHLD,
|
218
|
+
SIGNALS = [:HUP, :INT, :USR2, :CONT, :TERM, :QUIT].freeze
|
219
|
+
OPTIONAL_SIGNALS = [:INFO].freeze
|
220
|
+
OTHER_SIGNALS = [:CHLD, "EXIT"].freeze
|
221
221
|
TRAPS = SIGNALS + OPTIONAL_SIGNALS + OTHER_SIGNALS
|
222
222
|
|
223
|
-
SIGNAL_QUEUE = []
|
223
|
+
SIGNAL_QUEUE = [] # rubocop: disable Style/MutableConstant
|
224
224
|
|
225
225
|
def install_signal_handlers
|
226
226
|
trap(:CHLD) { awake }
|
227
|
-
SIGNALS.each { |signal| trap(signal) { SIGNAL_QUEUE << signal
|
228
|
-
OPTIONAL_SIGNALS.each { |signal| trap(signal) { SIGNAL_QUEUE << signal
|
227
|
+
SIGNALS.each { |signal| trap(signal) { SIGNAL_QUEUE << signal; awake } }
|
228
|
+
OPTIONAL_SIGNALS.each { |signal| trap(signal) { SIGNAL_QUEUE << signal; awake } rescue nil }
|
229
229
|
end
|
230
230
|
|
231
231
|
def report_unexpected_exits
|
232
|
-
trap(
|
232
|
+
trap("EXIT") do
|
233
233
|
log("EXIT #{$!.inspect}")
|
234
|
-
|
235
|
-
|
236
|
-
log(line)
|
237
|
-
end
|
234
|
+
$!&.backtrace&.each do |line|
|
235
|
+
log(line)
|
238
236
|
end
|
239
237
|
end
|
240
238
|
end
|
241
239
|
|
242
240
|
def no_more_unexpected_exits
|
243
|
-
trap(
|
241
|
+
trap("EXIT", "DEFAULT")
|
244
242
|
end
|
245
243
|
|
246
244
|
def yawn(duration)
|
247
|
-
super(duration,
|
245
|
+
super(duration, @listeners.map { |l| l.read_pipe })
|
248
246
|
end
|
249
247
|
|
250
248
|
def write_procline
|
251
|
-
$0 = "#{procline_version} master [gen #{@listeners_created}] [#{
|
249
|
+
$0 = "#{procline_version} master [gen #{@state.listeners_created}] [#{@listeners.size} running] #{ARGV.join(' ')}"
|
252
250
|
end
|
253
251
|
|
254
252
|
def listener_status(listener, status)
|
255
|
-
if listener
|
256
|
-
status_message(
|
253
|
+
if listener&.pid
|
254
|
+
status_message("listener", listener.pid, status)
|
257
255
|
end
|
258
256
|
end
|
259
257
|
|
260
258
|
def worker_status(pid, status)
|
261
|
-
status_message(
|
259
|
+
status_message("worker", pid, status)
|
262
260
|
end
|
263
261
|
|
264
262
|
def status_message(type, pid, status)
|
265
|
-
|
266
|
-
@status_pipe.write("#{type},#{pid},#{status}\n")
|
267
|
-
end
|
263
|
+
@status_pipe&.write("#{type},#{pid},#{status}\n")
|
268
264
|
end
|
269
265
|
end
|
270
266
|
end
|