daemon_controller 1.2.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.
@@ -1,16 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # daemon_controller, library for robust daemon management
2
- # Copyright (c) 2010-2014 Phusion
3
- #
4
+ # Copyright (c) 2010-2025 Asynchronous B.V.
5
+ #
4
6
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
7
  # of this software and associated documentation files (the "Software"), to deal
6
8
  # in the Software without restriction, including without limitation the rights
7
9
  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
10
  # copies of the Software, and to permit persons to whom the Software is
9
11
  # furnished to do so, subject to the following conditions:
10
- #
12
+ #
11
13
  # The above copyright notice and this permission notice shall be included in
12
14
  # all copies or substantial portions of the Software.
13
- #
15
+ #
14
16
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
17
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
18
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -19,855 +21,839 @@
19
21
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
22
  # THE SOFTWARE.
21
23
 
22
- require 'tempfile'
23
- require 'fcntl'
24
- require 'socket'
25
- require 'timeout'
26
- if Process.respond_to?(:spawn)
27
- require 'rbconfig'
28
- end
24
+ require "tempfile"
25
+ require "fcntl"
26
+ require "socket"
27
+ require "pathname"
28
+ require "timeout"
29
+ require "rbconfig"
29
30
 
30
- require 'daemon_controller/lock_file'
31
+ require_relative "daemon_controller/lock_file"
31
32
 
32
33
  # Main daemon controller object. See the README for an introduction and tutorial.
33
34
  class DaemonController
34
- ALLOWED_CONNECT_EXCEPTIONS = [Errno::ECONNREFUSED, Errno::ENETUNREACH,
35
- Errno::ETIMEDOUT, Errno::ECONNRESET, Errno::EINVAL,
36
- Errno::EADDRNOTAVAIL]
37
-
38
- SPAWNER_FILE = File.expand_path(File.join(File.dirname(__FILE__),
39
- "daemon_controller", "spawn.rb"))
40
-
41
- class Error < StandardError
42
- end
43
- class TimeoutError < Error
44
- end
45
- class AlreadyStarted < Error
46
- end
47
- class StartError < Error
48
- end
49
- class StartTimeout < TimeoutError
50
- end
51
- class StopError < Error
52
- end
53
- class StopTimeout < TimeoutError
54
- end
55
- class ConnectError < Error
56
- end
57
- class DaemonizationTimeout < TimeoutError
58
- end
59
-
60
- # Create a new DaemonController object.
61
- #
62
- # === Mandatory options
63
- #
64
- # [:identifier]
65
- # A human-readable, unique name for this daemon, e.g. "Sphinx search server".
66
- # This identifier will be used in some error messages. On some platforms, it will
67
- # be used for concurrency control: on such platforms, no two DaemonController
68
- # objects will operate on the same identifier on the same time.
69
- #
70
- # [:start_command]
71
- # The command to start the daemon. This must be a a String, e.g.
72
- # "mongrel_rails start -e production", or a Proc which returns a String.
73
- #
74
- # If the value is a Proc, and the +before_start+ option is given too, then
75
- # the +start_command+ Proc is guaranteed to be called after the +before_start+
76
- # Proc is called.
77
- #
78
- # [:ping_command]
79
- # The ping command is used to check whether the daemon can be connected to.
80
- # It is also used to ensure that #start only returns when the daemon can be
81
- # connected to.
82
- #
83
- # The value may be a command string. This command must exit with an exit code of
84
- # 0 if the daemon can be successfully connected to, or exit with a non-0 exit
85
- # code on failure.
86
- #
87
- # The value may also be an Array which specifies the socket address of the daemon.
88
- # It must be in one of the following forms:
89
- # - [:tcp, host_name, port]
90
- # - [:unix, filename]
91
- #
92
- # The value may also be a Proc, which returns an expression that evaluates to
93
- # true (indicating that the daemon can be connected to) or false (failure).
94
- # If the Proc raises Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::ETIMEDOUT
95
- # Errno::ECONNRESET, Errno::EINVAL or Errno::EADDRNOTAVAIL then that also
96
- # means that the daemon cannot be connected to.
97
- # <b>NOTE:</b> if the ping command returns an object which responds to
98
- # <tt>#close</tt>, then that method will be called on it.
99
- # This makes it possible to specify a ping command such as
100
- # <tt>lambda { TCPSocket.new('localhost', 1234) }</tt>, without having to worry
101
- # about closing it afterwards.
102
- # Any exceptions raised by #close are ignored.
103
- #
104
- # [:pid_file]
105
- # The PID file that the daemon will write to. Used to check whether the daemon
106
- # is running.
107
- #
108
- # [:lock_file]
109
- # The lock file to use for serializing concurrent daemon management operations.
110
- # Defaults to "(filename of PID file).lock".
111
- #
112
- # [:log_file]
113
- # The log file that the daemon will write to. It will be consulted to see
114
- # whether the daemon has printed any error messages during startup.
115
- #
116
- # === Optional options
117
- # [:stop_command]
118
- # A command to stop the daemon with, e.g. "/etc/rc.d/nginx stop". If no stop
119
- # command is given (i.e. +nil+), then DaemonController will stop the daemon
120
- # by killing the PID written in the PID file.
121
- #
122
- # The default value is +nil+.
123
- #
124
- # [:restart_command]
125
- # A command to restart the daemon with, e.g. "/etc/rc.d/nginx restart". If
126
- # no restart command is given (i.e. +nil+), then DaemonController will
127
- # restart the daemon by calling #stop and #start.
128
- #
129
- # The default value is +nil+.
130
- #
131
- # [:before_start]
132
- # This may be a Proc. It will be called just before running the start command.
133
- # The before_start proc is not subject to the start timeout.
134
- #
135
- # [:start_timeout]
136
- # The maximum amount of time, in seconds, that #start may take to start
137
- # the daemon. Since #start also waits until the daemon can be connected to,
138
- # that wait time is counted as well. If the daemon does not start in time,
139
- # then #start will raise an exception.
140
- #
141
- # The default value is 15.
142
- #
143
- # [:stop_timeout]
144
- # The maximum amount of time, in seconds, that #stop may take to stop
145
- # the daemon. Since #stop also waits until the daemon is no longer running,
146
- # that wait time is counted as well. If the daemon does not stop in time,
147
- # then #stop will raise an exception.
148
- #
149
- # The default value is 15.
150
- #
151
- # [:log_file_activity_timeout]
152
- # Once a daemon has gone into the background, it will become difficult to
153
- # know for certain whether it is still initializing or whether it has
154
- # failed and exited, until it has written its PID file. Suppose that it
155
- # failed with an error after daemonizing but before it has written its PID file;
156
- # not many system administrators want to wait 15 seconds (the default start
157
- # timeout) to be notified of whether the daemon has terminated with an error.
158
- #
159
- # An alternative way to check whether the daemon has terminated with an error,
160
- # is by checking whether its log file has been recently updated. If, after the
161
- # daemon has started, the log file hasn't been updated for the amount of seconds
162
- # given by the :log_file_activity_timeout option, then the daemon is assumed to
163
- # have terminated with an error.
164
- #
165
- # The default value is 7.
166
- #
167
- # [:daemonize_for_me]
168
- # Normally daemon_controller will wait until the daemon has daemonized into the
169
- # background, in order to capture any errors that it may print on stdout or
170
- # stderr before daemonizing. However, if the daemon doesn't support daemonization
171
- # for some reason, then setting this option to true will cause daemon_controller
172
- # to do the daemonization for the daemon.
173
- #
174
- # The default is false.
175
- #
176
- # [:keep_ios]
177
- # Upon spawning the daemon, daemon_controller will normally close all file
178
- # descriptors except stdin, stdout and stderr. However if there are any file
179
- # descriptors you want to keep open, specify the IO objects here. This must be
180
- # an array of IO objects.
181
- #
182
- # [:env]
183
- # This must be a Hash. The hash will contain the environment variables available
184
- # to be made available to the daemon. Hash keys must be strings, not symbols.
185
- def initialize(options)
186
- [:identifier, :start_command, :ping_command, :pid_file, :log_file].each do |option|
187
- if !options.has_key?(option)
188
- raise ArgumentError, "The ':#{option}' option is mandatory."
189
- end
190
- end
191
- @identifier = options[:identifier]
192
- @start_command = options[:start_command]
193
- @stop_command = options[:stop_command]
194
- @ping_command = options[:ping_command]
195
- @restart_command = options[:restart_command]
196
- @ping_interval = options[:ping_interval] || 0.1
197
- @pid_file = options[:pid_file]
198
- @log_file = options[:log_file]
199
- @before_start = options[:before_start]
200
- @start_timeout = options[:start_timeout] || 15
201
- @stop_timeout = options[:stop_timeout] || 15
202
- @log_file_activity_timeout = options[:log_file_activity_timeout] || 7
203
- @daemonize_for_me = options[:daemonize_for_me]
204
- @keep_ios = options[:keep_ios] || []
205
- @lock_file = determine_lock_file(options, @identifier, @pid_file)
206
- @env = options[:env] || {}
207
- end
208
-
209
- # Start the daemon and wait until it can be pinged.
210
- #
211
- # Raises:
212
- # - AlreadyStarted - the daemon is already running.
213
- # - StartError - the start command failed.
214
- # - StartTimeout - the daemon did not start in time. This could also
215
- # mean that the daemon failed after it has gone into the background.
216
- def start
217
- @lock_file.exclusive_lock do
218
- start_without_locking
219
- end
220
- end
221
-
222
- # Connect to the daemon by running the given block, which contains the
223
- # connection logic. If the daemon isn't already running, then it will be
224
- # started.
225
- #
226
- # The block must return nil or raise Errno::ECONNREFUSED, Errno::ENETUNREACH,
227
- # Errno::ETIMEDOUT, Errno::ECONNRESET, Errno::EINVAL and Errno::EADDRNOTAVAIL
228
- # to indicate that the daemon cannot be
229
- # connected to. It must return non-nil if the daemon can be connected to.
230
- # Upon successful connection, the return value of the block will
231
- # be returned by #connect.
232
- #
233
- # Note that the block may be called multiple times.
234
- #
235
- # Raises:
236
- # - StartError - an attempt to start the daemon was made, but the start
237
- # command failed with an error.
238
- # - StartTimeout - an attempt to start the daemon was made, but the daemon
239
- # did not start in time, or it failed after it has gone into the background.
240
- # - ConnectError - the daemon wasn't already running, but we couldn't connect
241
- # to the daemon even after starting it.
242
- def connect
243
- connection = nil
244
- @lock_file.shared_lock do
245
- begin
246
- connection = yield
247
- rescue *ALLOWED_CONNECT_EXCEPTIONS
248
- connection = nil
249
- end
250
- end
251
- if connection.nil?
252
- @lock_file.exclusive_lock do
253
- if !daemon_is_running?
254
- start_without_locking
255
- end
256
- connect_exception = nil
257
- begin
258
- connection = yield
259
- rescue *ALLOWED_CONNECT_EXCEPTIONS => e
260
- connection = nil
261
- connect_exception = e
262
- end
263
- if connection.nil?
264
- # Daemon is running but we couldn't connect to it. Possible
265
- # reasons:
266
- # - The daemon froze.
267
- # - Bizarre security restrictions.
268
- # - There's a bug in the yielded code.
269
- if connect_exception
270
- raise ConnectError, "Cannot connect to the daemon: #{connect_exception} (#{connect_exception.class})"
271
- else
272
- raise ConnectError, "Cannot connect to the daemon"
273
- end
274
- else
275
- return connection
276
- end
277
- end
278
- else
279
- return connection
280
- end
281
- end
282
-
283
- # Stop the daemon and wait until it has exited.
284
- #
285
- # Raises:
286
- # - StopError - the stop command failed.
287
- # - StopTimeout - the daemon didn't stop in time.
288
- def stop
289
- @lock_file.exclusive_lock do
290
- begin
291
- Timeout.timeout(@stop_timeout, Timeout::Error) do
292
- kill_daemon
293
- wait_until do
294
- !daemon_is_running?
295
- end
296
- end
297
- rescue Timeout::Error
298
- raise StopTimeout, "Daemon '#{@identifier}' did not exit in time"
299
- end
300
- end
301
- end
302
-
303
- # Restarts the daemon. Uses the restart_command if provided, otherwise
304
- # calls #stop and #start.
305
- def restart
306
- if @restart_command
307
- run_command(@restart_command)
308
- else
309
- stop
310
- start
311
- end
312
- end
313
-
314
- # Returns the daemon's PID, as reported by its PID file. Returns the PID
315
- # as an integer, or nil there is no valid PID in the PID file.
316
- #
317
- # This method doesn't check whether the daemon's actually running.
318
- # Use #running? if you want to check whether it's actually running.
319
- #
320
- # Raises SystemCallError or IOError if something went wrong during
321
- # reading of the PID file.
322
- def pid
323
- @lock_file.shared_lock do
324
- return read_pid_file
325
- end
326
- end
327
-
328
- # Checks whether the daemon is still running. This is done by reading
329
- # the PID file and then checking whether there is a process with that
330
- # PID.
331
- #
332
- # Raises SystemCallError or IOError if something went wrong during
333
- # reading of the PID file.
334
- def running?
335
- @lock_file.shared_lock do
336
- return daemon_is_running?
337
- end
338
- end
339
-
340
- # Checks whether ping Unix domain sockets is supported. Currently
341
- # this is supported on all Ruby implementations, except JRuby.
342
- def self.can_ping_unix_sockets?
343
- return RUBY_PLATFORM != "java"
344
- end
345
-
346
- private
347
- def start_without_locking
348
- if daemon_is_running?
349
- raise AlreadyStarted, "Daemon '#{@identifier}' is already started"
350
- end
351
- save_log_file_information
352
- delete_pid_file
353
- begin
354
- started = false
355
- before_start
356
- Timeout.timeout(@start_timeout, Timeout::Error) do
357
- done = false
358
- spawn_daemon
359
- record_activity
360
-
361
- # We wait until the PID file is available and until
362
- # the daemon responds to pings, but we wait no longer
363
- # than @start_timeout seconds in total (including daemon
364
- # spawn time).
365
- # Furthermore, if the log file hasn't changed for
366
- # @log_file_activity_timeout seconds, and the PID file
367
- # still isn't available or the daemon still doesn't
368
- # respond to pings, then assume that the daemon has
369
- # terminated with an error.
370
- wait_until do
371
- if log_file_has_changed?
372
- record_activity
373
- elsif no_activity?(@log_file_activity_timeout)
374
- raise Timeout::Error, "Daemon seems to have exited"
375
- end
376
- pid_file_available?
377
- end
378
- wait_until(@ping_interval) do
379
- if log_file_has_changed?
380
- record_activity
381
- elsif no_activity?(@log_file_activity_timeout)
382
- raise Timeout::Error, "Daemon seems to have exited"
383
- end
384
- run_ping_command || !daemon_is_running?
385
- end
386
- started = run_ping_command
387
- end
388
- result = started
389
- rescue DaemonizationTimeout, Timeout::Error => e
390
- start_timed_out
391
- if pid_file_available?
392
- kill_daemon_with_signal(true)
393
- end
394
- if e.is_a?(DaemonizationTimeout)
395
- result = :daemonization_timeout
396
- else
397
- result = :start_timeout
398
- end
399
- end
400
- if !result
401
- raise(StartError, differences_in_log_file ||
402
- "Daemon '#{@identifier}' failed to start.")
403
- elsif result == :daemonization_timeout
404
- raise(StartTimeout, differences_in_log_file ||
405
- "Daemon '#{@identifier}' didn't daemonize in time.")
406
- elsif result == :start_timeout
407
- raise(StartTimeout, differences_in_log_file ||
408
- "Daemon '#{@identifier}' failed to start in time.")
409
- else
410
- return true
411
- end
412
- end
413
-
414
- def before_start
415
- if @before_start
416
- @before_start.call
417
- end
418
- end
419
-
420
- def spawn_daemon
421
- if @start_command.respond_to?(:call)
422
- run_command(@start_command.call)
423
- else
424
- run_command(@start_command)
425
- end
426
- end
427
-
428
- def kill_daemon
429
- if @stop_command
430
- begin
431
- run_command(@stop_command)
432
- rescue StartError => e
433
- raise StopError, e.message
434
- end
435
- else
436
- kill_daemon_with_signal
437
- end
438
- end
439
-
440
- def kill_daemon_with_signal(force = false)
441
- pid = read_pid_file
442
- if pid
443
- if force
444
- Process.kill('SIGKILL', pid)
445
- else
446
- Process.kill('SIGTERM', pid)
447
- end
448
- end
449
- rescue Errno::ESRCH, Errno::ENOENT
450
- end
451
-
452
- def daemon_is_running?
453
- begin
454
- pid = read_pid_file
455
- rescue Errno::ENOENT
456
- # The PID file may not exist, or another thread/process
457
- # executing #running? may have just deleted the PID file.
458
- # So we catch this error.
459
- pid = nil
460
- end
461
- if pid.nil?
462
- return false
463
- elsif check_pid(pid)
464
- return true
465
- else
466
- delete_pid_file
467
- return false
468
- end
469
- end
470
-
471
- def read_pid_file
472
- pid = File.read(@pid_file).strip
473
- if pid =~ /\A\d+\Z/
474
- return pid.to_i
475
- else
476
- return nil
477
- end
478
- end
479
-
480
- def delete_pid_file
481
- File.unlink(@pid_file)
482
- rescue Errno::EPERM, Errno::EACCES, Errno::ENOENT # ignore
483
- end
484
-
485
- def check_pid(pid)
486
- Process.kill(0, pid)
487
- return true
488
- rescue Errno::ESRCH
489
- return false
490
- rescue Errno::EPERM
491
- # We didn't have permission to kill the process. Either the process
492
- # is owned by someone else, or the system has draconian security
493
- # settings and we aren't allowed to kill *any* process. Assume that
494
- # the process is running.
495
- return true
496
- end
497
-
498
- def wait_until(sleep_interval = 0.1)
499
- while !yield
500
- sleep(sleep_interval)
501
- end
502
- end
503
-
504
- def wait_until_pid_file_is_available_or_log_file_has_changed
505
- while !(pid_file_available? || log_file_has_changed?)
506
- sleep 0.1
507
- end
508
- return pid_file_is_available?
509
- end
510
-
511
- def wait_until_daemon_responds_to_ping_or_has_exited_or_log_file_has_changed
512
- while !(run_ping_command || !daemon_is_running? || log_file_has_changed?)
513
- sleep(@ping_interval)
514
- end
515
- return run_ping_command
516
- end
517
-
518
- def record_activity
519
- @last_activity_time = Time.now
520
- end
521
-
522
- # Check whether there has been no recorded activity in the past +seconds+ seconds.
523
- def no_activity?(seconds)
524
- return Time.now - @last_activity_time > seconds
525
- end
526
-
527
- def pid_file_available?
528
- return File.exist?(@pid_file) && File.stat(@pid_file).size != 0
529
- end
530
-
531
- # This method does nothing and only serves as a hook for the unit test.
532
- def start_timed_out
533
- end
534
-
535
- # This method does nothing and only serves as a hook for the unit test.
536
- def daemonization_timed_out
537
- end
538
-
539
- def save_log_file_information
540
- @original_log_file_stat = File.stat(@log_file) rescue nil
541
- @current_log_file_stat = @original_log_file_stat
542
- end
543
-
544
- def log_file_has_changed?
545
- if @current_log_file_stat
546
- stat = File.stat(@log_file) rescue nil
547
- if stat
548
- result = @current_log_file_stat.mtime != stat.mtime ||
549
- @current_log_file_stat.size != stat.size
550
- @current_log_file_stat = stat
551
- return result
552
- else
553
- return true
554
- end
555
- else
556
- return false
557
- end
558
- end
559
-
560
- def differences_in_log_file
561
- if @original_log_file_stat
562
- File.open(@log_file, 'r') do |f|
563
- f.seek(@original_log_file_stat.size, IO::SEEK_SET)
564
- diff = f.read.strip
565
- if diff.empty?
566
- return nil
567
- else
568
- return diff
569
- end
570
- end
571
- else
572
- return nil
573
- end
574
- rescue Errno::ENOENT, Errno::ESPIPE
575
- # ESPIPE means the log file is a pipe.
576
- return nil
577
- end
578
-
579
- def determine_lock_file(options, identifier, pid_file)
580
- if options[:lock_file]
581
- return LockFile.new(File.expand_path(options[:lock_file]))
582
- else
583
- return LockFile.new(File.expand_path(pid_file + ".lock"))
584
- end
585
- end
586
-
587
- def self.fork_supported?
588
- return RUBY_PLATFORM != "java" && RUBY_PLATFORM !~ /win32/
589
- end
590
-
591
- def self.spawn_supported?
592
- # Process.spawn doesn't work very well in JRuby.
593
- return Process.respond_to?(:spawn) && RUBY_PLATFORM != "java"
594
- end
595
-
596
- def run_command(command)
597
- # Create tempfile for storing the command's output.
598
- tempfile = Tempfile.new('daemon-output')
599
- tempfile_path = tempfile.path
600
- File.chmod(0666, tempfile_path)
601
- tempfile.close
602
-
603
- if self.class.fork_supported? || self.class.spawn_supported?
604
- if Process.respond_to?(:spawn)
605
- options = {
606
- :in => "/dev/null",
607
- :out => tempfile_path,
608
- :err => tempfile_path,
609
- :close_others => true
610
- }
611
- @keep_ios.each do |io|
612
- options[io] = io
613
- end
614
- if @daemonize_for_me
615
- pid = Process.spawn(@env, ruby_interpreter, SPAWNER_FILE,
616
- command, options)
617
- else
618
- pid = Process.spawn(@env, command, options)
619
- end
620
- else
621
- pid = safe_fork(@daemonize_for_me) do
622
- ObjectSpace.each_object(IO) do |obj|
623
- if !@keep_ios.include?(obj)
624
- obj.close rescue nil
625
- end
626
- end
627
- STDIN.reopen("/dev/null", "r")
628
- STDOUT.reopen(tempfile_path, "w")
629
- STDERR.reopen(tempfile_path, "w")
630
- ENV.update(@env)
631
- exec(command)
632
- end
633
- end
634
-
635
- # run_command might be running in a timeout block (like
636
- # in #start_without_locking).
637
- begin
638
- interruptable_waitpid(pid)
639
- rescue Errno::ECHILD
640
- # Maybe a background thread or whatever waitpid()'ed
641
- # this child process before we had the chance. There's
642
- # no way to obtain the exit status now. Assume that
643
- # it started successfully; if it didn't we'll know
644
- # that later by checking the PID file and by pinging
645
- # it.
646
- return
647
- rescue Timeout::Error
648
- daemonization_timed_out
649
-
650
- # If the daemon doesn't fork into the background
651
- # in time, then kill it.
652
- begin
653
- Process.kill('SIGTERM', pid)
654
- rescue SystemCallError
655
- end
656
- begin
657
- Timeout.timeout(5, Timeout::Error) do
658
- begin
659
- interruptable_waitpid(pid)
660
- rescue SystemCallError
661
- end
662
- end
663
- rescue Timeout::Error
664
- begin
665
- Process.kill('SIGKILL', pid)
666
- interruptable_waitpid(pid)
667
- rescue SystemCallError
668
- end
669
- end
670
- raise DaemonizationTimeout
671
- end
672
- if $?.exitstatus != 0
673
- raise StartError, File.read(tempfile_path).strip
674
- end
675
- else
676
- if @env && !@env.empty?
677
- raise "Setting the :env option is not supported on this Ruby implementation."
678
- elsif @daemonize_for_me
679
- raise "Setting the :daemonize_for_me option is not supported on this Ruby implementation."
680
- end
681
-
682
- cmd = "#{command} >\"#{tempfile_path}\""
683
- cmd << " 2>\"#{tempfile_path}\"" unless PLATFORM =~ /mswin/
684
- if !system(cmd)
685
- raise StartError, File.read(tempfile_path).strip
686
- end
687
- end
688
- ensure
689
- File.unlink(tempfile_path) rescue nil
690
- end
691
-
692
- def run_ping_command
693
- if @ping_command.respond_to?(:call)
694
- begin
695
- value = @ping_command.call
696
- if value.respond_to?(:close)
697
- value.close rescue nil
698
- end
699
- return value
700
- rescue *ALLOWED_CONNECT_EXCEPTIONS
701
- return false
702
- end
703
- elsif @ping_command.is_a?(Array)
704
- type, *args = @ping_command
705
- if self.class.can_ping_unix_sockets?
706
- case type
707
- when :tcp
708
- hostname, port = args
709
- sockaddr = Socket.pack_sockaddr_in(port, hostname)
710
- return ping_tcp_socket(sockaddr)
711
- when :unix
712
- socket_domain = Socket::Constants::AF_LOCAL
713
- sockaddr = Socket.pack_sockaddr_un(args[0])
714
- return ping_socket(socket_domain, sockaddr)
715
- else
716
- raise ArgumentError, "Unknown ping command type #{type.inspect}"
717
- end
718
- else
719
- case type
720
- when :tcp
721
- hostname, port = args
722
- return ping_socket(hostname, port)
723
- when :unix
724
- raise "Pinging Unix domain sockets is not supported on this Ruby implementation"
725
- else
726
- raise ArgumentError, "Unknown ping command type #{type.inspect}"
727
- end
728
- end
729
- else
730
- return system(@ping_command)
731
- end
732
- end
733
-
734
- if !can_ping_unix_sockets?
735
- require 'java'
736
-
737
- def ping_socket(host_name, port)
738
- channel = java.nio.channels.SocketChannel.open
739
- begin
740
- address = java.net.InetSocketAddress.new(host_name, port)
741
- channel.configure_blocking(false)
742
- if channel.connect(address)
743
- return true
744
- end
745
-
746
- deadline = Time.now.to_f + 0.1
747
- done = false
748
- while true
749
- begin
750
- if channel.finish_connect
751
- return true
752
- end
753
- rescue java.net.ConnectException => e
754
- if e.message =~ /Connection refused/i
755
- return false
756
- else
757
- throw e
758
- end
759
- end
760
-
761
- # Not done connecting and no error.
762
- sleep 0.01
763
- if Time.now.to_f >= deadline
764
- return false
765
- end
766
- end
767
- ensure
768
- channel.close
769
- end
770
- end
771
- else
772
- def ping_socket(socket_domain, sockaddr)
773
- begin
774
- socket = Socket.new(socket_domain, Socket::Constants::SOCK_STREAM, 0)
775
- begin
776
- socket.connect_nonblock(sockaddr)
777
- rescue Errno::ENOENT, Errno::EINPROGRESS, Errno::EAGAIN, Errno::EWOULDBLOCK
778
- if select(nil, [socket], nil, 0.1)
779
- begin
780
- socket.connect_nonblock(sockaddr)
781
- rescue Errno::EISCONN
782
- rescue Errno::EINVAL
783
- if RUBY_PLATFORM =~ /freebsd/i
784
- raise Errno::ECONNREFUSED
785
- else
786
- raise
787
- end
788
- end
789
- else
790
- raise Errno::ECONNREFUSED
791
- end
792
- end
793
- return true
794
- rescue Errno::ECONNREFUSED, Errno::ENOENT
795
- return false
796
- ensure
797
- socket.close if socket
798
- end
799
- end
800
-
801
- def ping_tcp_socket(sockaddr)
802
- begin
803
- ping_socket(Socket::Constants::AF_INET, sockaddr)
804
- rescue Errno::EAFNOSUPPORT
805
- ping_socket(Socket::Constants::AF_INET6, sockaddr)
806
- end
807
- end
808
- end
809
-
810
- def ruby_interpreter
811
- if defined?(RbConfig)
812
- rb_config = RbConfig::CONFIG
813
- else
814
- rb_config = Config::CONFIG
815
- end
816
- File.join(
817
- rb_config['bindir'],
818
- rb_config['RUBY_INSTALL_NAME']
819
- ) + rb_config['EXEEXT']
820
- end
821
-
822
- def safe_fork(double_fork)
823
- pid = fork
824
- if pid.nil?
825
- begin
826
- if double_fork
827
- pid2 = fork
828
- if pid2.nil?
829
- Process.setsid
830
- yield
831
- end
832
- else
833
- yield
834
- end
835
- rescue Exception => e
836
- message = "*** Exception #{e.class} " <<
837
- "(#{e}) (process #{$$}):\n" <<
838
- "\tfrom " << e.backtrace.join("\n\tfrom ")
839
- STDERR.write(e)
840
- STDERR.flush
841
- exit!
842
- ensure
843
- exit!(0)
844
- end
845
- else
846
- if double_fork
847
- Process.waitpid(pid) rescue nil
848
- return pid
849
- else
850
- return pid
851
- end
852
- end
853
- end
854
-
855
- if RUBY_VERSION < "1.9"
856
- def interruptable_waitpid(pid)
857
- Process.waitpid(pid)
858
- end
859
- else
860
- # On Ruby 1.9, Thread#kill (which is called by timeout.rb) may
861
- # not be able to interrupt Process.waitpid. So here we use a
862
- # special version that's a bit less efficient but is at least
863
- # interruptable.
864
- def interruptable_waitpid(pid)
865
- result = nil
866
- while !result
867
- result = Process.waitpid(pid, Process::WNOHANG)
868
- sleep 0.01 if !result
869
- end
870
- return result
871
- end
872
- end
35
+ ALLOWED_CONNECT_EXCEPTIONS = [Errno::ECONNREFUSED, Errno::ENETUNREACH,
36
+ Errno::ETIMEDOUT, Errno::ECONNRESET, Errno::EINVAL,
37
+ Errno::EADDRNOTAVAIL]
38
+
39
+ SPAWNER_FILE = File.absolute_path(File.join(File.dirname(__FILE__),
40
+ "daemon_controller", "spawn.rb"))
41
+
42
+ class Error < StandardError
43
+ end
44
+
45
+ class TimeoutError < Error
46
+ end
47
+
48
+ class AlreadyStarted < Error
49
+ end
50
+
51
+ class StartError < Error
52
+ end
53
+
54
+ class StartTimeout < TimeoutError
55
+ end
56
+
57
+ class StopError < Error
58
+ end
59
+
60
+ class StopTimeout < TimeoutError
61
+ end
62
+
63
+ class ConnectError < Error
64
+ end
65
+
66
+ InternalCommandOkResult = Struct.new(:pid, :output)
67
+ InternalCommandErrorResult = Struct.new(:pid, :output, :exit_status)
68
+ InternalCommandTimeoutResult = Struct.new(:pid, :output)
69
+
70
+ # Create a new DaemonController object.
71
+ #
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
99
+ end
100
+
101
+ # Start the daemon and wait until it can be pinged.
102
+ #
103
+ # Raises:
104
+ # - AlreadyStarted - the daemon is already running.
105
+ # - StartError - the start command failed.
106
+ # - StartTimeout - the daemon did not start in time. This could also
107
+ # mean that the daemon failed after it has gone into the background.
108
+ def start
109
+ @lock_file.exclusive_lock do
110
+ start_without_locking
111
+ end
112
+ end
113
+
114
+ # Connect to the daemon by running the given block, which contains the
115
+ # connection logic. If the daemon isn't already running, then it will be
116
+ # started.
117
+ #
118
+ # The block must return nil or raise Errno::ECONNREFUSED, Errno::ENETUNREACH,
119
+ # Errno::ETIMEDOUT, Errno::ECONNRESET, Errno::EINVAL and Errno::EADDRNOTAVAIL
120
+ # to indicate that the daemon cannot be
121
+ # connected to. It must return non-nil if the daemon can be connected to.
122
+ # Upon successful connection, the return value of the block will
123
+ # be returned by #connect.
124
+ #
125
+ # Note that the block may be called multiple times.
126
+ #
127
+ # Raises:
128
+ # - StartError - an attempt to start the daemon was made, but the start
129
+ # command failed with an error.
130
+ # - StartTimeout - an attempt to start the daemon was made, but the daemon
131
+ # did not start in time, or it failed after it has gone into the background.
132
+ # - ConnectError - the daemon wasn't already running, but we couldn't connect
133
+ # to the daemon even after starting it.
134
+ def connect
135
+ connection = nil
136
+ @lock_file.shared_lock do
137
+ connection = yield
138
+ rescue *ALLOWED_CONNECT_EXCEPTIONS
139
+ connection = nil
140
+ end
141
+ if connection.nil?
142
+ @lock_file.exclusive_lock do
143
+ if !daemon_is_running?
144
+ start_without_locking
145
+ end
146
+ connect_exception = nil
147
+ begin
148
+ connection = yield
149
+ rescue *ALLOWED_CONNECT_EXCEPTIONS => e
150
+ connection = nil
151
+ connect_exception = e
152
+ end
153
+ if connection.nil?
154
+ # Daemon is running but we couldn't connect to it. Possible
155
+ # reasons:
156
+ # - The daemon froze.
157
+ # - Bizarre security restrictions.
158
+ # - There's a bug in the yielded code.
159
+ if connect_exception
160
+ raise ConnectError, "Cannot connect to the daemon: #{connect_exception} (#{connect_exception.class})"
161
+ else
162
+ raise ConnectError, "Cannot connect to the daemon"
163
+ end
164
+ else
165
+ connection
166
+ end
167
+ end
168
+ else
169
+ connection
170
+ end
171
+ end
172
+
173
+ # Stop the daemon and wait until it has exited.
174
+ #
175
+ # Raises:
176
+ # - StopError - the stop command failed.
177
+ # - StopTimeout - the daemon didn't stop in time.
178
+ def stop
179
+ @lock_file.exclusive_lock do
180
+ timeoutable(@stop_timeout) do
181
+ allow_timeout do
182
+ kill_daemon
183
+ wait_until { !daemon_is_running? }
184
+ end
185
+ end
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)"
191
+ end
192
+
193
+ # Restarts the daemon. Uses the restart_command if provided, otherwise
194
+ # calls #stop and #start.
195
+ def restart
196
+ if @restart_command
197
+ run_command(@restart_command)
198
+ else
199
+ stop
200
+ start
201
+ end
202
+ end
203
+
204
+ # Returns the daemon's PID, as reported by its PID file. Returns the PID
205
+ # as an integer, or nil there is no valid PID in the PID file.
206
+ #
207
+ # This method doesn't check whether the daemon's actually running.
208
+ # Use #running? if you want to check whether it's actually running.
209
+ #
210
+ # Raises SystemCallError or IOError if something went wrong during
211
+ # reading of the PID file.
212
+ def pid
213
+ @lock_file.shared_lock do
214
+ read_pid_file
215
+ end
216
+ end
217
+
218
+ # Checks whether the daemon is still running. This is done by reading
219
+ # the PID file and then checking whether there is a process with that
220
+ # PID.
221
+ #
222
+ # Raises SystemCallError or IOError if something went wrong during
223
+ # reading of the PID file.
224
+ def running?
225
+ @lock_file.shared_lock do
226
+ daemon_is_running?
227
+ end
228
+ end
229
+
230
+ # Checks whether ping Unix domain sockets is supported. Currently
231
+ # this is supported on all Ruby implementations, except JRuby.
232
+ def self.can_ping_unix_sockets?
233
+ RUBY_PLATFORM != "java"
234
+ end
235
+
236
+ private
237
+
238
+ def start_without_locking
239
+ raise AlreadyStarted, "Daemon '#{@identifier}' is already started" if daemon_is_running?
240
+
241
+ save_log_file_information
242
+ delete_pid_file
243
+ spawn_result = nil
244
+
245
+ begin
246
+ _, remaining_time = timeoutable(@start_timeout) do
247
+ allow_timeout { before_start }
248
+ spawn_result = allow_timeout { spawn_daemon }
249
+ daemon_spawned
250
+ record_activity
251
+
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
279
+ end
280
+ end
281
+
282
+ spawn_result
283
+ end
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
302
+ else
303
+ raise StartError, concat_spawn_output_and_logs(spawn_result.output, differences_in_log_file)
304
+ end
305
+
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
+
317
+ else
318
+ raise "Bug: unexpected result from #spawn_daemon: #{spawn_result.inspect}"
319
+ end
320
+ end
321
+
322
+ def before_start
323
+ if @before_start
324
+ @before_start.call
325
+ end
326
+ end
327
+
328
+ def spawn_daemon
329
+ if @start_command.respond_to?(:call)
330
+ run_command(@start_command.call)
331
+ else
332
+ run_command(@start_command)
333
+ end
334
+ end
335
+
336
+ def kill_daemon
337
+ if @stop_command
338
+ if @dont_stop_if_pid_file_invalid && read_pid_file.nil?
339
+ return
340
+ end
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}"
352
+ end
353
+ else
354
+ kill_daemon_with_signal
355
+ end
356
+ end
357
+
358
+ def kill_daemon_with_signal(force: false)
359
+ pid = read_pid_file
360
+ if pid
361
+ if force
362
+ Process.kill("SIGKILL", pid)
363
+ else
364
+ Process.kill(normalize_signal_name(@stop_graceful_signal), pid)
365
+ end
366
+ end
367
+ rescue Errno::ESRCH, Errno::ENOENT
368
+ end
369
+
370
+ def daemon_is_running?
371
+ pid = read_pid_file
372
+ if pid.nil?
373
+ nil
374
+ elsif check_pid(pid)
375
+ true
376
+ else
377
+ delete_pid_file
378
+ false
379
+ end
380
+ end
381
+
382
+ def read_pid_file
383
+ begin
384
+ pid = File.read(@pid_file).strip
385
+ rescue Errno::ENOENT
386
+ return nil
387
+ end
388
+ if /\A\d+\Z/.match?(pid)
389
+ pid.to_i
390
+ end
391
+ end
392
+
393
+ def delete_pid_file
394
+ File.unlink(@pid_file)
395
+ rescue Errno::EPERM, Errno::EACCES, Errno::ENOENT # ignore
396
+ end
397
+
398
+ def check_pid(pid)
399
+ Process.kill(0, pid)
400
+ true
401
+ rescue Errno::ESRCH
402
+ false
403
+ rescue Errno::EPERM
404
+ # We didn't have permission to kill the process. Either the process
405
+ # is owned by someone else, or the system has draconian security
406
+ # settings and we aren't allowed to kill *any* process. Assume that
407
+ # the process is running.
408
+ true
409
+ end
410
+
411
+ def wait_until(sleep_interval: 0.1)
412
+ until yield
413
+ sleep(sleep_interval)
414
+ end
415
+ end
416
+
417
+ def wait_until_pid_file_is_available_or_log_file_has_changed
418
+ until pid_file_available? || log_file_has_changed?
419
+ sleep 0.1
420
+ end
421
+ pid_file_is_available?
422
+ end
423
+
424
+ def wait_until_daemon_responds_to_ping_or_has_exited_or_log_file_has_changed
425
+ until run_ping_command || !daemon_is_running? || log_file_has_changed?
426
+ sleep(@ping_interval)
427
+ end
428
+ run_ping_command
429
+ end
430
+
431
+ def record_activity
432
+ @last_activity_time = Time.now
433
+ end
434
+
435
+ # Check whether there has been no recorded activity in the past +seconds+ seconds.
436
+ def no_activity?(seconds)
437
+ Time.now - @last_activity_time > seconds
438
+ end
439
+
440
+ def pid_file_available?
441
+ File.exist?(@pid_file) && File.stat(@pid_file).size != 0
442
+ end
443
+
444
+ # This method does nothing and only serves as a hook for the unit test.
445
+ def daemon_spawned
446
+ end
447
+
448
+ # This method does nothing and only serves as a hook for the unit test.
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
520
+ end
521
+
522
+ def save_log_file_information
523
+ @original_log_file_stat = begin
524
+ File.stat(@log_file)
525
+ rescue
526
+ nil
527
+ end
528
+ @current_log_file_stat = @original_log_file_stat
529
+ end
530
+
531
+ def log_file_has_changed?
532
+ if @current_log_file_stat
533
+ stat = begin
534
+ File.stat(@log_file)
535
+ rescue
536
+ nil
537
+ end
538
+ if stat
539
+ result = @current_log_file_stat.mtime != stat.mtime ||
540
+ @current_log_file_stat.size != stat.size
541
+ @current_log_file_stat = stat
542
+ result
543
+ else
544
+ true
545
+ end
546
+ else
547
+ false
548
+ end
549
+ end
550
+
551
+ def differences_in_log_file
552
+ if @original_log_file_stat && @original_log_file_stat.file?
553
+ File.open(@log_file, "r") do |f|
554
+ f.seek(@original_log_file_stat.size, IO::SEEK_SET)
555
+ f.read.strip
556
+ end
557
+ end
558
+ rescue Errno::ENOENT, Errno::ESPIPE
559
+ # ESPIPE means the log file is a pipe.
560
+ nil
561
+ end
562
+
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))
566
+ else
567
+ LockFile.new(File.absolute_path(pid_file + ".lock"))
568
+ end
569
+ end
570
+
571
+ def run_command(command)
572
+ if should_capture_output_while_running_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
+ }
585
+ else
586
+ spawn_options = {
587
+ in: "/dev/null",
588
+ out: :out,
589
+ err: :err,
590
+ close_others: true
591
+ }
592
+ end
593
+
594
+ if @keep_ios
595
+ @keep_ios.each do |io|
596
+ spawn_options[io] = io
597
+ end
598
+ end
599
+
600
+ pid = if @daemonize_for_me
601
+ Process.spawn(@env || {}, ruby_interpreter, SPAWNER_FILE,
602
+ command, spawn_options)
603
+ else
604
+ Process.spawn(@env || {}, command, spawn_options)
605
+ end
606
+
607
+ # run_command might be running in a timeout block (like
608
+ # in #start_without_locking).
609
+ begin
610
+ Process.waitpid(pid)
611
+ rescue Errno::ECHILD
612
+ # Maybe a background thread or whatever waitpid()'ed
613
+ # this child process before we had the chance. There's
614
+ # no way to obtain the exit status now. Assume that
615
+ # it started successfully; if it didn't we'll know
616
+ # that later by checking the PID file and by pinging
617
+ # it.
618
+ return InternalCommandOkResult.new(pid, tempfile_path ? File.read(tempfile_path).strip : nil)
619
+ rescue Timeout::Error
620
+ return InternalCommandTimeoutResult.new(pid, tempfile_path ? File.read(tempfile_path).strip : nil)
621
+ end
622
+
623
+ child_status = $?
624
+ output = File.read(tempfile_path).strip if tempfile_path
625
+ if child_status.success?
626
+ InternalCommandOkResult.new(pid, output)
627
+ else
628
+ InternalCommandErrorResult.new(pid, output, child_status)
629
+ end
630
+ ensure
631
+ begin
632
+ File.unlink(tempfile_path) if tempfile_path
633
+ rescue SystemCallError
634
+ nil
635
+ end
636
+ end
637
+
638
+ def should_capture_output_while_running_command?
639
+ if is_std_channel_chardev?(@log_file)
640
+ false
641
+ else
642
+ begin
643
+ real_log_file = Pathname.new(@log_file).realpath.to_s
644
+ rescue SystemCallError
645
+ real_log_file = nil
646
+ end
647
+ if real_log_file
648
+ !is_std_channel_chardev?(real_log_file)
649
+ else
650
+ true
651
+ end
652
+ end
653
+ end
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
+
663
+ def run_ping_command
664
+ if @ping_command.respond_to?(:call)
665
+ begin
666
+ value = @ping_command.call
667
+ if value.respond_to?(:close)
668
+ begin
669
+ value.close
670
+ rescue
671
+ nil
672
+ end
673
+ end
674
+ value
675
+ rescue *ALLOWED_CONNECT_EXCEPTIONS
676
+ false
677
+ end
678
+ elsif @ping_command.is_a?(Array)
679
+ type, *args = @ping_command
680
+ if self.class.can_ping_unix_sockets?
681
+ case type
682
+ when :tcp
683
+ hostname, port = args
684
+ sockaddr = Socket.pack_sockaddr_in(port, hostname)
685
+ ping_tcp_socket(sockaddr)
686
+ when :unix
687
+ socket_domain = Socket::Constants::AF_LOCAL
688
+ sockaddr = Socket.pack_sockaddr_un(args[0])
689
+ ping_socket(socket_domain, sockaddr)
690
+ else
691
+ raise ArgumentError, "Unknown ping command type #{type.inspect}"
692
+ end
693
+ else
694
+ case type
695
+ when :tcp
696
+ hostname, port = args
697
+ ping_socket(hostname, port)
698
+ when :unix
699
+ raise "Pinging Unix domain sockets is not supported on this Ruby implementation"
700
+ else
701
+ raise ArgumentError, "Unknown ping command type #{type.inspect}"
702
+ end
703
+ end
704
+ else
705
+ system(@ping_command)
706
+ end
707
+ end
708
+
709
+ if !can_ping_unix_sockets?
710
+ require "java"
711
+
712
+ def ping_socket(host_name, port)
713
+ channel = java.nio.channels.SocketChannel.open
714
+ begin
715
+ address = java.net.InetSocketAddress.new(host_name, port)
716
+ channel.configure_blocking(false)
717
+ if channel.connect(address)
718
+ return true
719
+ end
720
+
721
+ deadline = Time.now.to_f + 0.1
722
+ while true
723
+ begin
724
+ if channel.finish_connect
725
+ return true
726
+ end
727
+ rescue java.net.ConnectException => e
728
+ if /Connection refused/i.match?(e.message)
729
+ return false
730
+ else
731
+ throw e
732
+ end
733
+ end
734
+
735
+ # Not done connecting and no error.
736
+ sleep 0.01
737
+ if Time.now.to_f >= deadline
738
+ return false
739
+ end
740
+ end
741
+ ensure
742
+ channel.close
743
+ end
744
+ end
745
+ else
746
+ def ping_socket(socket_domain, sockaddr)
747
+ socket = Socket.new(socket_domain, Socket::Constants::SOCK_STREAM, 0)
748
+ begin
749
+ socket.connect_nonblock(sockaddr)
750
+ rescue Errno::ENOENT, Errno::EINPROGRESS, Errno::EAGAIN, Errno::EWOULDBLOCK
751
+ if select(nil, [socket], nil, 0.1)
752
+ begin
753
+ socket.connect_nonblock(sockaddr)
754
+ rescue Errno::EISCONN
755
+ rescue Errno::EINVAL
756
+ if RUBY_PLATFORM.match?(/freebsd/i)
757
+ raise Errno::ECONNREFUSED
758
+ else
759
+ raise
760
+ end
761
+ end
762
+ else
763
+ raise Errno::ECONNREFUSED
764
+ end
765
+ end
766
+ true
767
+ rescue Errno::ECONNREFUSED, Errno::ENOENT
768
+ false
769
+ ensure
770
+ socket.close if socket
771
+ end
772
+
773
+ def ping_tcp_socket(sockaddr)
774
+ ping_socket(Socket::Constants::AF_INET, sockaddr)
775
+ rescue Errno::EAFNOSUPPORT
776
+ ping_socket(Socket::Constants::AF_INET6, sockaddr)
777
+ end
778
+ end
779
+
780
+ def ruby_interpreter
781
+ rb_config = if defined?(RbConfig)
782
+ RbConfig::CONFIG
783
+ else
784
+ Config::CONFIG
785
+ end
786
+ File.join(
787
+ rb_config["bindir"],
788
+ rb_config["RUBY_INSTALL_NAME"]
789
+ ) + rb_config["EXEEXT"]
790
+ end
791
+
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]
797
+ end
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)
806
+ end
807
+
808
+ def signal_termination_message(process_status)
809
+ if process_status.signaled?
810
+ "terminated with signal #{signal_name_for(process_status.termsig)}"
811
+ else
812
+ "exited with status #{process_status.exitstatus}"
813
+ end
814
+ end
815
+
816
+ def normalize_signal_name(name)
817
+ name.start_with?("SIG") ? name : "SIG#{name}"
818
+ end
819
+
820
+ def signal_name_for(num)
821
+ if (name = Signal.list.find { |name, n| n == num }[0])
822
+ "SIG#{name}"
823
+ else
824
+ num.to_s
825
+ end
826
+ end
827
+
828
+ def concat_spawn_output_and_logs(output, logs, exit_status = nil, suffix_message = nil)
829
+ if output.nil? && logs.nil?
830
+ result_inner = [
831
+ "logs not available",
832
+ exit_status ? signal_termination_message(exit_status) : nil,
833
+ suffix_message
834
+ ].compact.join("; ")
835
+ "(#{result_inner})"
836
+ elsif (output && output.empty? && logs && logs.empty?) || (output && output.empty? && logs.nil?) || (output.nil? && logs && logs.empty?)
837
+ result_inner = [
838
+ "logs empty",
839
+ exit_status ? signal_termination_message(exit_status) : nil,
840
+ suffix_message
841
+ ].compact.join("; ")
842
+ "(#{result_inner})"
843
+ else
844
+ result = ((output || "") + "\n" + (logs || "")).strip
845
+ result_suffix = [
846
+ exit_status ? signal_termination_message(exit_status) : nil,
847
+ suffix_message
848
+ ].compact.join("; ")
849
+ if !result_suffix.empty?
850
+ result << "\n(#{result_suffix})"
851
+ end
852
+ result
853
+ end
854
+ end
855
+
856
+ def debug(message)
857
+ @logger.debug(message) if @logger
858
+ end
873
859
  end