daemon_controller 0.2.3 → 0.2.4

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.
data/README.markdown CHANGED
@@ -29,10 +29,10 @@ It provides the following functionality:
29
29
 
30
30
  gem install daemon_controller
31
31
 
32
-
33
32
  ## Resources
34
33
 
35
34
  * [Website](http://github.com/FooBarWidget/daemon_controller)
35
+ * [RDoc](http://rdoc.info/projects/FooBarWidget/daemon_controller)
36
36
  * [Git repository](git://github.com/FooBarWidget/daemon_controller.git)
37
37
  * [RubyForge project](http://rubyforge.org/projects/daemoncontrol/)
38
38
 
@@ -1,7 +1,7 @@
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 = "0.2.3"
4
+ s.version = "0.2.4"
5
5
  s.date = "2009-11-13"
6
6
  s.summary = "A library for implementing daemon management capabilities"
7
7
  s.email = "hongli@phusion.nl"
@@ -1,5 +1,5 @@
1
1
  # daemon_controller, library for robust daemon management
2
- # Copyright (c) 2008 Phusion
2
+ # Copyright (c) 2010 Phusion
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -22,7 +22,14 @@
22
22
  require 'tempfile'
23
23
  require 'fcntl'
24
24
  require 'timeout'
25
- require File.expand_path(File.dirname(__FILE__) << '/daemon_controller/lock_file')
25
+
26
+ libdir = File.expand_path(File.dirname(__FILE__))
27
+ $LOAD_PATH.unshift(libdir)
28
+ require 'daemon_controller/lock_file'
29
+
30
+ if Process.respond_to?(:spawn)
31
+ require 'rbconfig'
32
+ end
26
33
 
27
34
  # Main daemon controller object. See the README for an introduction and tutorial.
28
35
  class DaemonController
@@ -30,6 +37,9 @@ class DaemonController
30
37
  Errno::ETIMEDOUT, Errno::ECONNRESET, Errno::EINVAL,
31
38
  Errno::EADDRNOTAVAIL]
32
39
 
40
+ SPAWNER_FILE = File.expand_path(File.join(File.dirname(__FILE__),
41
+ "daemon_controller", "spawn.rb"))
42
+
33
43
  class Error < StandardError
34
44
  end
35
45
  class TimeoutError < Error
@@ -125,8 +135,8 @@ class DaemonController
125
135
  # [:log_file_activity_timeout]
126
136
  # Once a daemon has gone into the background, it will become difficult to
127
137
  # know for certain whether it is still initializing or whether it has
128
- # failed and exited, until it has written its PID file. It's 99.9% probable
129
- # that the daemon has terminated with an if its start timeout has expired,
138
+ # failed and exited, until it has written its PID file. Suppose that it
139
+ # failed with an error after daemonizing but before it has written its PID file;
130
140
  # not many system administrators want to wait 15 seconds (the default start
131
141
  # timeout) to be notified of whether the daemon has terminated with an error.
132
142
  #
@@ -137,6 +147,21 @@ class DaemonController
137
147
  # have terminated with an error.
138
148
  #
139
149
  # The default value is 7.
150
+ #
151
+ # [:daemonize_for_me]
152
+ # Normally daemon_controller will wait until the daemon has daemonized into the
153
+ # background, in order to capture any errors that it may print on stdout or
154
+ # stderr before daemonizing. However, if the daemon doesn't support daemonization
155
+ # for some reason, then setting this option to true will cause daemon_controller
156
+ # to do the daemonization for the daemon.
157
+ #
158
+ # The default is false.
159
+ #
160
+ # [:keep_ios]
161
+ # Upon spawning the daemon, daemon_controller will normally close all file
162
+ # descriptors except stdin, stdout and stderr. However if there are any file
163
+ # descriptors you want to keep open, specify the IO objects here. This must be
164
+ # an array of IO objects.
140
165
  def initialize(options)
141
166
  [:identifier, :start_command, :ping_command, :pid_file, :log_file].each do |option|
142
167
  if !options.has_key?(option)
@@ -154,6 +179,8 @@ class DaemonController
154
179
  @start_timeout = options[:start_timeout] || 15
155
180
  @stop_timeout = options[:stop_timeout] || 15
156
181
  @log_file_activity_timeout = options[:log_file_activity_timeout] || 7
182
+ @daemonize_for_me = options[:daemonize_for_me]
183
+ @keep_ios = options[:keep_ios] || []
157
184
  @lock_file = determine_lock_file(@identifier, @pid_file)
158
185
  end
159
186
 
@@ -511,16 +538,31 @@ private
511
538
 
512
539
  if self.class.fork_supported? || Process.respond_to?(:spawn)
513
540
  if Process.respond_to?(:spawn)
514
- pid = Process.spawn(command,
515
- :in => "/dev/null",
516
- :out => tempfile_path,
517
- :err => tempfile_path,
541
+ options = {
542
+ STDIN => "/dev/null",
543
+ STDOUT => tempfile_path,
544
+ STDERR => tempfile_path,
518
545
  :close_others => true
519
- )
546
+ }
547
+ @keep_ios.each do |io|
548
+ options[io] = io
549
+ end
550
+ if @daemonize_for_me
551
+ ruby_interpreter = File.join(
552
+ Config::CONFIG['bindir'],
553
+ Config::CONFIG['RUBY_INSTALL_NAME']
554
+ ) + Config::CONFIG['EXEEXT']
555
+ pid = Process.spawn(ruby_interpreter, SPAWNER_FILE,
556
+ command, options)
557
+ else
558
+ pid = Process.spawn(command, options)
559
+ end
520
560
  else
521
- pid = safe_fork do
561
+ pid = safe_fork(@daemonize_for_me) do
522
562
  ObjectSpace.each_object(IO) do |obj|
523
- obj.close rescue nil
563
+ if !@keep_ios.include?(obj)
564
+ obj.close rescue nil
565
+ end
524
566
  end
525
567
  STDIN.reopen("/dev/null", "r")
526
568
  STDOUT.reopen(tempfile_path, "w")
@@ -594,11 +636,18 @@ private
594
636
  end
595
637
  end
596
638
 
597
- def safe_fork
639
+ def safe_fork(double_fork)
598
640
  pid = fork
599
641
  if pid.nil?
600
642
  begin
601
- yield
643
+ if double_fork
644
+ pid2 = fork
645
+ if pid2.nil?
646
+ yield
647
+ end
648
+ else
649
+ yield
650
+ end
602
651
  rescue Exception => e
603
652
  message = "*** Exception #{e.class} " <<
604
653
  "(#{e}) (process #{$$}):\n" <<
@@ -609,7 +658,12 @@ private
609
658
  exit!
610
659
  end
611
660
  else
612
- return pid
661
+ if double_fork
662
+ Process.waitpid(pid) rescue nil
663
+ return pid
664
+ else
665
+ return pid
666
+ end
613
667
  end
614
668
  end
615
669
 
@@ -1,5 +1,5 @@
1
1
  # daemon_controller, library for robust daemon management
2
- # Copyright (c) 2008 Phusion
2
+ # Copyright (c) 2010 Phusion
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -1,5 +1,5 @@
1
1
  # daemon_controller, library for robust daemon management
2
- # Copyright (c) 2009 Phusion
2
+ # Copyright (c) 2010 Phusion
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -22,6 +22,6 @@
22
22
  class DaemonController
23
23
  MAJOR = 0
24
24
  MINOR = 2
25
- TINY = 3
25
+ TINY = 4
26
26
  VERSION_STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
27
27
  end # class DaemonController
@@ -12,6 +12,7 @@ describe DaemonController, "#start" do
12
12
 
13
13
  it "works" do
14
14
  @controller.start
15
+ ping_echo_server.should be_true
15
16
  @controller.stop
16
17
  end
17
18
 
@@ -186,6 +187,30 @@ describe DaemonController, "#start" do
186
187
  end
187
188
  log.should == ["before_start", "start_command"]
188
189
  end
190
+
191
+ it "keeps the file descriptors in 'keep_ios' open" do
192
+ a, b = IO.pipe
193
+ begin
194
+ new_controller(:keep_ios => [b])
195
+ begin
196
+ @controller.start
197
+ b.close
198
+ select([a], nil, nil, 0).should be_nil
199
+ ensure
200
+ @controller.stop
201
+ end
202
+ ensure
203
+ a.close if !a.closed?
204
+ b.close if !b.closed?
205
+ end
206
+ end
207
+
208
+ it "performs the daemonization on behalf of the daemon if 'daemonize_for_me' is set" do
209
+ new_controller(:no_daemonize => true, :daemonize_for_me => true)
210
+ @controller.start
211
+ ping_echo_server.should be_true
212
+ @controller.stop
213
+ end
189
214
  end
190
215
 
191
216
  describe DaemonController, "#stop" do
data/spec/echo_server.rb CHANGED
@@ -9,7 +9,8 @@ options = {
9
9
  :log_file => "/dev/null",
10
10
  :wait1 => 0,
11
11
  :wait2 => 0,
12
- :stop_time => 0
12
+ :stop_time => 0,
13
+ :daemonize => true
13
14
  }
14
15
  parser = OptionParser.new do |opts|
15
16
  opts.banner = "Usage: echo_server.rb [options]"
@@ -40,6 +41,9 @@ parser = OptionParser.new do |opts|
40
41
  opts.on("--crash-before-bind", "Whether the daemon should crash before binding the server socket.") do
41
42
  options[:crash_before_bind] = true
42
43
  end
44
+ opts.on("--no-daemonize", "Don't daemonize.") do
45
+ options[:daemonize] = false
46
+ end
43
47
  end
44
48
  begin
45
49
  parser.parse!
@@ -57,60 +61,65 @@ if options[:pid_file]
57
61
  end
58
62
  end
59
63
 
60
- pid = fork do
61
- Process.setsid
62
- fork do
63
- STDIN.reopen("/dev/null", 'r')
64
- STDOUT.reopen(options[:log_file], 'a')
65
- STDERR.reopen(options[:log_file], 'a')
66
- STDOUT.sync = true
67
- STDERR.sync = true
68
- Dir.chdir(options[:chdir])
69
- File.umask(0)
70
-
71
- if options[:pid_file]
72
- sleep(options[:wait1])
73
- File.open(options[:pid_file], 'w') do |f|
74
- f.write(Process.pid)
75
- end
76
- at_exit do
77
- File.unlink(options[:pid_file]) rescue nil
78
- end
64
+ def main(options)
65
+ STDIN.reopen("/dev/null", 'r')
66
+ STDOUT.reopen(options[:log_file], 'a')
67
+ STDERR.reopen(options[:log_file], 'a')
68
+ STDOUT.sync = true
69
+ STDERR.sync = true
70
+ Dir.chdir(options[:chdir])
71
+ File.umask(0)
72
+
73
+ if options[:pid_file]
74
+ sleep(options[:wait1])
75
+ File.open(options[:pid_file], 'w') do |f|
76
+ f.write(Process.pid)
79
77
  end
80
-
81
- sleep(options[:wait2])
82
- if options[:crash_before_bind]
83
- puts "#{Time.now}: crashing, as instructed."
84
- exit 2
78
+ at_exit do
79
+ File.unlink(options[:pid_file]) rescue nil
85
80
  end
86
-
87
- server = TCPServer.new('127.0.0.1', options[:port])
88
- begin
89
- puts "*** #{Time.now}: echo server started"
90
- while (client = server.accept)
91
- puts "#{Time.now}: new client"
92
- begin
93
- while (line = client.readline)
94
- puts "#{Time.now}: client sent: #{line.strip}"
95
- client.puts(line)
96
- end
97
- rescue EOFError
98
- ensure
99
- puts "#{Time.now}: connection closed"
100
- client.close rescue nil
81
+ end
82
+
83
+ sleep(options[:wait2])
84
+ if options[:crash_before_bind]
85
+ puts "#{Time.now}: crashing, as instructed."
86
+ exit 2
87
+ end
88
+
89
+ server = TCPServer.new('127.0.0.1', options[:port])
90
+ begin
91
+ puts "*** #{Time.now}: echo server started"
92
+ while (client = server.accept)
93
+ puts "#{Time.now}: new client"
94
+ begin
95
+ while (line = client.readline)
96
+ puts "#{Time.now}: client sent: #{line.strip}"
97
+ client.puts(line)
101
98
  end
99
+ rescue EOFError
100
+ ensure
101
+ puts "#{Time.now}: connection closed"
102
+ client.close rescue nil
102
103
  end
103
- rescue SignalException
104
- exit 2
105
- rescue => e
106
- puts e.to_s
107
- puts " " << e.backtrace.join("\n ")
108
- exit 3
109
- ensure
110
- puts "*** #{Time.now}: echo server exiting..."
111
- sleep(options[:stop_time])
112
- puts "*** #{Time.now}: echo server exited"
113
104
  end
105
+ rescue SignalException
106
+ exit 2
107
+ rescue => e
108
+ puts e.to_s
109
+ puts " " << e.backtrace.join("\n ")
110
+ exit 3
111
+ ensure
112
+ puts "*** #{Time.now}: echo server exiting..."
113
+ sleep(options[:stop_time])
114
+ puts "*** #{Time.now}: echo server exited"
114
115
  end
115
116
  end
116
- Process.waitpid(pid)
117
+
118
+ if options[:daemonize]
119
+ fork do
120
+ Process.setsid
121
+ main(options)
122
+ end
123
+ else
124
+ main(options)
125
+ end
data/spec/test_helper.rb CHANGED
@@ -17,17 +17,13 @@ module TestHelper
17
17
  if options[:crash_before_bind]
18
18
  @start_command << " --crash-before-bind"
19
19
  end
20
+ if options[:no_daemonize]
21
+ @start_command << " --no-daemonize"
22
+ end
20
23
  new_options = {
21
24
  :identifier => 'My Test Daemon',
22
25
  :start_command => @start_command,
23
- :ping_command => proc do
24
- begin
25
- TCPSocket.new('127.0.0.1', 3230)
26
- true
27
- rescue SystemCallError
28
- false
29
- end
30
- end,
26
+ :ping_command => method(:ping_echo_server),
31
27
  :pid_file => 'spec/echo_server.pid',
32
28
  :log_file => 'spec/echo_server.log',
33
29
  :start_timeout => 3,
@@ -36,6 +32,15 @@ module TestHelper
36
32
  @controller = DaemonController.new(new_options)
37
33
  end
38
34
 
35
+ def ping_echo_server
36
+ begin
37
+ TCPSocket.new('127.0.0.1', 3230)
38
+ true
39
+ rescue SystemCallError
40
+ false
41
+ end
42
+ end
43
+
39
44
  def write_file(filename, contents)
40
45
  File.open(filename, 'w') do |f|
41
46
  f.write(contents)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: daemon_controller
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hongli Lai