daemon_controller 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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