daemon_controller 2.0.0 → 3.0.0
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/README.md +77 -75
- data/lib/daemon_controller/version.rb +1 -1
- data/lib/daemon_controller.rb +270 -374
- data/spec/daemon_controller_spec.rb +341 -227
- data/spec/echo_server.rb +9 -1
- data/spec/test_helper.rb +66 -20
- data/spec/unresponsive_daemon.rb +6 -12
- metadata +2 -3
- data/spec/run_echo_server +0 -9
data/lib/daemon_controller.rb
CHANGED
@@ -63,172 +63,39 @@ class DaemonController
|
|
63
63
|
class ConnectError < Error
|
64
64
|
end
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
def initialize(output)
|
71
|
-
super()
|
72
|
-
@output = output
|
73
|
-
end
|
74
|
-
end
|
66
|
+
InternalCommandOkResult = Struct.new(:pid, :output)
|
67
|
+
InternalCommandErrorResult = Struct.new(:pid, :output, :exit_status)
|
68
|
+
InternalCommandTimeoutResult = Struct.new(:pid, :output)
|
75
69
|
|
76
70
|
# Create a new DaemonController object.
|
77
71
|
#
|
78
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
# - [:tcp, host_name, port]
|
106
|
-
# - [:unix, filename]
|
107
|
-
#
|
108
|
-
# The value may also be a Proc, which returns an expression that evaluates to
|
109
|
-
# true (indicating that the daemon can be connected to) or false (failure).
|
110
|
-
# If the Proc raises Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::ETIMEDOUT
|
111
|
-
# Errno::ECONNRESET, Errno::EINVAL or Errno::EADDRNOTAVAIL then that also
|
112
|
-
# means that the daemon cannot be connected to.
|
113
|
-
# <b>NOTE:</b> if the ping command returns an object which responds to
|
114
|
-
# <tt>#close</tt>, then that method will be called on it.
|
115
|
-
# This makes it possible to specify a ping command such as
|
116
|
-
# <tt>lambda { TCPSocket.new('localhost', 1234) }</tt>, without having to worry
|
117
|
-
# about closing it afterwards.
|
118
|
-
# Any exceptions raised by #close are ignored.
|
119
|
-
#
|
120
|
-
# [:pid_file]
|
121
|
-
# The PID file that the daemon will write to. Used to check whether the daemon
|
122
|
-
# is running.
|
123
|
-
#
|
124
|
-
# [:lock_file]
|
125
|
-
# The lock file to use for serializing concurrent daemon management operations.
|
126
|
-
# Defaults to "(filename of PID file).lock".
|
127
|
-
#
|
128
|
-
# [:log_file]
|
129
|
-
# The log file that the daemon will write to. It will be consulted to see
|
130
|
-
# whether the daemon has printed any error messages during startup.
|
131
|
-
#
|
132
|
-
# === Optional options
|
133
|
-
# [:stop_command]
|
134
|
-
# A command to stop the daemon with, e.g. "/etc/rc.d/nginx stop". If no stop
|
135
|
-
# command is given (i.e. +nil+), then DaemonController will stop the daemon
|
136
|
-
# by killing the PID written in the PID file.
|
137
|
-
#
|
138
|
-
# The default value is +nil+.
|
139
|
-
#
|
140
|
-
# [:restart_command]
|
141
|
-
# A command to restart the daemon with, e.g. "/etc/rc.d/nginx restart". If
|
142
|
-
# no restart command is given (i.e. +nil+), then DaemonController will
|
143
|
-
# restart the daemon by calling #stop and #start.
|
144
|
-
#
|
145
|
-
# The default value is +nil+.
|
146
|
-
#
|
147
|
-
# [:before_start]
|
148
|
-
# This may be a Proc. It will be called just before running the start command.
|
149
|
-
# The before_start proc is not subject to the start timeout.
|
150
|
-
#
|
151
|
-
# [:start_timeout]
|
152
|
-
# The maximum amount of time, in seconds, that #start may take to start
|
153
|
-
# the daemon. Since #start also waits until the daemon can be connected to,
|
154
|
-
# that wait time is counted as well. If the daemon does not start in time,
|
155
|
-
# then #start will raise an exception.
|
156
|
-
#
|
157
|
-
# The default value is 15.
|
158
|
-
#
|
159
|
-
# [:stop_timeout]
|
160
|
-
# The maximum amount of time, in seconds, that #stop may take to stop
|
161
|
-
# the daemon. Since #stop also waits until the daemon is no longer running,
|
162
|
-
# that wait time is counted as well. If the daemon does not stop in time,
|
163
|
-
# then #stop will raise an exception.
|
164
|
-
#
|
165
|
-
# The default value is 15.
|
166
|
-
#
|
167
|
-
# [:log_file_activity_timeout]
|
168
|
-
# Once a daemon has gone into the background, it will become difficult to
|
169
|
-
# know for certain whether it is still initializing or whether it has
|
170
|
-
# failed and exited, until it has written its PID file. Suppose that it
|
171
|
-
# failed with an error after daemonizing but before it has written its PID file;
|
172
|
-
# not many system administrators want to wait 15 seconds (the default start
|
173
|
-
# timeout) to be notified of whether the daemon has terminated with an error.
|
174
|
-
#
|
175
|
-
# An alternative way to check whether the daemon has terminated with an error,
|
176
|
-
# is by checking whether its log file has been recently updated. If, after the
|
177
|
-
# daemon has started, the log file hasn't been updated for the amount of seconds
|
178
|
-
# given by the :log_file_activity_timeout option, then the daemon is assumed to
|
179
|
-
# have terminated with an error.
|
180
|
-
#
|
181
|
-
# The default value is 7.
|
182
|
-
#
|
183
|
-
# [:dont_stop_if_pid_file_invalid]
|
184
|
-
# If the :stop_command option is given, then normally daemon_controller will
|
185
|
-
# always execute this command upon calling #stop. But if :dont_stop_if_pid_file_invalid
|
186
|
-
# is given, then daemon_controller will not do that if the PID file does not contain
|
187
|
-
# a valid number.
|
188
|
-
#
|
189
|
-
# The default is false.
|
190
|
-
#
|
191
|
-
# [:daemonize_for_me]
|
192
|
-
# Normally daemon_controller will wait until the daemon has daemonized into the
|
193
|
-
# background, in order to capture any errors that it may print on stdout or
|
194
|
-
# stderr before daemonizing. However, if the daemon doesn't support daemonization
|
195
|
-
# for some reason, then setting this option to true will cause daemon_controller
|
196
|
-
# to do the daemonization for the daemon.
|
197
|
-
#
|
198
|
-
# The default is false.
|
199
|
-
#
|
200
|
-
# [:keep_ios]
|
201
|
-
# Upon spawning the daemon, daemon_controller will normally close all file
|
202
|
-
# descriptors except stdin, stdout and stderr. However if there are any file
|
203
|
-
# descriptors you want to keep open, specify the IO objects here. This must be
|
204
|
-
# an array of IO objects.
|
205
|
-
#
|
206
|
-
# [:env]
|
207
|
-
# This must be a Hash. The hash will contain the environment variables available
|
208
|
-
# to be made available to the daemon. Hash keys must be strings, not symbols.
|
209
|
-
def initialize(options)
|
210
|
-
[:identifier, :start_command, :ping_command, :pid_file, :log_file].each do |option|
|
211
|
-
if !options.has_key?(option)
|
212
|
-
raise ArgumentError, "The ':#{option}' option is mandatory"
|
213
|
-
end
|
214
|
-
end
|
215
|
-
@identifier = options[:identifier]
|
216
|
-
@start_command = options[:start_command]
|
217
|
-
@stop_command = options[:stop_command]
|
218
|
-
@ping_command = options[:ping_command]
|
219
|
-
@restart_command = options[:restart_command]
|
220
|
-
@ping_interval = options[:ping_interval] || 0.1
|
221
|
-
@pid_file = options[:pid_file]
|
222
|
-
@log_file = options[:log_file]
|
223
|
-
@before_start = options[:before_start]
|
224
|
-
@start_timeout = options[:start_timeout] || 15
|
225
|
-
@stop_timeout = options[:stop_timeout] || 15
|
226
|
-
@log_file_activity_timeout = options[:log_file_activity_timeout] || 7
|
227
|
-
@dont_stop_if_pid_file_invalid = options[:dont_stop_if_pid_file_invalid]
|
228
|
-
@daemonize_for_me = options[:daemonize_for_me]
|
229
|
-
@keep_ios = options[:keep_ios] || []
|
230
|
-
@lock_file = determine_lock_file(options, @identifier, @pid_file)
|
231
|
-
@env = options[:env] || {}
|
72
|
+
# See doc/OPTIONS.md for options docs.
|
73
|
+
def initialize(identifier:, start_command:, ping_command:, pid_file:, log_file:,
|
74
|
+
lock_file: nil, stop_command: nil, restart_command: nil, before_start: nil,
|
75
|
+
start_timeout: 30, start_abort_timeout: 10, stop_timeout: 30,
|
76
|
+
log_file_activity_timeout: 10, ping_interval: 0.1, stop_graceful_signal: "TERM", dont_stop_if_pid_file_invalid: false,
|
77
|
+
daemonize_for_me: false, keep_ios: nil, env: nil, logger: nil)
|
78
|
+
@identifier = identifier
|
79
|
+
@start_command = start_command
|
80
|
+
@ping_command = ping_command
|
81
|
+
@pid_file = pid_file
|
82
|
+
@log_file = log_file
|
83
|
+
|
84
|
+
@lock_file = determine_lock_file(lock_file, identifier, pid_file)
|
85
|
+
@stop_command = stop_command
|
86
|
+
@restart_command = restart_command
|
87
|
+
@before_start = before_start
|
88
|
+
@start_timeout = start_timeout
|
89
|
+
@start_abort_timeout = start_abort_timeout
|
90
|
+
@stop_timeout = stop_timeout
|
91
|
+
@log_file_activity_timeout = log_file_activity_timeout
|
92
|
+
@ping_interval = ping_interval
|
93
|
+
@stop_graceful_signal = stop_graceful_signal
|
94
|
+
@dont_stop_if_pid_file_invalid = dont_stop_if_pid_file_invalid
|
95
|
+
@daemonize_for_me = daemonize_for_me
|
96
|
+
@keep_ios = keep_ios
|
97
|
+
@env = env
|
98
|
+
@logger = logger
|
232
99
|
end
|
233
100
|
|
234
101
|
# Start the daemon and wait until it can be pinged.
|
@@ -310,15 +177,17 @@ class DaemonController
|
|
310
177
|
# - StopTimeout - the daemon didn't stop in time.
|
311
178
|
def stop
|
312
179
|
@lock_file.exclusive_lock do
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
!daemon_is_running?
|
180
|
+
timeoutable(@stop_timeout) do
|
181
|
+
allow_timeout do
|
182
|
+
kill_daemon
|
183
|
+
wait_until { !daemon_is_running? }
|
317
184
|
end
|
318
185
|
end
|
319
|
-
rescue Timeout::Error
|
320
|
-
raise StopTimeout, "Daemon '#{@identifier}' did not exit in time"
|
321
186
|
end
|
187
|
+
rescue Timeout::Error
|
188
|
+
kill_daemon_with_signal(force: true)
|
189
|
+
wait_until { !daemon_is_running? }
|
190
|
+
raise StopTimeout, "Daemon '#{@identifier}' did not exit in time (force killed)"
|
322
191
|
end
|
323
192
|
|
324
193
|
# Restarts the daemon. Uses the restart_command if provided, otherwise
|
@@ -367,66 +236,86 @@ class DaemonController
|
|
367
236
|
private
|
368
237
|
|
369
238
|
def start_without_locking
|
370
|
-
if daemon_is_running?
|
371
|
-
|
372
|
-
end
|
239
|
+
raise AlreadyStarted, "Daemon '#{@identifier}' is already started" if daemon_is_running?
|
240
|
+
|
373
241
|
save_log_file_information
|
374
242
|
delete_pid_file
|
243
|
+
spawn_result = nil
|
244
|
+
|
375
245
|
begin
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
246
|
+
_, remaining_time = timeoutable(@start_timeout) do
|
247
|
+
allow_timeout { before_start }
|
248
|
+
spawn_result = allow_timeout { spawn_daemon }
|
249
|
+
daemon_spawned
|
380
250
|
record_activity
|
381
251
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
252
|
+
if spawn_result.is_a?(InternalCommandOkResult)
|
253
|
+
allow_timeout do
|
254
|
+
# We wait until the PID file is available and until
|
255
|
+
# the daemon responds to pings, but we wait no longer
|
256
|
+
# than @start_timeout seconds in total (including daemon
|
257
|
+
# spawn time).
|
258
|
+
# Furthermore, if the log file hasn't changed for
|
259
|
+
# @log_file_activity_timeout seconds, and the PID file
|
260
|
+
# still isn't available or the daemon still doesn't
|
261
|
+
# respond to pings, then assume that the daemon has
|
262
|
+
# terminated with an error.
|
263
|
+
wait_until do
|
264
|
+
if log_file_has_changed?
|
265
|
+
record_activity
|
266
|
+
elsif no_activity?(@log_file_activity_timeout)
|
267
|
+
raise Timeout::Error, "Log file inactivity"
|
268
|
+
end
|
269
|
+
pid_file_available?
|
270
|
+
end
|
271
|
+
wait_until(sleep_interval: @ping_interval) do
|
272
|
+
if log_file_has_changed?
|
273
|
+
record_activity
|
274
|
+
elsif no_activity?(@log_file_activity_timeout)
|
275
|
+
raise Timeout::Error, "Log file inactivity"
|
276
|
+
end
|
277
|
+
run_ping_command || !daemon_is_running?
|
278
|
+
end
|
404
279
|
end
|
405
|
-
run_ping_command || !daemon_is_running?
|
406
280
|
end
|
407
|
-
|
408
|
-
|
409
|
-
result = started
|
410
|
-
rescue DaemonizationTimeout, Timeout::Error => e
|
411
|
-
start_timed_out
|
412
|
-
if pid_file_available?
|
413
|
-
kill_daemon_with_signal(true)
|
281
|
+
|
282
|
+
spawn_result
|
414
283
|
end
|
415
|
-
|
416
|
-
|
284
|
+
rescue Timeout::Error
|
285
|
+
# If we got here then it means either the #before_start timed out (= no PID),
|
286
|
+
# or the code after #spawn_daemon timed out (already daemonized, so use PID file).
|
287
|
+
# #spawn_daemon itself won't trigger Timeout:Error because that's handled as
|
288
|
+
# InternalCommandTimeoutResult.
|
289
|
+
pid = spawn_result ? read_pid_file : nil
|
290
|
+
start_timed_out(pid)
|
291
|
+
debug "Timeout waiting for daemon to be ready, PID #{pid.inspect}"
|
292
|
+
abort_start(pid: pid, is_direct_child: false) if pid
|
293
|
+
raise StartTimeout, concat_spawn_output_and_logs(spawn_result ? spawn_result.output : nil,
|
294
|
+
differences_in_log_file, nil, "timed out")
|
295
|
+
end
|
296
|
+
|
297
|
+
case spawn_result
|
298
|
+
when InternalCommandOkResult
|
299
|
+
success, _ = timeoutable(remaining_time) { allow_timeout { run_ping_command } }
|
300
|
+
if success
|
301
|
+
true
|
417
302
|
else
|
418
|
-
|
303
|
+
raise StartError, concat_spawn_output_and_logs(spawn_result.output, differences_in_log_file)
|
419
304
|
end
|
420
|
-
end
|
421
305
|
|
422
|
-
|
423
|
-
raise StartError, concat_spawn_output_and_logs(
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
306
|
+
when InternalCommandErrorResult
|
307
|
+
raise StartError, concat_spawn_output_and_logs(spawn_result.output,
|
308
|
+
differences_in_log_file, spawn_result.exit_status)
|
309
|
+
|
310
|
+
when InternalCommandTimeoutResult
|
311
|
+
daemonization_timed_out(spawn_result.pid)
|
312
|
+
abort_start(pid: spawn_result.pid, is_direct_child: true)
|
313
|
+
debug "Timeout waiting for daemon to fork, PID #{spawn_result.pid}"
|
314
|
+
raise StartTimeout, concat_spawn_output_and_logs(spawn_result.output,
|
315
|
+
differences_in_log_file, nil, "timed out")
|
316
|
+
|
428
317
|
else
|
429
|
-
|
318
|
+
raise "Bug: unexpected result from #spawn_daemon: #{spawn_result.inspect}"
|
430
319
|
end
|
431
320
|
end
|
432
321
|
|
@@ -437,14 +326,11 @@ class DaemonController
|
|
437
326
|
end
|
438
327
|
|
439
328
|
def spawn_daemon
|
440
|
-
|
329
|
+
if @start_command.respond_to?(:call)
|
441
330
|
run_command(@start_command.call)
|
442
331
|
else
|
443
332
|
run_command(@start_command)
|
444
333
|
end
|
445
|
-
rescue DaemonizationTimeout => e
|
446
|
-
@spawn_output = e.output
|
447
|
-
raise e
|
448
334
|
end
|
449
335
|
|
450
336
|
def kill_daemon
|
@@ -452,39 +338,39 @@ class DaemonController
|
|
452
338
|
if @dont_stop_if_pid_file_invalid && read_pid_file.nil?
|
453
339
|
return
|
454
340
|
end
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
341
|
+
|
342
|
+
result = run_command(@stop_command)
|
343
|
+
case result
|
344
|
+
when InternalCommandOkResult
|
345
|
+
# Success
|
346
|
+
when InternalCommandErrorResult
|
347
|
+
raise StopError, concat_spawn_output_and_logs(result.output, nil, result.exit_status)
|
348
|
+
when InternalCommandTimeoutResult
|
349
|
+
raise StopError, concat_spawn_output_and_logs(result.output, nil, nil, "timed out")
|
350
|
+
else
|
351
|
+
raise "Bug: unexpected result from #run_command: #{result.inspect}"
|
459
352
|
end
|
460
353
|
else
|
461
354
|
kill_daemon_with_signal
|
462
355
|
end
|
463
356
|
end
|
464
357
|
|
465
|
-
def kill_daemon_with_signal(force
|
358
|
+
def kill_daemon_with_signal(force: false)
|
466
359
|
pid = read_pid_file
|
467
360
|
if pid
|
468
361
|
if force
|
469
362
|
Process.kill("SIGKILL", pid)
|
470
363
|
else
|
471
|
-
Process.kill(
|
364
|
+
Process.kill(normalize_signal_name(@stop_graceful_signal), pid)
|
472
365
|
end
|
473
366
|
end
|
474
367
|
rescue Errno::ESRCH, Errno::ENOENT
|
475
368
|
end
|
476
369
|
|
477
370
|
def daemon_is_running?
|
478
|
-
|
479
|
-
pid = read_pid_file
|
480
|
-
rescue Errno::ENOENT
|
481
|
-
# The PID file may not exist, or another thread/process
|
482
|
-
# executing #running? may have just deleted the PID file.
|
483
|
-
# So we catch this error.
|
484
|
-
pid = nil
|
485
|
-
end
|
371
|
+
pid = read_pid_file
|
486
372
|
if pid.nil?
|
487
|
-
|
373
|
+
nil
|
488
374
|
elsif check_pid(pid)
|
489
375
|
true
|
490
376
|
else
|
@@ -522,7 +408,7 @@ class DaemonController
|
|
522
408
|
true
|
523
409
|
end
|
524
410
|
|
525
|
-
def wait_until(sleep_interval
|
411
|
+
def wait_until(sleep_interval: 0.1)
|
526
412
|
until yield
|
527
413
|
sleep(sleep_interval)
|
528
414
|
end
|
@@ -556,11 +442,81 @@ class DaemonController
|
|
556
442
|
end
|
557
443
|
|
558
444
|
# This method does nothing and only serves as a hook for the unit test.
|
559
|
-
def
|
445
|
+
def daemon_spawned
|
560
446
|
end
|
561
447
|
|
562
448
|
# This method does nothing and only serves as a hook for the unit test.
|
563
|
-
def
|
449
|
+
def start_timed_out(pid)
|
450
|
+
end
|
451
|
+
|
452
|
+
# This method does nothing and only serves as a hook for the unit test.
|
453
|
+
def daemonization_timed_out(pid)
|
454
|
+
end
|
455
|
+
|
456
|
+
# Aborts a daemon that we tried to start, but timed out.
|
457
|
+
def abort_start(pid:, is_direct_child:)
|
458
|
+
begin
|
459
|
+
debug "Killing process #{pid}"
|
460
|
+
Process.kill("SIGTERM", pid)
|
461
|
+
rescue SystemCallError
|
462
|
+
end
|
463
|
+
|
464
|
+
begin
|
465
|
+
timeoutable(@start_abort_timeout) do
|
466
|
+
allow_timeout do
|
467
|
+
if is_direct_child
|
468
|
+
begin
|
469
|
+
debug "Waiting directly for process #{pid}"
|
470
|
+
Process.waitpid(pid)
|
471
|
+
rescue SystemCallError
|
472
|
+
end
|
473
|
+
|
474
|
+
# The daemon may have:
|
475
|
+
# 1. Written a PID file before forking. We delete this PID file.
|
476
|
+
# -OR-
|
477
|
+
# 2. It might have forked (and written a PID file) right before
|
478
|
+
# we terminated it. We'll want the fork to stay alive rather
|
479
|
+
# than going through the (complicated) trouble of killing it.
|
480
|
+
# Don't touch the PID file.
|
481
|
+
pid2 = read_pid_file
|
482
|
+
debug "PID file contains #{pid2.inspect}"
|
483
|
+
delete_pid_file if pid == pid2
|
484
|
+
else
|
485
|
+
debug "Waiting until daemon is no longer running"
|
486
|
+
wait_until { !daemon_is_running? }
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
rescue Timeout::Error
|
491
|
+
begin
|
492
|
+
Process.kill("SIGKILL", pid)
|
493
|
+
rescue SystemCallError
|
494
|
+
end
|
495
|
+
|
496
|
+
allow_timeout do
|
497
|
+
if is_direct_child
|
498
|
+
begin
|
499
|
+
debug "Waiting directly for process #{pid}"
|
500
|
+
Process.waitpid(pid)
|
501
|
+
rescue SystemCallError
|
502
|
+
end
|
503
|
+
|
504
|
+
# The daemon may have:
|
505
|
+
# 1. Written a PID file before forking. We delete this PID file.
|
506
|
+
# -OR-
|
507
|
+
# 2. It might have forked (and written a PID file) right before
|
508
|
+
# we terminated it. We'll want the fork to stay alive rather
|
509
|
+
# than going through the (complicated) trouble of killing it.
|
510
|
+
# Don't touch the PID file.
|
511
|
+
pid2 = read_pid_file
|
512
|
+
debug "PID file contains #{pid2.inspect}"
|
513
|
+
delete_pid_file if pid == pid2
|
514
|
+
else
|
515
|
+
debug "Waiting until daemon is no longer running"
|
516
|
+
wait_until { !daemon_is_running? }
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
564
520
|
end
|
565
521
|
|
566
522
|
def save_log_file_information
|
@@ -604,9 +560,9 @@ class DaemonController
|
|
604
560
|
nil
|
605
561
|
end
|
606
562
|
|
607
|
-
def determine_lock_file(
|
608
|
-
if
|
609
|
-
LockFile.new(File.absolute_path(
|
563
|
+
def determine_lock_file(given_lock_file, identifier, pid_file)
|
564
|
+
if given_lock_file
|
565
|
+
LockFile.new(File.absolute_path(given_lock_file))
|
610
566
|
else
|
611
567
|
LockFile.new(File.absolute_path(pid_file + ".lock"))
|
612
568
|
end
|
@@ -614,64 +570,44 @@ class DaemonController
|
|
614
570
|
|
615
571
|
def run_command(command)
|
616
572
|
if should_capture_output_while_running_command?
|
617
|
-
|
573
|
+
# Create tempfile for storing the command's output.
|
574
|
+
tempfile = Tempfile.new("daemon-output")
|
575
|
+
tempfile.chmod(0o666)
|
576
|
+
tempfile_path = tempfile.path
|
577
|
+
tempfile.close
|
578
|
+
|
579
|
+
spawn_options = {
|
580
|
+
in: "/dev/null",
|
581
|
+
out: tempfile_path,
|
582
|
+
err: tempfile_path,
|
583
|
+
close_others: true
|
584
|
+
}
|
618
585
|
else
|
619
|
-
|
586
|
+
spawn_options = {
|
587
|
+
in: "/dev/null",
|
588
|
+
out: :out,
|
589
|
+
err: :err,
|
590
|
+
close_others: true
|
591
|
+
}
|
620
592
|
end
|
621
|
-
end
|
622
593
|
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
else
|
627
|
-
begin
|
628
|
-
real_log_file = Pathname.new(@log_file).realpath.to_s
|
629
|
-
rescue SystemCallError
|
630
|
-
real_log_file = nil
|
631
|
-
end
|
632
|
-
if real_log_file
|
633
|
-
!is_std_channel_chardev?(real_log_file)
|
634
|
-
else
|
635
|
-
true
|
594
|
+
if @keep_ios
|
595
|
+
@keep_ios.each do |io|
|
596
|
+
spawn_options[io] = io
|
636
597
|
end
|
637
598
|
end
|
638
|
-
end
|
639
|
-
|
640
|
-
def is_std_channel_chardev?(path)
|
641
|
-
path == "/dev/stdout" ||
|
642
|
-
path == "/dev/stderr" ||
|
643
|
-
path == "/dev/fd/1" ||
|
644
|
-
path == "/dev/fd/2" ||
|
645
|
-
path =~ %r{\A/proc/([0-9]+|self)/fd/[12]\Z}
|
646
|
-
end
|
647
|
-
|
648
|
-
def run_command_while_capturing_output(command)
|
649
|
-
# Create tempfile for storing the command's output.
|
650
|
-
tempfile = Tempfile.new("daemon-output")
|
651
|
-
tempfile.chmod(0o666)
|
652
|
-
tempfile_path = tempfile.path
|
653
|
-
tempfile.close
|
654
599
|
|
655
|
-
options = {
|
656
|
-
in: "/dev/null",
|
657
|
-
out: tempfile_path,
|
658
|
-
err: tempfile_path,
|
659
|
-
close_others: true
|
660
|
-
}
|
661
|
-
@keep_ios.each do |io|
|
662
|
-
options[io] = io
|
663
|
-
end
|
664
600
|
pid = if @daemonize_for_me
|
665
|
-
Process.spawn(@env, ruby_interpreter, SPAWNER_FILE,
|
666
|
-
command,
|
601
|
+
Process.spawn(@env || {}, ruby_interpreter, SPAWNER_FILE,
|
602
|
+
command, spawn_options)
|
667
603
|
else
|
668
|
-
Process.spawn(@env, command,
|
604
|
+
Process.spawn(@env || {}, command, spawn_options)
|
669
605
|
end
|
670
606
|
|
671
607
|
# run_command might be running in a timeout block (like
|
672
608
|
# in #start_without_locking).
|
673
609
|
begin
|
674
|
-
|
610
|
+
Process.waitpid(pid)
|
675
611
|
rescue Errno::ECHILD
|
676
612
|
# Maybe a background thread or whatever waitpid()'ed
|
677
613
|
# this child process before we had the chance. There's
|
@@ -679,102 +615,51 @@ class DaemonController
|
|
679
615
|
# it started successfully; if it didn't we'll know
|
680
616
|
# that later by checking the PID file and by pinging
|
681
617
|
# it.
|
682
|
-
return
|
618
|
+
return InternalCommandOkResult.new(pid, tempfile_path ? File.read(tempfile_path).strip : nil)
|
683
619
|
rescue Timeout::Error
|
684
|
-
|
685
|
-
|
686
|
-
# If the daemon doesn't fork into the background
|
687
|
-
# in time, then kill it.
|
688
|
-
begin
|
689
|
-
Process.kill("SIGTERM", pid)
|
690
|
-
rescue SystemCallError
|
691
|
-
end
|
692
|
-
begin
|
693
|
-
Timeout.timeout(5, Timeout::Error) do
|
694
|
-
interruptable_waitpid(pid)
|
695
|
-
rescue SystemCallError
|
696
|
-
end
|
697
|
-
rescue Timeout::Error
|
698
|
-
begin
|
699
|
-
Process.kill("SIGKILL", pid)
|
700
|
-
interruptable_waitpid(pid)
|
701
|
-
rescue SystemCallError
|
702
|
-
end
|
703
|
-
end
|
704
|
-
raise DaemonizationTimeout, File.read(tempfile_path).strip
|
620
|
+
return InternalCommandTimeoutResult.new(pid, tempfile_path ? File.read(tempfile_path).strip : nil)
|
705
621
|
end
|
706
622
|
|
707
|
-
|
708
|
-
|
709
|
-
|
623
|
+
child_status = $?
|
624
|
+
output = File.read(tempfile_path).strip if tempfile_path
|
625
|
+
if child_status.success?
|
626
|
+
InternalCommandOkResult.new(pid, output)
|
710
627
|
else
|
711
|
-
output
|
628
|
+
InternalCommandErrorResult.new(pid, output, child_status)
|
712
629
|
end
|
713
630
|
ensure
|
714
631
|
begin
|
715
|
-
File.unlink(tempfile_path)
|
716
|
-
rescue
|
632
|
+
File.unlink(tempfile_path) if tempfile_path
|
633
|
+
rescue SystemCallError
|
717
634
|
nil
|
718
635
|
end
|
719
636
|
end
|
720
637
|
|
721
|
-
def
|
722
|
-
|
723
|
-
|
724
|
-
out: :out,
|
725
|
-
err: :err,
|
726
|
-
close_others: true
|
727
|
-
}
|
728
|
-
@keep_ios.each do |io|
|
729
|
-
options[io] = io
|
730
|
-
end
|
731
|
-
pid = if @daemonize_for_me
|
732
|
-
Process.spawn(@env, ruby_interpreter, SPAWNER_FILE,
|
733
|
-
command, options)
|
638
|
+
def should_capture_output_while_running_command?
|
639
|
+
if is_std_channel_chardev?(@log_file)
|
640
|
+
false
|
734
641
|
else
|
735
|
-
Process.spawn(@env, command, options)
|
736
|
-
end
|
737
|
-
|
738
|
-
# run_command might be running in a timeout block (like
|
739
|
-
# in #start_without_locking).
|
740
|
-
begin
|
741
|
-
interruptable_waitpid(pid)
|
742
|
-
rescue Errno::ECHILD
|
743
|
-
# Maybe a background thread or whatever waitpid()'ed
|
744
|
-
# this child process before we had the chance. There's
|
745
|
-
# no way to obtain the exit status now. Assume that
|
746
|
-
# it started successfully; if it didn't we'll know
|
747
|
-
# that later by checking the PID file and by pinging
|
748
|
-
# it.
|
749
|
-
return
|
750
|
-
rescue Timeout::Error
|
751
|
-
daemonization_timed_out
|
752
|
-
|
753
|
-
# If the daemon doesn't fork into the background
|
754
|
-
# in time, then kill it.
|
755
642
|
begin
|
756
|
-
|
643
|
+
real_log_file = Pathname.new(@log_file).realpath.to_s
|
757
644
|
rescue SystemCallError
|
645
|
+
real_log_file = nil
|
758
646
|
end
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
end
|
764
|
-
rescue Timeout::Error
|
765
|
-
begin
|
766
|
-
Process.kill("SIGKILL", pid)
|
767
|
-
interruptable_waitpid(pid)
|
768
|
-
rescue SystemCallError
|
769
|
-
end
|
647
|
+
if real_log_file
|
648
|
+
!is_std_channel_chardev?(real_log_file)
|
649
|
+
else
|
650
|
+
true
|
770
651
|
end
|
771
|
-
raise DaemonizationTimeout, nil
|
772
|
-
end
|
773
|
-
if $?.exitstatus != 0
|
774
|
-
raise StartError, concat_spawn_output_and_logs(nil, differences_in_log_file, $?)
|
775
652
|
end
|
776
653
|
end
|
777
654
|
|
655
|
+
def is_std_channel_chardev?(path)
|
656
|
+
path == "/dev/stdout" ||
|
657
|
+
path == "/dev/stderr" ||
|
658
|
+
path == "/dev/fd/1" ||
|
659
|
+
path == "/dev/fd/2" ||
|
660
|
+
path =~ %r{\A/proc/([0-9]+|self)/fd/[12]\Z}
|
661
|
+
end
|
662
|
+
|
778
663
|
def run_ping_command
|
779
664
|
if @ping_command.respond_to?(:call)
|
780
665
|
begin
|
@@ -904,17 +789,20 @@ class DaemonController
|
|
904
789
|
) + rb_config["EXEEXT"]
|
905
790
|
end
|
906
791
|
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
result = nil
|
913
|
-
until result
|
914
|
-
result = Process.waitpid(pid, Process::WNOHANG)
|
915
|
-
sleep 0.01 if !result
|
792
|
+
def timeoutable(amount, &block)
|
793
|
+
Thread.handle_interrupt(Timeout::Error => :never) do
|
794
|
+
start_time = monotonic_time
|
795
|
+
result = Timeout.timeout(amount, Timeout::Error, &block)
|
796
|
+
[result, [monotonic_time - start_time, 0].max]
|
916
797
|
end
|
917
|
-
|
798
|
+
end
|
799
|
+
|
800
|
+
def allow_timeout(&block)
|
801
|
+
Thread.handle_interrupt(Timeout::Error => :on_blocking, &block)
|
802
|
+
end
|
803
|
+
|
804
|
+
def monotonic_time
|
805
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
918
806
|
end
|
919
807
|
|
920
808
|
def signal_termination_message(process_status)
|
@@ -925,6 +813,10 @@ class DaemonController
|
|
925
813
|
end
|
926
814
|
end
|
927
815
|
|
816
|
+
def normalize_signal_name(name)
|
817
|
+
name.start_with?("SIG") ? name : "SIG#{name}"
|
818
|
+
end
|
819
|
+
|
928
820
|
def signal_name_for(num)
|
929
821
|
if (name = Signal.list.find { |name, n| n == num }[0])
|
930
822
|
"SIG#{name}"
|
@@ -960,4 +852,8 @@ class DaemonController
|
|
960
852
|
result
|
961
853
|
end
|
962
854
|
end
|
855
|
+
|
856
|
+
def debug(message)
|
857
|
+
@logger.debug(message) if @logger
|
858
|
+
end
|
963
859
|
end
|