daemon_controller 2.0.0 → 3.0.1

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: {}, 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.
@@ -273,7 +140,7 @@ class DaemonController
273
140
  end
274
141
  if connection.nil?
275
142
  @lock_file.exclusive_lock do
276
- if !daemon_is_running?
143
+ unless daemon_is_running?
277
144
  start_without_locking
278
145
  end
279
146
  connect_exception = nil
@@ -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,54 +326,48 @@ 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
451
337
  if @stop_command
452
- if @dont_stop_if_pid_file_invalid && read_pid_file.nil?
453
- return
454
- end
455
- begin
456
- run_command(@stop_command)
457
- rescue StartError => e
458
- raise StopError, e.message
338
+ return if @dont_stop_if_pid_file_invalid && read_pid_file.nil?
339
+
340
+ result = run_command(@stop_command)
341
+ case result
342
+ when InternalCommandOkResult
343
+ # Success
344
+ when InternalCommandErrorResult
345
+ raise StopError, concat_spawn_output_and_logs(result.output, nil, result.exit_status)
346
+ when InternalCommandTimeoutResult
347
+ raise StopError, concat_spawn_output_and_logs(result.output, nil, nil, "timed out")
348
+ else
349
+ raise "Bug: unexpected result from #run_command: #{result.inspect}"
459
350
  end
460
351
  else
461
352
  kill_daemon_with_signal
462
353
  end
463
354
  end
464
355
 
465
- def kill_daemon_with_signal(force = false)
466
- pid = read_pid_file
467
- if pid
356
+ def kill_daemon_with_signal(force: false)
357
+ if (pid = read_pid_file)
468
358
  if force
469
359
  Process.kill("SIGKILL", pid)
470
360
  else
471
- Process.kill("SIGTERM", pid)
361
+ Process.kill(normalize_signal_name(@stop_graceful_signal), pid)
472
362
  end
473
363
  end
474
364
  rescue Errno::ESRCH, Errno::ENOENT
475
365
  end
476
366
 
477
367
  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
368
+ pid = read_pid_file
486
369
  if pid.nil?
487
- false
370
+ nil
488
371
  elsif check_pid(pid)
489
372
  true
490
373
  else
@@ -494,14 +377,11 @@ class DaemonController
494
377
  end
495
378
 
496
379
  def read_pid_file
497
- begin
498
- pid = File.read(@pid_file).strip
499
- rescue Errno::ENOENT
500
- return nil
501
- end
380
+ pid = File.read(@pid_file).strip
502
381
  if /\A\d+\Z/.match?(pid)
503
382
  pid.to_i
504
383
  end
384
+ rescue Errno::ENOENT
505
385
  end
506
386
 
507
387
  def delete_pid_file
@@ -522,7 +402,7 @@ class DaemonController
522
402
  true
523
403
  end
524
404
 
525
- def wait_until(sleep_interval = 0.1)
405
+ def wait_until(sleep_interval: 0.1)
526
406
  until yield
527
407
  sleep(sleep_interval)
528
408
  end
@@ -552,15 +432,69 @@ class DaemonController
552
432
  end
553
433
 
554
434
  def pid_file_available?
555
- File.exist?(@pid_file) && File.stat(@pid_file).size != 0
435
+ File.exist?(@pid_file) && !File.zero?(@pid_file)
556
436
  end
557
437
 
558
438
  # This method does nothing and only serves as a hook for the unit test.
559
- def start_timed_out
439
+ def daemon_spawned
560
440
  end
561
441
 
562
442
  # This method does nothing and only serves as a hook for the unit test.
563
- def daemonization_timed_out
443
+ def start_timed_out(pid)
444
+ end
445
+
446
+ # This method does nothing and only serves as a hook for the unit test.
447
+ def daemonization_timed_out(pid)
448
+ end
449
+
450
+ # Aborts a daemon that we tried to start, but timed out.
451
+ def abort_start(pid:, is_direct_child:)
452
+ begin
453
+ debug "Killing process #{pid}"
454
+ Process.kill("SIGTERM", pid)
455
+ rescue SystemCallError
456
+ end
457
+
458
+ begin
459
+ timeoutable(@start_abort_timeout) do
460
+ allow_timeout do
461
+ wait_for_aborted_process(pid:, is_direct_child:)
462
+ end
463
+ end
464
+ rescue Timeout::Error
465
+ begin
466
+ Process.kill("SIGKILL", pid)
467
+ rescue SystemCallError
468
+ end
469
+
470
+ allow_timeout do
471
+ wait_for_aborted_process(pid:, is_direct_child:)
472
+ end
473
+ end
474
+ end
475
+
476
+ def wait_for_aborted_process(pid:, is_direct_child:)
477
+ if is_direct_child
478
+ begin
479
+ debug "Waiting directly for process #{pid}"
480
+ Process.waitpid(pid)
481
+ rescue SystemCallError
482
+ end
483
+
484
+ # The daemon may have:
485
+ # 1. Written a PID file before forking. We delete this PID file.
486
+ # -OR-
487
+ # 2. It might have forked (and written a PID file) right before
488
+ # we terminated it. We'll want the fork to stay alive rather
489
+ # than going through the (complicated) trouble of killing it.
490
+ # Don't touch the PID file.
491
+ pid2 = read_pid_file
492
+ debug "PID file contains #{pid2.inspect}"
493
+ delete_pid_file if pid == pid2
494
+ else
495
+ debug "Waiting until daemon is no longer running"
496
+ wait_until { !daemon_is_running? }
497
+ end
564
498
  end
565
499
 
566
500
  def save_log_file_information
@@ -604,9 +538,9 @@ class DaemonController
604
538
  nil
605
539
  end
606
540
 
607
- def determine_lock_file(options, identifier, pid_file)
608
- if options[:lock_file]
609
- LockFile.new(File.absolute_path(options[:lock_file]))
541
+ def determine_lock_file(given_lock_file, identifier, pid_file)
542
+ if given_lock_file
543
+ LockFile.new(File.absolute_path(given_lock_file))
610
544
  else
611
545
  LockFile.new(File.absolute_path(pid_file + ".lock"))
612
546
  end
@@ -614,64 +548,44 @@ class DaemonController
614
548
 
615
549
  def run_command(command)
616
550
  if should_capture_output_while_running_command?
617
- run_command_while_capturing_output(command)
551
+ # Create tempfile for storing the command's output.
552
+ tempfile = Tempfile.new("daemon-output")
553
+ tempfile.chmod(0o666)
554
+ tempfile_path = tempfile.path
555
+ tempfile.close
556
+
557
+ spawn_options = {
558
+ in: "/dev/null",
559
+ out: tempfile_path,
560
+ err: tempfile_path,
561
+ close_others: true
562
+ }
618
563
  else
619
- run_command_without_capturing_output(command)
564
+ spawn_options = {
565
+ in: "/dev/null",
566
+ out: :out,
567
+ err: :err,
568
+ close_others: true
569
+ }
620
570
  end
621
- end
622
571
 
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
572
+ if @keep_ios
573
+ @keep_ios.each do |io|
574
+ spawn_options[io] = io
636
575
  end
637
576
  end
638
- end
639
577
 
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
-
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
578
  pid = if @daemonize_for_me
665
579
  Process.spawn(@env, ruby_interpreter, SPAWNER_FILE,
666
- command, options)
580
+ command, spawn_options)
667
581
  else
668
- Process.spawn(@env, command, options)
582
+ Process.spawn(@env, command, spawn_options)
669
583
  end
670
584
 
671
585
  # run_command might be running in a timeout block (like
672
586
  # in #start_without_locking).
673
587
  begin
674
- interruptable_waitpid(pid)
588
+ Process.waitpid(pid)
675
589
  rescue Errno::ECHILD
676
590
  # Maybe a background thread or whatever waitpid()'ed
677
591
  # this child process before we had the chance. There's
@@ -679,102 +593,51 @@ class DaemonController
679
593
  # it started successfully; if it didn't we'll know
680
594
  # that later by checking the PID file and by pinging
681
595
  # it.
682
- return
596
+ return InternalCommandOkResult.new(pid, tempfile_path && File.read(tempfile_path).strip)
683
597
  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
598
+ return InternalCommandTimeoutResult.new(pid, tempfile_path && File.read(tempfile_path).strip)
705
599
  end
706
600
 
707
- output = File.read(tempfile_path).strip
708
- if $?.exitstatus != 0
709
- raise StartError, concat_spawn_output_and_logs(output, differences_in_log_file, $?)
601
+ child_status = $?
602
+ output = File.read(tempfile_path).strip if tempfile_path
603
+ if child_status.success?
604
+ InternalCommandOkResult.new(pid, output)
710
605
  else
711
- output
606
+ InternalCommandErrorResult.new(pid, output, child_status)
712
607
  end
713
608
  ensure
714
609
  begin
715
- File.unlink(tempfile_path)
716
- rescue
610
+ tempfile.unlink if tempfile
611
+ rescue SystemCallError
717
612
  nil
718
613
  end
719
614
  end
720
615
 
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)
616
+ def should_capture_output_while_running_command?
617
+ if is_std_channel_chardev?(@log_file)
618
+ false
734
619
  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
620
  begin
756
- Process.kill("SIGTERM", pid)
621
+ real_log_file = Pathname.new(@log_file).realpath.to_s
757
622
  rescue SystemCallError
623
+ real_log_file = nil
758
624
  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
625
+ if real_log_file
626
+ !is_std_channel_chardev?(real_log_file)
627
+ else
628
+ true
770
629
  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
630
  end
776
631
  end
777
632
 
633
+ def is_std_channel_chardev?(path)
634
+ path == "/dev/stdout" ||
635
+ path == "/dev/stderr" ||
636
+ path == "/dev/fd/1" ||
637
+ path == "/dev/fd/2" ||
638
+ path =~ %r{\A/proc/([0-9]+|self)/fd/[12]\Z}
639
+ end
640
+
778
641
  def run_ping_command
779
642
  if @ping_command.respond_to?(:call)
780
643
  begin
@@ -821,43 +684,7 @@ class DaemonController
821
684
  end
822
685
  end
823
686
 
824
- if !can_ping_unix_sockets?
825
- require "java"
826
-
827
- def ping_socket(host_name, port)
828
- channel = java.nio.channels.SocketChannel.open
829
- begin
830
- address = java.net.InetSocketAddress.new(host_name, port)
831
- channel.configure_blocking(false)
832
- if channel.connect(address)
833
- return true
834
- end
835
-
836
- deadline = Time.now.to_f + 0.1
837
- while true
838
- begin
839
- if channel.finish_connect
840
- return true
841
- end
842
- rescue java.net.ConnectException => e
843
- if /Connection refused/i.match?(e.message)
844
- return false
845
- else
846
- throw e
847
- end
848
- end
849
-
850
- # Not done connecting and no error.
851
- sleep 0.01
852
- if Time.now.to_f >= deadline
853
- return false
854
- end
855
- end
856
- ensure
857
- channel.close
858
- end
859
- end
860
- else
687
+ if can_ping_unix_sockets?
861
688
  def ping_socket(socket_domain, sockaddr)
862
689
  socket = Socket.new(socket_domain, Socket::Constants::SOCK_STREAM, 0)
863
690
  begin
@@ -890,31 +717,59 @@ class DaemonController
890
717
  rescue Errno::EAFNOSUPPORT
891
718
  ping_socket(Socket::Constants::AF_INET6, sockaddr)
892
719
  end
720
+ else
721
+ require "java"
722
+
723
+ def ping_socket(host_name, port)
724
+ channel = java.nio.channels.SocketChannel.open
725
+ begin
726
+ address = java.net.InetSocketAddress.new(host_name, port)
727
+ channel.configure_blocking(false)
728
+ return true if channel.connect(address)
729
+
730
+ deadline = Time.now.to_f + 0.1
731
+ loop do
732
+ begin
733
+ return true if channel.finish_connect
734
+ rescue java.net.ConnectException => e
735
+ if /Connection refused/i.match?(e.message)
736
+ return false
737
+ else
738
+ throw e
739
+ end
740
+ end
741
+
742
+ # Not done connecting and no error.
743
+ sleep 0.01
744
+ return false if Time.now.to_f >= deadline
745
+ end
746
+ ensure
747
+ channel.close
748
+ end
749
+ end
893
750
  end
894
751
 
895
752
  def ruby_interpreter
896
- rb_config = if defined?(RbConfig)
897
- RbConfig::CONFIG
898
- else
899
- Config::CONFIG
900
- end
901
753
  File.join(
902
- rb_config["bindir"],
903
- rb_config["RUBY_INSTALL_NAME"]
904
- ) + rb_config["EXEEXT"]
754
+ RbConfig::CONFIG["bindir"],
755
+ RbConfig::CONFIG.values_at("RUBY_INSTALL_NAME", "EXEEXT").join
756
+ )
905
757
  end
906
758
 
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
759
+ def timeoutable(amount, &block)
760
+ Thread.handle_interrupt(Timeout::Error => :never) do
761
+ start_time = monotonic_time
762
+ result = Timeout.timeout(amount, Timeout::Error, &block)
763
+ [result, [monotonic_time - start_time, 0].max]
916
764
  end
917
- result
765
+ end
766
+
767
+ def allow_timeout(&block)
768
+ Thread.handle_interrupt(Timeout::Error => :on_blocking, &block)
769
+ end
770
+
771
+ def monotonic_time
772
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
918
773
  end
919
774
 
920
775
  def signal_termination_message(process_status)
@@ -925,8 +780,12 @@ class DaemonController
925
780
  end
926
781
  end
927
782
 
783
+ def normalize_signal_name(name)
784
+ name.start_with?("SIG") ? name : "SIG#{name}"
785
+ end
786
+
928
787
  def signal_name_for(num)
929
- if (name = Signal.list.find { |name, n| n == num }[0])
788
+ if (name = Signal.list.key(num))
930
789
  "SIG#{name}"
931
790
  else
932
791
  num.to_s
@@ -934,30 +793,31 @@ class DaemonController
934
793
  end
935
794
 
936
795
  def concat_spawn_output_and_logs(output, logs, exit_status = nil, suffix_message = nil)
937
- if output.nil? && logs.nil?
938
- result_inner = [
939
- "logs not available",
796
+ format_full_suffix_message = lambda do |main_message = nil|
797
+ [
798
+ main_message,
940
799
  exit_status ? signal_termination_message(exit_status) : nil,
941
800
  suffix_message
942
801
  ].compact.join("; ")
943
- "(#{result_inner})"
802
+ end
803
+
804
+ if output.nil? && logs.nil?
805
+ "(#{format_full_suffix_message.call("logs not available")})"
944
806
  elsif (output && output.empty? && logs && logs.empty?) || (output && output.empty? && logs.nil?) || (output.nil? && logs && logs.empty?)
945
- result_inner = [
946
- "logs empty",
947
- exit_status ? signal_termination_message(exit_status) : nil,
948
- suffix_message
949
- ].compact.join("; ")
950
- "(#{result_inner})"
807
+ "(#{format_full_suffix_message.call("logs empty")})"
951
808
  else
952
- result = ((output || "") + "\n" + (logs || "")).strip
953
- result_suffix = [
954
- exit_status ? signal_termination_message(exit_status) : nil,
955
- suffix_message
956
- ].compact.join("; ")
957
- if !result_suffix.empty?
958
- result << "\n(#{result_suffix})"
809
+ full_suffix_message = format_full_suffix_message.call
810
+ if full_suffix_message.empty?
811
+ "#{output}\n#{logs}".strip
812
+ elsif logs && logs.empty?
813
+ "#{output}\n(#{full_suffix_message})".strip
814
+ else
815
+ "#{output}\n#{logs}\n(#{full_suffix_message})".strip
959
816
  end
960
- result
961
817
  end
962
818
  end
819
+
820
+ def debug(message)
821
+ @logger.debug(message) if @logger
822
+ end
963
823
  end