serverengine 1.6.4 → 2.0.0pre1
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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/Changelog +11 -0
- data/README.md +31 -3
- data/Rakefile +16 -3
- data/appveyor.yml +11 -5
- data/examples/server.rb +138 -0
- data/examples/spawn_worker_script.rb +38 -0
- data/lib/serverengine/blocking_flag.rb +2 -3
- data/lib/serverengine/command_sender.rb +89 -0
- data/lib/serverengine/config_loader.rb +2 -0
- data/lib/serverengine/daemon.rb +114 -86
- data/lib/serverengine/daemon_logger.rb +3 -139
- data/lib/serverengine/embedded_server.rb +2 -0
- data/lib/serverengine/multi_process_server.rb +28 -7
- data/lib/serverengine/multi_spawn_server.rb +17 -18
- data/lib/serverengine/multi_thread_server.rb +6 -0
- data/lib/serverengine/multi_worker_server.rb +14 -0
- data/lib/serverengine/privilege.rb +57 -0
- data/lib/serverengine/process_manager.rb +66 -48
- data/lib/serverengine/server.rb +45 -11
- data/lib/serverengine/signal_thread.rb +0 -2
- data/lib/serverengine/signals.rb +31 -0
- data/lib/serverengine/socket_manager.rb +3 -5
- data/lib/serverengine/socket_manager_unix.rb +1 -0
- data/lib/serverengine/socket_manager_win.rb +4 -2
- data/lib/serverengine/supervisor.rb +105 -25
- data/lib/serverengine/utils.rb +23 -0
- data/lib/serverengine/version.rb +1 -1
- data/lib/serverengine/worker.rb +10 -7
- data/lib/serverengine.rb +9 -27
- data/serverengine.gemspec +12 -1
- data/spec/daemon_logger_spec.rb +17 -12
- data/spec/daemon_spec.rb +147 -24
- data/spec/multi_process_server_spec.rb +59 -7
- data/spec/server_worker_context.rb +104 -0
- data/spec/signal_thread_spec.rb +61 -56
- data/spec/supervisor_spec.rb +113 -99
- metadata +40 -6
data/lib/serverengine/daemon.rb
CHANGED
@@ -15,9 +15,12 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
|
-
|
18
|
+
require 'serverengine/command_sender'
|
19
|
+
require 'serverengine/config_loader'
|
20
|
+
require 'serverengine/privilege'
|
21
|
+
require 'serverengine/supervisor'
|
19
22
|
|
20
|
-
|
23
|
+
module ServerEngine
|
21
24
|
|
22
25
|
class Daemon
|
23
26
|
include ConfigLoader
|
@@ -47,55 +50,19 @@ module ServerEngine
|
|
47
50
|
@chuser = @config[:chuser]
|
48
51
|
@chgroup = @config[:chgroup]
|
49
52
|
@chumask = @config[:chumask]
|
50
|
-
end
|
51
|
-
|
52
|
-
# server is available when run() is called. It is a Supervisor instance if supervisor is set to true. Otherwise a Server instance.
|
53
|
-
attr_reader :server
|
54
|
-
|
55
|
-
module Signals
|
56
|
-
GRACEFUL_STOP = :TERM
|
57
|
-
IMMEDIATE_STOP = :QUIT
|
58
|
-
GRACEFUL_RESTART = :USR1
|
59
|
-
IMMEDIATE_RESTART = :HUP
|
60
|
-
RELOAD = :USR2
|
61
|
-
DETACH = :INT
|
62
|
-
DUMP = :CONT
|
63
|
-
end
|
64
53
|
|
65
|
-
|
66
|
-
|
67
|
-
|
54
|
+
@pid = nil
|
55
|
+
@command_pipe = @config.fetch(:command_pipe, nil)
|
56
|
+
@command_sender = @config.fetch(:command_sender, ServerEngine.windows? ? "pipe" : "signal")
|
57
|
+
if @command_sender == "pipe"
|
58
|
+
extend ServerEngine::CommandSender::Pipe
|
68
59
|
else
|
69
|
-
|
60
|
+
extend ServerEngine::CommandSender::Signal
|
70
61
|
end
|
71
62
|
end
|
72
63
|
|
73
|
-
|
74
|
-
|
75
|
-
Etc.getgrgid(group.to_i)
|
76
|
-
else
|
77
|
-
Etc.getgrnam(group)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def self.change_privilege(user, group)
|
82
|
-
if user
|
83
|
-
etc_pw = Daemon.get_etc_passwd(user)
|
84
|
-
user_groups = [etc_pw.gid]
|
85
|
-
Etc.setgrent
|
86
|
-
Etc.group { |gr| user_groups << gr.gid if gr.mem.include?(etc_pw.name) } # emulate 'id -G'
|
87
|
-
|
88
|
-
Process.groups = Process.groups | user_groups
|
89
|
-
Process::UID.change_privilege(etc_pw.uid)
|
90
|
-
end
|
91
|
-
|
92
|
-
if group
|
93
|
-
etc_group = Daemon.get_etc_group(group)
|
94
|
-
Process::GID.change_privilege(etc_group.gid)
|
95
|
-
end
|
96
|
-
|
97
|
-
nil
|
98
|
-
end
|
64
|
+
# server is available when run() is called. It is a Supervisor instance if supervisor is set to true. Otherwise a Server instance.
|
65
|
+
attr_reader :server
|
99
66
|
|
100
67
|
def run
|
101
68
|
begin
|
@@ -113,7 +80,7 @@ module ServerEngine
|
|
113
80
|
def server_main
|
114
81
|
$0 = @daemon_process_name if @daemon_process_name
|
115
82
|
|
116
|
-
|
83
|
+
Privilege.change(@chuser, @chgroup)
|
117
84
|
File.umask(@chumask) if @chumask
|
118
85
|
|
119
86
|
s = create_server(create_logger)
|
@@ -128,72 +95,133 @@ module ServerEngine
|
|
128
95
|
end
|
129
96
|
|
130
97
|
def main
|
131
|
-
|
98
|
+
if @daemonize
|
99
|
+
if @command_sender == "pipe"
|
100
|
+
inpipe, @command_sender_pipe = IO.pipe
|
101
|
+
@command_sender_pipe.sync = true
|
102
|
+
@command_sender_pipe.binmode
|
103
|
+
end
|
104
|
+
|
105
|
+
if ServerEngine.windows?
|
106
|
+
ret = daemonize_with_spawn(inpipe)
|
107
|
+
else
|
108
|
+
ret = daemonize_with_double_fork(inpipe)
|
109
|
+
end
|
110
|
+
|
111
|
+
if @command_sender == "pipe"
|
112
|
+
inpipe.close
|
113
|
+
end
|
114
|
+
return ret
|
115
|
+
else
|
116
|
+
@pid = Process.pid
|
132
117
|
s = create_server(create_logger)
|
133
118
|
s.install_signal_handlers
|
134
|
-
|
119
|
+
begin
|
120
|
+
s.main
|
121
|
+
rescue SystemExit => e
|
122
|
+
return e.status
|
123
|
+
end
|
135
124
|
return 0
|
136
125
|
end
|
126
|
+
end
|
137
127
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
128
|
+
def daemonize_with_spawn(inpipe)
|
129
|
+
windows_daemon_cmdline = config[:windows_daemon_cmdline]
|
130
|
+
config = {}
|
131
|
+
if @command_sender == "pipe"
|
132
|
+
config[:in] = inpipe
|
133
|
+
end
|
134
|
+
@pid = Process.spawn(*Array(windows_daemon_cmdline), config)
|
145
135
|
|
146
|
-
|
147
|
-
|
148
|
-
rpipe.close
|
136
|
+
write_pid_file
|
137
|
+
end
|
149
138
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
wpipe.write "#{Process.pid}\n"
|
139
|
+
def daemonize_with_double_fork(inpipe)
|
140
|
+
rpipe, wpipe = IO.pipe
|
141
|
+
wpipe.sync = true
|
154
142
|
|
155
|
-
|
156
|
-
|
143
|
+
Process.fork do
|
144
|
+
begin
|
145
|
+
rpipe.close
|
146
|
+
if @command_sender == "pipe"
|
147
|
+
@command_sender_pipe.close
|
148
|
+
end
|
149
|
+
|
150
|
+
Process.setsid
|
151
|
+
Process.fork do
|
152
|
+
$0 = @daemon_process_name if @daemon_process_name
|
153
|
+
wpipe.write "#{Process.pid}\n"
|
157
154
|
|
158
|
-
|
155
|
+
Privilege.change(@chuser, @chgroup)
|
156
|
+
File.umask(@chumask) if @chumask
|
159
157
|
|
160
|
-
|
161
|
-
|
162
|
-
|
158
|
+
s = create_server(create_logger)
|
159
|
+
if @command_sender == "pipe"
|
160
|
+
s.instance_variable_set(:@command_pipe, inpipe)
|
161
|
+
end
|
163
162
|
|
164
|
-
|
163
|
+
STDIN.reopen(File::NULL)
|
164
|
+
STDOUT.reopen(File::NULL, "wb")
|
165
|
+
STDERR.reopen(File::NULL, "wb")
|
165
166
|
|
166
|
-
|
167
|
-
wpipe.close
|
167
|
+
s.install_signal_handlers
|
168
168
|
|
169
|
+
wpipe.write "\n"
|
170
|
+
wpipe.close
|
171
|
+
|
172
|
+
begin
|
169
173
|
s.main
|
174
|
+
rescue SystemExit => e
|
175
|
+
exit e.status
|
170
176
|
end
|
171
|
-
|
172
|
-
exit 0
|
173
|
-
ensure
|
174
|
-
exit! @daemonize_error_exit_code
|
175
177
|
end
|
178
|
+
|
179
|
+
exit 0
|
180
|
+
ensure
|
181
|
+
exit! @daemonize_error_exit_code
|
176
182
|
end
|
183
|
+
end
|
177
184
|
|
178
|
-
|
185
|
+
wpipe.close
|
186
|
+
@pid = rpipe.gets.to_i
|
187
|
+
data = rpipe.read
|
188
|
+
rpipe.close
|
179
189
|
|
180
|
-
|
190
|
+
if data != "\n"
|
191
|
+
return @daemonize_error_exit_code
|
181
192
|
end
|
182
193
|
|
194
|
+
write_pid_file
|
195
|
+
|
196
|
+
return 0
|
197
|
+
end
|
198
|
+
|
199
|
+
def write_pid_file
|
183
200
|
if @pid_path
|
184
201
|
File.open(@pid_path, "w") {|f|
|
185
|
-
f.write "#{pid}\n"
|
202
|
+
f.write "#{@pid}\n"
|
186
203
|
}
|
187
204
|
end
|
205
|
+
end
|
188
206
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
return @daemonize_error_exit_code
|
193
|
-
end
|
194
|
-
end
|
207
|
+
def stop(graceful)
|
208
|
+
_stop(graceful)
|
209
|
+
end
|
195
210
|
|
196
|
-
|
211
|
+
def restart(graceful)
|
212
|
+
_restart(graceful)
|
213
|
+
end
|
214
|
+
|
215
|
+
def reload
|
216
|
+
_reload
|
217
|
+
end
|
218
|
+
|
219
|
+
def detach
|
220
|
+
_detach
|
221
|
+
end
|
222
|
+
|
223
|
+
def dump
|
224
|
+
_dump
|
197
225
|
end
|
198
226
|
|
199
227
|
private
|
@@ -15,9 +15,9 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
|
-
|
18
|
+
require 'logger'
|
19
19
|
|
20
|
-
|
20
|
+
module ServerEngine
|
21
21
|
|
22
22
|
class ::Logger::LogDevice
|
23
23
|
def reopen!
|
@@ -34,14 +34,6 @@ module ServerEngine
|
|
34
34
|
@rotate_size = config[:log_rotate_size] || 1048576
|
35
35
|
@file_dev = nil
|
36
36
|
|
37
|
-
if RUBY_VERSION < "2.1.0"
|
38
|
-
# Ruby < 2.1.0 has a problem around log rotation with multiprocess:
|
39
|
-
# https://github.com/ruby/ruby/pull/428
|
40
|
-
@logdev_class = MultiprocessFileLogDevice
|
41
|
-
else
|
42
|
-
@logdev_class = LogDevice
|
43
|
-
end
|
44
|
-
|
45
37
|
super(nil)
|
46
38
|
|
47
39
|
self.level = config[:log_level] || 'debug'
|
@@ -62,7 +54,7 @@ module ServerEngine
|
|
62
54
|
elsif !@file_dev || @file_dev.filename != logdev
|
63
55
|
# update path string
|
64
56
|
old_file_dev = @file_dev
|
65
|
-
@file_dev =
|
57
|
+
@file_dev = LogDevice.new(logdev, shift_age: @rotate_age, shift_size: @rotate_size)
|
66
58
|
old_file_dev.close if old_file_dev
|
67
59
|
@logdev = @file_dev
|
68
60
|
end
|
@@ -138,134 +130,6 @@ module ServerEngine
|
|
138
130
|
nil
|
139
131
|
end
|
140
132
|
|
141
|
-
class MultiprocessFileLogDevice
|
142
|
-
def initialize(path, opts={})
|
143
|
-
@shift_age = opts[:shift_age] || 7
|
144
|
-
@shift_size = opts[:shift_size] || 1024*1024
|
145
|
-
@mutex = Mutex.new
|
146
|
-
self.path = path
|
147
|
-
end
|
148
|
-
|
149
|
-
def write(data)
|
150
|
-
# it's hard to remove this synchronize because IO#write raises
|
151
|
-
# Errno::ENOENT if IO#reopen is running concurrently.
|
152
|
-
@mutex.synchronize do
|
153
|
-
unless @file
|
154
|
-
return nil
|
155
|
-
end
|
156
|
-
log_rotate_or_reopen
|
157
|
-
@file.write(data)
|
158
|
-
end
|
159
|
-
rescue Exception => e
|
160
|
-
warn "log writing failed: #{e}"
|
161
|
-
end
|
162
|
-
|
163
|
-
def path=(path)
|
164
|
-
@mutex.synchronize do
|
165
|
-
old_file = @file
|
166
|
-
file = open_logfile(path)
|
167
|
-
begin
|
168
|
-
@file = file
|
169
|
-
@path = path
|
170
|
-
file = old_file
|
171
|
-
ensure
|
172
|
-
file.close if file
|
173
|
-
end
|
174
|
-
end
|
175
|
-
return path
|
176
|
-
end
|
177
|
-
|
178
|
-
def close
|
179
|
-
@mutex.synchronize do
|
180
|
-
@file.close
|
181
|
-
@file = nil
|
182
|
-
end
|
183
|
-
nil
|
184
|
-
end
|
185
|
-
|
186
|
-
def reopen!
|
187
|
-
@mutex.synchronize do
|
188
|
-
if @file
|
189
|
-
@file.reopen(@path, 'a')
|
190
|
-
@file.sync = true
|
191
|
-
end
|
192
|
-
end
|
193
|
-
true
|
194
|
-
end
|
195
|
-
|
196
|
-
# for compatibility with Logger::LogDevice
|
197
|
-
def dev
|
198
|
-
@file
|
199
|
-
end
|
200
|
-
|
201
|
-
# for compatibility with Logger::LogDevice
|
202
|
-
def filename
|
203
|
-
@path
|
204
|
-
end
|
205
|
-
|
206
|
-
private
|
207
|
-
|
208
|
-
def open_logfile(path)
|
209
|
-
return nil unless path
|
210
|
-
file = File.open(path, 'a')
|
211
|
-
file.sync = true
|
212
|
-
return file
|
213
|
-
end
|
214
|
-
|
215
|
-
def log_rotate_or_reopen
|
216
|
-
stat = @file.stat
|
217
|
-
if stat.size <= @shift_size
|
218
|
-
return
|
219
|
-
end
|
220
|
-
|
221
|
-
# inter-process locking
|
222
|
-
retry_limit = 8
|
223
|
-
retry_sleep = 0.1
|
224
|
-
begin
|
225
|
-
# 1) other process is log-rotating now
|
226
|
-
# 2) other process log rotated
|
227
|
-
# 3) no active processes
|
228
|
-
lock = File.open(@path, File::WRONLY | File::APPEND)
|
229
|
-
begin
|
230
|
-
lock.flock(File::LOCK_EX)
|
231
|
-
ino = lock.stat.ino
|
232
|
-
if ino == File.stat(@path).ino and ino == stat.ino
|
233
|
-
# 3)
|
234
|
-
log_rotate
|
235
|
-
else
|
236
|
-
@file.reopen(@path, 'a')
|
237
|
-
@file.sync = true
|
238
|
-
end
|
239
|
-
ensure
|
240
|
-
lock.close
|
241
|
-
end
|
242
|
-
rescue Errno::ENOENT => e
|
243
|
-
raise e if retry_limit <= 0
|
244
|
-
sleep retry_sleep
|
245
|
-
retry_limit -= 1
|
246
|
-
retry_sleep *= 2
|
247
|
-
retry
|
248
|
-
end
|
249
|
-
|
250
|
-
rescue => e
|
251
|
-
warn "log rotation inter-process lock failed: #{e}"
|
252
|
-
end
|
253
|
-
|
254
|
-
def log_rotate
|
255
|
-
(@shift_age-2).downto(0) do |i|
|
256
|
-
old_path = "#{@path}.#{i}"
|
257
|
-
shift_path = "#{@path}.#{i+1}"
|
258
|
-
if FileTest.exist?(old_path)
|
259
|
-
File.rename(old_path, shift_path)
|
260
|
-
end
|
261
|
-
end
|
262
|
-
File.rename(@path, "#{@path}.0")
|
263
|
-
@file.reopen(@path, 'a')
|
264
|
-
@file.sync = true
|
265
|
-
rescue => e
|
266
|
-
warn "log rotation failed: #{e}"
|
267
|
-
end
|
268
|
-
end
|
269
133
|
end
|
270
134
|
|
271
135
|
end
|
@@ -15,14 +15,20 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
|
+
require 'serverengine/signals'
|
19
|
+
require 'serverengine/daemon'
|
20
|
+
require 'serverengine/process_manager'
|
21
|
+
require 'serverengine/multi_worker_server'
|
22
|
+
require 'serverengine/privilege'
|
23
|
+
|
18
24
|
module ServerEngine
|
19
25
|
|
20
26
|
class MultiProcessServer < MultiWorkerServer
|
21
27
|
def initialize(worker_module, load_config_proc={}, &block)
|
22
28
|
@pm = ProcessManager.new(
|
23
29
|
auto_tick: false,
|
24
|
-
graceful_kill_signal:
|
25
|
-
immediate_kill_signal:
|
30
|
+
graceful_kill_signal: Signals::GRACEFUL_STOP,
|
31
|
+
immediate_kill_signal: Signals::IMMEDIATE_STOP,
|
26
32
|
enable_heartbeat: true,
|
27
33
|
auto_heartbeat: true,
|
28
34
|
on_heartbeat_error: Proc.new do
|
@@ -34,6 +40,7 @@ module ServerEngine
|
|
34
40
|
super(worker_module, load_config_proc, &block)
|
35
41
|
|
36
42
|
@worker_process_name = @config[:worker_process_name]
|
43
|
+
@unrecoverable_exit_codes = @config.fetch(:unrecoverable_exit_codes, [])
|
37
44
|
end
|
38
45
|
|
39
46
|
def run
|
@@ -70,7 +77,7 @@ module ServerEngine
|
|
70
77
|
$0 = @worker_process_name % [wid] if @worker_process_name
|
71
78
|
w.install_signal_handlers
|
72
79
|
|
73
|
-
|
80
|
+
Privilege.change(@chuser, @chgroup)
|
74
81
|
File.umask(@chumask) if @chumask
|
75
82
|
|
76
83
|
## recreate the logger created at Server#main
|
@@ -83,7 +90,7 @@ module ServerEngine
|
|
83
90
|
w.after_start
|
84
91
|
end
|
85
92
|
|
86
|
-
return WorkerMonitor.new(w, wid, pmon)
|
93
|
+
return WorkerMonitor.new(w, wid, pmon, unrecoverable_exit_codes: @unrecoverable_exit_codes)
|
87
94
|
end
|
88
95
|
|
89
96
|
def wait_tick
|
@@ -91,12 +98,18 @@ module ServerEngine
|
|
91
98
|
end
|
92
99
|
|
93
100
|
class WorkerMonitor
|
94
|
-
def initialize(worker, wid, pmon)
|
101
|
+
def initialize(worker, wid, pmon, reload_signal = Signals::RELOAD, unrecoverable_exit_codes: [])
|
95
102
|
@worker = worker
|
96
103
|
@wid = wid
|
97
104
|
@pmon = pmon
|
105
|
+
@reload_signal = reload_signal
|
106
|
+
@unrecoverable_exit_codes = unrecoverable_exit_codes
|
107
|
+
@unrecoverable_exit = false
|
108
|
+
@exitstatus = nil
|
98
109
|
end
|
99
110
|
|
111
|
+
attr_reader :exitstatus
|
112
|
+
|
100
113
|
def send_stop(stop_graceful)
|
101
114
|
@stop = true
|
102
115
|
if stop_graceful
|
@@ -108,7 +121,7 @@ module ServerEngine
|
|
108
121
|
end
|
109
122
|
|
110
123
|
def send_reload
|
111
|
-
@pmon.send_signal(
|
124
|
+
@pmon.send_signal(@reload_signal) if @pmon
|
112
125
|
nil
|
113
126
|
end
|
114
127
|
|
@@ -121,13 +134,21 @@ module ServerEngine
|
|
121
134
|
return false unless @pmon
|
122
135
|
|
123
136
|
if stat = @pmon.try_join
|
124
|
-
@worker.logger.info "Worker #{@wid} finished#{@stop ? '' : ' unexpectedly'} with #{
|
137
|
+
@worker.logger.info "Worker #{@wid} finished#{@stop ? '' : ' unexpectedly'} with #{ServerEngine.format_join_status(stat)}"
|
138
|
+
if stat.is_a?(Process::Status) && stat.exited? && @unrecoverable_exit_codes.include?(stat.exitstatus)
|
139
|
+
@unrecoverable_exit = true
|
140
|
+
@exitstatus = stat.exitstatus
|
141
|
+
end
|
125
142
|
@pmon = nil
|
126
143
|
return false
|
127
144
|
else
|
128
145
|
return true
|
129
146
|
end
|
130
147
|
end
|
148
|
+
|
149
|
+
def recoverable?
|
150
|
+
!@unrecoverable_exit
|
151
|
+
end
|
131
152
|
end
|
132
153
|
end
|
133
154
|
|
@@ -15,6 +15,10 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
|
+
require 'serverengine/signals'
|
19
|
+
require 'serverengine/process_manager'
|
20
|
+
require 'serverengine/multi_worker_server'
|
21
|
+
|
18
22
|
module ServerEngine
|
19
23
|
|
20
24
|
class MultiSpawnServer < MultiWorkerServer
|
@@ -22,15 +26,15 @@ module ServerEngine
|
|
22
26
|
if ServerEngine.windows?
|
23
27
|
@pm = ProcessManager.new(
|
24
28
|
auto_tick: false,
|
25
|
-
graceful_kill_signal:
|
29
|
+
graceful_kill_signal: Signals::GRACEFUL_STOP,
|
26
30
|
immediate_kill_signal: false,
|
27
31
|
enable_heartbeat: false,
|
28
32
|
)
|
29
33
|
else
|
30
34
|
@pm = ProcessManager.new(
|
31
35
|
auto_tick: false,
|
32
|
-
graceful_kill_signal:
|
33
|
-
immediate_kill_signal:
|
36
|
+
graceful_kill_signal: Signals::GRACEFUL_STOP,
|
37
|
+
immediate_kill_signal: Signals::IMMEDIATE_STOP,
|
34
38
|
enable_heartbeat: false,
|
35
39
|
)
|
36
40
|
end
|
@@ -38,6 +42,15 @@ module ServerEngine
|
|
38
42
|
super(worker_module, load_config_proc, &block)
|
39
43
|
|
40
44
|
@reload_signal = @config[:worker_reload_signal]
|
45
|
+
@unrecoverable_exit_codes = @config.fetch(:unrecoverable_exit_codes, [])
|
46
|
+
@pm.command_sender = @command_sender
|
47
|
+
end
|
48
|
+
|
49
|
+
def stop(stop_graceful)
|
50
|
+
if @command_sender == "pipe"
|
51
|
+
@pm.command_sender_pipe.write(stop_graceful ? "GRACEFUL_STOP\n" : "IMMEDIATE_STOP\n")
|
52
|
+
end
|
53
|
+
super
|
41
54
|
end
|
42
55
|
|
43
56
|
def run
|
@@ -71,26 +84,12 @@ module ServerEngine
|
|
71
84
|
w.after_start
|
72
85
|
end
|
73
86
|
|
74
|
-
return WorkerMonitor.new(w, wid, pmon, @reload_signal)
|
87
|
+
return MultiProcessServer::WorkerMonitor.new(w, wid, pmon, @reload_signal, unrecoverable_exit_codes: @unrecoverable_exit_codes)
|
75
88
|
end
|
76
89
|
|
77
90
|
def wait_tick
|
78
91
|
@pm.tick(0.5)
|
79
92
|
end
|
80
|
-
|
81
|
-
class WorkerMonitor < MultiProcessServer::WorkerMonitor
|
82
|
-
def initialize(worker, wid, pmon, reload_signal)
|
83
|
-
super(worker, wid, pmon)
|
84
|
-
@reload_signal = reload_signal
|
85
|
-
end
|
86
|
-
|
87
|
-
def send_reload
|
88
|
-
if @reload_signal
|
89
|
-
@pmon.send_signal(@reload_signal) if @pmon
|
90
|
-
end
|
91
|
-
nil
|
92
|
-
end
|
93
|
-
end
|
94
93
|
end
|
95
94
|
|
96
95
|
end
|
@@ -15,6 +15,8 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
|
+
require 'serverengine/multi_worker_server'
|
19
|
+
|
18
20
|
module ServerEngine
|
19
21
|
|
20
22
|
class MultiThreadServer < MultiWorkerServer
|
@@ -68,6 +70,10 @@ module ServerEngine
|
|
68
70
|
def alive?
|
69
71
|
@thread.alive?
|
70
72
|
end
|
73
|
+
|
74
|
+
def recoverable?
|
75
|
+
true
|
76
|
+
end
|
71
77
|
end
|
72
78
|
end
|
73
79
|
|
@@ -15,6 +15,8 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
|
+
require 'serverengine/server'
|
19
|
+
|
18
20
|
module ServerEngine
|
19
21
|
|
20
22
|
class MultiWorkerServer < Server
|
@@ -23,6 +25,8 @@ module ServerEngine
|
|
23
25
|
@last_start_worker_time = 0
|
24
26
|
|
25
27
|
super(worker_module, load_config_proc, &block)
|
28
|
+
|
29
|
+
@stop_immediately_at_unrecoverable_exit = @config.fetch(:stop_immediately_at_unrecoverable_exit, false)
|
26
30
|
end
|
27
31
|
|
28
32
|
def stop(stop_graceful)
|
@@ -99,6 +103,16 @@ module ServerEngine
|
|
99
103
|
# alive
|
100
104
|
num_alive += 1
|
101
105
|
|
106
|
+
elsif m && m.respond_to?(:recoverable?) && !m.recoverable?
|
107
|
+
# exited, with unrecoverable exit code
|
108
|
+
if @stop_immediately_at_unrecoverable_exit
|
109
|
+
stop(true) # graceful stop for workers
|
110
|
+
# @stop is set by Server#stop
|
111
|
+
end
|
112
|
+
# server will stop when all workers exited in this state
|
113
|
+
# the last status will be used for server/supervisor/daemon
|
114
|
+
@stop_status = m.exitstatus if m.exitstatus
|
115
|
+
|
102
116
|
elsif wid < @num_workers
|
103
117
|
# scale up or reboot
|
104
118
|
unless @stop
|