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.
@@ -63,172 +63,39 @@ class DaemonController
63
63
  class ConnectError < Error
64
64
  end
65
65
 
66
- # Internal, not publicly thrown.
67
- class DaemonizationTimeout < TimeoutError
68
- attr_reader :output
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
- # === Mandatory options
79
- #
80
- # [:identifier]
81
- # A human-readable, unique name for this daemon, e.g. "Sphinx search server".
82
- # This identifier will be used in some error messages. On some platforms, it will
83
- # be used for concurrency control: on such platforms, no two DaemonController
84
- # objects will operate on the same identifier on the same time.
85
- #
86
- # [:start_command]
87
- # The command to start the daemon. This must be a a String, e.g.
88
- # "mongrel_rails start -e production", or a Proc which returns a String.
89
- #
90
- # If the value is a Proc, and the +before_start+ option is given too, then
91
- # the +start_command+ Proc is guaranteed to be called after the +before_start+
92
- # Proc is called.
93
- #
94
- # [:ping_command]
95
- # The ping command is used to check whether the daemon can be connected to.
96
- # It is also used to ensure that #start only returns when the daemon can be
97
- # connected to.
98
- #
99
- # The value may be a command string. This command must exit with an exit code of
100
- # 0 if the daemon can be successfully connected to, or exit with a non-0 exit
101
- # code on failure.
102
- #
103
- # The value may also be an Array which specifies the socket address of the daemon.
104
- # It must be in one of the following forms:
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
- Timeout.timeout(@stop_timeout, Timeout::Error) do
314
- kill_daemon
315
- wait_until do
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
- raise AlreadyStarted, "Daemon '#{@identifier}' is already started"
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
- started = false
377
- before_start
378
- Timeout.timeout(@start_timeout, Timeout::Error) do
379
- spawn_daemon
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
- # We wait until the PID file is available and until
383
- # the daemon responds to pings, but we wait no longer
384
- # than @start_timeout seconds in total (including daemon
385
- # spawn time).
386
- # Furthermore, if the log file hasn't changed for
387
- # @log_file_activity_timeout seconds, and the PID file
388
- # still isn't available or the daemon still doesn't
389
- # respond to pings, then assume that the daemon has
390
- # terminated with an error.
391
- wait_until do
392
- if log_file_has_changed?
393
- record_activity
394
- elsif no_activity?(@log_file_activity_timeout)
395
- raise Timeout::Error, "Log file inactivity"
396
- end
397
- pid_file_available?
398
- end
399
- wait_until(@ping_interval) do
400
- if log_file_has_changed?
401
- record_activity
402
- elsif no_activity?(@log_file_activity_timeout)
403
- raise Timeout::Error, "Log file inactivity"
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
- started = run_ping_command
408
- end
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
- result = if e.is_a?(DaemonizationTimeout)
416
- :daemonization_timeout
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
- :start_timeout
303
+ raise StartError, concat_spawn_output_and_logs(spawn_result.output, differences_in_log_file)
419
304
  end
420
- end
421
305
 
422
- if !result
423
- raise StartError, concat_spawn_output_and_logs(@spawn_output, differences_in_log_file)
424
- elsif result == :daemonization_timeout
425
- raise StartTimeout, concat_spawn_output_and_logs(@spawn_output, differences_in_log_file, nil, "timed out")
426
- elsif result == :start_timeout
427
- raise StartTimeout, concat_spawn_output_and_logs(@spawn_output, differences_in_log_file, nil, "timed out")
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
- true
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
- @spawn_output = if @start_command.respond_to?(:call)
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
- begin
456
- run_command(@stop_command)
457
- rescue StartError => e
458
- raise StopError, e.message
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 = false)
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("SIGTERM", pid)
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
- begin
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
- false
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 = 0.1)
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 start_timed_out
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 daemonization_timed_out
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(options, identifier, pid_file)
608
- if options[:lock_file]
609
- LockFile.new(File.absolute_path(options[:lock_file]))
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
- run_command_while_capturing_output(command)
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
- run_command_without_capturing_output(command)
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
- def should_capture_output_while_running_command?
624
- if is_std_channel_chardev?(@log_file)
625
- false
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, options)
601
+ Process.spawn(@env || {}, ruby_interpreter, SPAWNER_FILE,
602
+ command, spawn_options)
667
603
  else
668
- Process.spawn(@env, command, options)
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
- interruptable_waitpid(pid)
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
- daemonization_timed_out
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
- output = File.read(tempfile_path).strip
708
- if $?.exitstatus != 0
709
- raise StartError, concat_spawn_output_and_logs(output, differences_in_log_file, $?)
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 run_command_without_capturing_output(command)
722
- options = {
723
- in: "/dev/null",
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
- Process.kill("SIGTERM", pid)
643
+ real_log_file = Pathname.new(@log_file).realpath.to_s
757
644
  rescue SystemCallError
645
+ real_log_file = nil
758
646
  end
759
- begin
760
- Timeout.timeout(5, Timeout::Error) do
761
- interruptable_waitpid(pid)
762
- rescue SystemCallError
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
- # Thread#kill (which is called by timeout.rb) may
908
- # not be able to interrupt Process.waitpid. So here we use a
909
- # special version that's a bit less efficient but is at least
910
- # interruptable.
911
- def interruptable_waitpid(pid)
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
- result
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