daemon_controller 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,8 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "daemon_controller"
3
3
  # Don't forget to update version.rb too.
4
- s.version = "1.0.0"
5
- s.date = "2012-02-04"
4
+ s.version = "1.1.0"
5
+ s.date = "2012-10-27"
6
6
  s.summary = "A library for implementing daemon management capabilities"
7
7
  s.email = "hongli@phusion.nl"
8
8
  s.homepage = "https://github.com/FooBarWidget/daemon_controller"
@@ -22,15 +22,12 @@
22
22
  require 'tempfile'
23
23
  require 'fcntl'
24
24
  require 'timeout'
25
-
26
- libdir = File.expand_path(File.dirname(__FILE__))
27
- $LOAD_PATH.unshift(libdir)
28
- require 'daemon_controller/lock_file'
29
-
30
25
  if Process.respond_to?(:spawn)
31
26
  require 'rbconfig'
32
27
  end
33
28
 
29
+ require 'daemon_controller/lock_file'
30
+
34
31
  # Main daemon controller object. See the README for an introduction and tutorial.
35
32
  class DaemonController
36
33
  ALLOWED_CONNECT_EXCEPTIONS = [Errno::ECONNREFUSED, Errno::ENETUNREACH,
@@ -123,6 +120,13 @@ class DaemonController
123
120
  #
124
121
  # The default value is +nil+.
125
122
  #
123
+ # [:restart_command]
124
+ # A command to restart the daemon with, e.g. "/etc/rc.d/nginx restart". If
125
+ # no restart command is given (i.e. +nil+), then DaemonController will
126
+ # restart the daemon by calling #stop and #start.
127
+ #
128
+ # The default value is +nil+.
129
+ #
126
130
  # [:before_start]
127
131
  # This may be a Proc. It will be called just before running the start command.
128
132
  # The before_start proc is not subject to the start timeout.
@@ -173,6 +177,10 @@ class DaemonController
173
177
  # descriptors except stdin, stdout and stderr. However if there are any file
174
178
  # descriptors you want to keep open, specify the IO objects here. This must be
175
179
  # an array of IO objects.
180
+ #
181
+ # [:env]
182
+ # This must be a Hash. The hash will contain the environment variables available
183
+ # to be made available to the daemon. Hash keys must be strings, not symbols.
176
184
  def initialize(options)
177
185
  [:identifier, :start_command, :ping_command, :pid_file, :log_file].each do |option|
178
186
  if !options.has_key?(option)
@@ -183,6 +191,7 @@ class DaemonController
183
191
  @start_command = options[:start_command]
184
192
  @stop_command = options[:stop_command]
185
193
  @ping_command = options[:ping_command]
194
+ @restart_command = options[:restart_command]
186
195
  @ping_interval = options[:ping_interval] || 0.1
187
196
  @pid_file = options[:pid_file]
188
197
  @log_file = options[:log_file]
@@ -193,6 +202,7 @@ class DaemonController
193
202
  @daemonize_for_me = options[:daemonize_for_me]
194
203
  @keep_ios = options[:keep_ios] || []
195
204
  @lock_file = determine_lock_file(options, @identifier, @pid_file)
205
+ @env = options[:env] || {}
196
206
  end
197
207
 
198
208
  # Start the daemon and wait until it can be pinged.
@@ -289,6 +299,17 @@ class DaemonController
289
299
  end
290
300
  end
291
301
 
302
+ # Restarts the daemon. Uses the restart_command if provided, otherwise
303
+ # calls #stop and #start.
304
+ def restart
305
+ if @restart_command
306
+ run_command(@restart_command)
307
+ else
308
+ stop
309
+ start
310
+ end
311
+ end
312
+
292
313
  # Returns the daemon's PID, as reported by its PID file. Returns the PID
293
314
  # as an integer, or nil there is no valid PID in the PID file.
294
315
  #
@@ -315,6 +336,12 @@ class DaemonController
315
336
  end
316
337
  end
317
338
 
339
+ # Checks whether ping Unix domain sockets is supported. Currently
340
+ # this is supported on all Ruby implementations, except JRuby.
341
+ def self.can_ping_unix_sockets?
342
+ return RUBY_PLATFORM != "java"
343
+ end
344
+
318
345
  private
319
346
  def start_without_locking
320
347
  if daemon_is_running?
@@ -558,6 +585,11 @@ private
558
585
  def self.fork_supported?
559
586
  return RUBY_PLATFORM != "java" && RUBY_PLATFORM !~ /win32/
560
587
  end
588
+
589
+ def self.spawn_supported?
590
+ # Process.spawn doesn't work very well in JRuby.
591
+ return Process.respond_to?(:spawn) && RUBY_PLATFORM != "java"
592
+ end
561
593
 
562
594
  def run_command(command)
563
595
  # Create tempfile for storing the command's output.
@@ -566,7 +598,7 @@ private
566
598
  File.chmod(0666, tempfile_path)
567
599
  tempfile.close
568
600
 
569
- if self.class.fork_supported? || Process.respond_to?(:spawn)
601
+ if self.class.fork_supported? || self.class.spawn_supported?
570
602
  if Process.respond_to?(:spawn)
571
603
  options = {
572
604
  :in => "/dev/null",
@@ -578,14 +610,10 @@ private
578
610
  options[io] = io
579
611
  end
580
612
  if @daemonize_for_me
581
- ruby_interpreter = File.join(
582
- Config::CONFIG['bindir'],
583
- Config::CONFIG['RUBY_INSTALL_NAME']
584
- ) + Config::CONFIG['EXEEXT']
585
- pid = Process.spawn(ruby_interpreter, SPAWNER_FILE,
613
+ pid = Process.spawn(@env, ruby_interpreter, SPAWNER_FILE,
586
614
  command, options)
587
615
  else
588
- pid = Process.spawn(command, options)
616
+ pid = Process.spawn(@env, command, options)
589
617
  end
590
618
  else
591
619
  pid = safe_fork(@daemonize_for_me) do
@@ -597,6 +625,7 @@ private
597
625
  STDIN.reopen("/dev/null", "r")
598
626
  STDOUT.reopen(tempfile_path, "w")
599
627
  STDERR.reopen(tempfile_path, "w")
628
+ ENV.update(@env)
600
629
  exec(command)
601
630
  end
602
631
  end
@@ -642,8 +671,14 @@ private
642
671
  raise StartError, File.read(tempfile_path).strip
643
672
  end
644
673
  else
674
+ if @env && !@env.empty?
675
+ raise "Setting the :env option is not supported on this Ruby implementation."
676
+ elsif @daemonize_for_me
677
+ raise "Setting the :daemonize_for_me option is not supported on this Ruby implementation."
678
+ end
679
+
645
680
  cmd = "#{command} >\"#{tempfile_path}\""
646
- cmd += " 2>\"#{tempfile_path}\"" unless PLATFORM =~ /mswin/
681
+ cmd << " 2>\"#{tempfile_path}\"" unless PLATFORM =~ /mswin/
647
682
  if !system(cmd)
648
683
  raise StartError, File.read(tempfile_path).strip
649
684
  end
@@ -665,19 +700,74 @@ private
665
700
  end
666
701
  elsif @ping_command.is_a?(Array)
667
702
  type, *args = @ping_command
668
-
669
- case type
670
- when :tcp
671
- socket_domain = Socket::Constants::AF_INET
672
- hostname, port = args
673
- sockaddr = Socket.pack_sockaddr_in(port, hostname)
674
- when :unix
675
- socket_domain = Socket::Constants::AF_LOCAL
676
- sockaddr = Socket.pack_sockaddr_un(args[0])
703
+ if self.class.can_ping_unix_sockets?
704
+ case type
705
+ when :tcp
706
+ socket_domain = Socket::Constants::AF_INET
707
+ hostname, port = args
708
+ sockaddr = Socket.pack_sockaddr_in(port, hostname)
709
+ when :unix
710
+ socket_domain = Socket::Constants::AF_LOCAL
711
+ sockaddr = Socket.pack_sockaddr_un(args[0])
712
+ else
713
+ raise ArgumentError, "Unknown ping command type #{type.inspect}"
714
+ end
715
+ return ping_socket(socket_domain, sockaddr)
677
716
  else
678
- raise ArgumentError, "Unknown ping command type #{type.inspect}"
717
+ case type
718
+ when :tcp
719
+ hostname, port = args
720
+ return ping_socket(hostname, port)
721
+ when :unix
722
+ raise "Pinging Unix domain sockets is not supported on this Ruby implementation"
723
+ else
724
+ raise ArgumentError, "Unknown ping command type #{type.inspect}"
725
+ end
679
726
  end
727
+ else
728
+ return system(@ping_command)
729
+ end
730
+ end
680
731
 
732
+ if !can_ping_unix_sockets?
733
+ require 'java'
734
+
735
+ def ping_socket(host_name, port)
736
+ channel = java.nio.channels.SocketChannel.open
737
+ begin
738
+ address = java.net.InetSocketAddress.new(host_name, port)
739
+ channel.configure_blocking(false)
740
+ if channel.connect(address)
741
+ return true
742
+ end
743
+
744
+ deadline = Time.now.to_f + 0.1
745
+ done = false
746
+ while true
747
+ begin
748
+ if channel.finish_connect
749
+ return true
750
+ end
751
+ rescue java.net.ConnectException => e
752
+ if e.message =~ /Connection refused/i
753
+ return false
754
+ else
755
+ throw e
756
+ end
757
+ end
758
+
759
+ # Not done connecting and no error.
760
+ sleep 0.01
761
+ if Time.now.to_f >= deadline
762
+ return false
763
+ end
764
+ end
765
+ ensure
766
+ channel.close
767
+ end
768
+ end
769
+ else
770
+ def ping_socket(socket_domain, sockaddr)
681
771
  begin
682
772
  socket = Socket.new(socket_domain, Socket::Constants::SOCK_STREAM, 0)
683
773
  begin
@@ -698,11 +788,16 @@ private
698
788
  ensure
699
789
  socket.close if socket
700
790
  end
701
- else
702
- return system(@ping_command)
703
791
  end
704
792
  end
705
793
 
794
+ def ruby_interpreter
795
+ File.join(
796
+ Config::CONFIG['bindir'],
797
+ Config::CONFIG['RUBY_INSTALL_NAME']
798
+ ) + Config::CONFIG['EXEEXT']
799
+ end
800
+
706
801
  def safe_fork(double_fork)
707
802
  pid = fork
708
803
  if pid.nil?
@@ -78,7 +78,7 @@ class LockFile
78
78
  # The lock file *must* be writable, otherwise an Errno::EACCESS
79
79
  # exception will be raised.
80
80
  def shared_lock
81
- File.open(@filename, 'w') do |f|
81
+ File.open(@filename, 'w+') do |f|
82
82
  if Fcntl.const_defined? :F_SETFD
83
83
  f.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
84
84
  end
@@ -93,7 +93,7 @@ class LockFile
93
93
  #
94
94
  # If a lock can be obtained, then the given block will be yielded.
95
95
  def try_shared_lock
96
- File.open(@filename, 'w') do |f|
96
+ File.open(@filename, 'w+') do |f|
97
97
  if Fcntl.const_defined? :F_SETFD
98
98
  f.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
99
99
  end
@@ -19,6 +19,8 @@
19
19
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
20
  # THE SOFTWARE.
21
21
 
22
- # Used on Ruby 1.9 because forking may not be safe/supported on all platforms.
22
+ # This helper script is used for daemonizing a command by executing it and
23
+ # then exiting ourselves. Used on Ruby 1.9 and JRuby because forking may not
24
+ # be safe/supported on all platforms.
23
25
  Process.setsid
24
26
  Process.spawn(ARGV[0])
@@ -21,7 +21,7 @@
21
21
 
22
22
  class DaemonController
23
23
  MAJOR = 1
24
- MINOR = 0
24
+ MINOR = 1
25
25
  TINY = 0
26
26
  VERSION_STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
27
27
  end # class DaemonController
@@ -128,7 +128,7 @@ describe DaemonController, "#start" do
128
128
  end
129
129
  end
130
130
 
131
- if DaemonController.send(:fork_supported?) || Process.respond_to?(:spawn)
131
+ if DaemonController.send(:fork_supported?) || DaemonController.send(:spawn_supported?)
132
132
  it "kills the daemon if it doesn't start in time and hasn't forked " <<
133
133
  "yet, on platforms where Ruby supports fork() or Process.spawn" do
134
134
  begin
@@ -209,7 +209,7 @@ describe DaemonController, "#start" do
209
209
  log.should == ["before_start", "start_command"]
210
210
  end
211
211
 
212
- if DaemonController.send(:fork_supported?) || Process.respond_to?(:spawn)
212
+ if DaemonController.send(:fork_supported?) || DaemonController.send(:spawn_supported?)
213
213
  it "keeps the file descriptors in 'keep_ios' open" do
214
214
  a, b = IO.pipe
215
215
  begin
@@ -234,6 +234,14 @@ describe DaemonController, "#start" do
234
234
  @controller.stop
235
235
  end
236
236
  end
237
+
238
+ it "receives environment variables" do
239
+ new_controller(:env => {'ENV_FILE' => 'spec/env_file.tmp'})
240
+ File.unlink('spec/env_file.tmp') if File.exist?('spec/env_file.tmp')
241
+ @controller.start
242
+ File.exist?('spec/env_file.tmp').should be_true
243
+ @controller.stop
244
+ end
237
245
  end
238
246
 
239
247
  describe DaemonController, "#stop" do
@@ -291,6 +299,36 @@ describe DaemonController, "#stop" do
291
299
  end
292
300
  end
293
301
 
302
+ describe DaemonController, "#restart" do
303
+ include TestHelper
304
+
305
+ before :each do
306
+ new_controller
307
+ end
308
+
309
+ it "raises no exception if the daemon is not running" do
310
+ @controller.restart
311
+ end
312
+
313
+ describe 'with no restart command' do
314
+ it "restart the daemon using stop and start" do
315
+ @controller.should_receive(:stop)
316
+ @controller.should_receive(:start)
317
+ @controller.restart
318
+ end
319
+ end
320
+
321
+ describe 'with a restart_command' do
322
+ it 'restarts the daemon using the restart_command' do
323
+ stop_cmd = "echo 'hello world'"
324
+ new_controller :restart_command => stop_cmd
325
+
326
+ @controller.should_receive(:run_command).with(stop_cmd)
327
+ @controller.restart
328
+ end
329
+ end
330
+ end
331
+
294
332
  describe DaemonController, "#connect" do
295
333
  include TestHelper
296
334
 
@@ -321,6 +359,11 @@ end
321
359
 
322
360
  describe DaemonController do
323
361
  include TestHelper
362
+
363
+ after :each do
364
+ @server.close if @server && !@server.closed?
365
+ File.unlink('spec/foo.sock') rescue nil
366
+ end
324
367
 
325
368
  specify "if the ping command is a block that raises Errno::ECONNREFUSED, then that's " <<
326
369
  "an indication that the daemon cannot be connected to" do
@@ -332,57 +375,50 @@ describe DaemonController do
332
375
 
333
376
  specify "if the ping command is a block that returns an object that responds to #close, " <<
334
377
  "then the close method will be called on that object" do
335
- server = TCPServer.new('localhost', 8278)
336
- begin
337
- socket = nil
338
- new_controller(:ping_command => lambda do
339
- socket = TCPSocket.new('localhost', 8278)
340
- end)
341
- @controller.send(:run_ping_command)
342
- socket.should be_closed
343
- ensure
344
- server.close
345
- end
378
+ @server = TCPServer.new('localhost', 8278)
379
+ socket = nil
380
+ new_controller(:ping_command => lambda do
381
+ socket = TCPSocket.new('localhost', 8278)
382
+ end)
383
+ @controller.send(:run_ping_command)
384
+ socket.should be_closed
346
385
  end
347
386
 
348
387
  specify "if the ping command is a block that returns an object that responds to #close, " <<
349
388
  "and #close raises an exception, then that exception is ignored" do
350
- server = TCPServer.new('localhost', 8278)
351
- begin
352
- o = Object.new
353
- o.should_receive(:close).and_return do
354
- raise StandardError, "foo"
355
- end
356
- new_controller(:ping_command => lambda do
357
- o
358
- end)
359
- lambda { @controller.send(:run_ping_command) }.should_not raise_error(StandardError)
360
- ensure
361
- server.close
389
+ @server = TCPServer.new('localhost', 8278)
390
+ o = Object.new
391
+ o.should_receive(:close).and_return do
392
+ raise StandardError, "foo"
362
393
  end
394
+ new_controller(:ping_command => lambda do
395
+ o
396
+ end)
397
+ lambda { @controller.send(:run_ping_command) }.should_not raise_error(StandardError)
363
398
  end
364
399
 
365
400
  specify "the ping command may be [:tcp, hostname, port]" do
366
- new_controller(:ping_command => [:tcp, "localhost", 8278])
401
+ new_controller(:ping_command => [:tcp, "127.0.0.1", 8278])
367
402
  @controller.send(:run_ping_command).should be_false
368
403
 
369
- server = TCPServer.new('localhost', 8278)
370
- begin
371
- @controller.send(:run_ping_command).should be_true
372
- ensure
373
- server.close
374
- end
404
+ @server = TCPServer.new('127.0.0.1', 8278)
405
+ @controller.send(:run_ping_command).should be_true
375
406
  end
376
407
 
377
- specify "the ping command may be [:unix, filename]" do
378
- new_controller(:ping_command => [:unix, "spec/foo.sock"])
379
- @controller.send(:run_ping_command).should be_false
408
+ if DaemonController.can_ping_unix_sockets?
409
+ specify "the ping command may be [:unix, filename]" do
410
+ new_controller(:ping_command => [:unix, "spec/foo.sock"])
411
+ @controller.send(:run_ping_command).should be_false
380
412
 
381
- server = UNIXServer.new('spec/foo.sock')
382
- begin
413
+ @server = UNIXServer.new('spec/foo.sock')
383
414
  @controller.send(:run_ping_command).should be_true
384
- ensure
385
- server.close
415
+ end
416
+ else
417
+ specify "a ping command of type [:unix, filename] is not supported on this Ruby implementation" do
418
+ new_controller(:ping_command => [:unix, "spec/foo.sock"])
419
+ @server = UNIXServer.new('spec/foo.sock')
420
+ lambda { @controller.send(:run_ping_command) }.should raise_error(
421
+ "Pinging Unix domain sockets is not supported on this Ruby implementation")
386
422
  end
387
423
  end
388
424
  end
@@ -1,5 +1,8 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/ruby
2
2
  # A simple echo server, used by the unit test.
3
+ # The hashbang is explicitly set to /usr/bin/ruby because we need
4
+ # a Ruby implementation that starts fast and supports forking. The
5
+ # Ruby in $PATH may be JRuby which is neither.
3
6
  require 'socket'
4
7
  require 'optparse'
5
8
 
@@ -61,6 +64,10 @@ if options[:pid_file]
61
64
  end
62
65
  end
63
66
 
67
+ if ENV['ENV_FILE']
68
+ options[:env_file] = File.expand_path(ENV['ENV_FILE'])
69
+ end
70
+
64
71
  def main(options)
65
72
  STDIN.reopen("/dev/null", 'r')
66
73
  STDOUT.reopen(options[:log_file], 'a')
@@ -69,6 +76,15 @@ def main(options)
69
76
  STDERR.sync = true
70
77
  Dir.chdir(options[:chdir])
71
78
  File.umask(0)
79
+
80
+ if options[:env_file]
81
+ File.open(options[:env_file], 'w') do |f|
82
+ f.write("\0")
83
+ end
84
+ at_exit do
85
+ File.unlink(options[:env_file]) rescue nil
86
+ end
87
+ end
72
88
 
73
89
  if options[:pid_file]
74
90
  sleep(options[:wait1])
@@ -122,4 +138,4 @@ if options[:daemonize]
122
138
  end
123
139
  else
124
140
  main(options)
125
- end
141
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: daemon_controller
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 0
10
- version: 1.0.0
10
+ version: 1.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Hongli Lai
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-02-04 00:00:00 Z
18
+ date: 2012-10-27 00:00:00 Z
19
19
  dependencies: []
20
20
 
21
21
  description: A library for robust daemon management.