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 +1 -1
- data/daemon_controller.gemspec +1 -1
- data/lib/daemon_controller.rb +68 -14
- data/lib/daemon_controller/lock_file.rb +1 -1
- data/lib/daemon_controller/version.rb +2 -2
- data/spec/daemon_controller_spec.rb +25 -0
- data/spec/echo_server.rb +60 -51
- data/spec/test_helper.rb +13 -8
- metadata +1 -1
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
|
|
data/daemon_controller.gemspec
CHANGED
@@ -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.
|
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"
|
data/lib/daemon_controller.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# daemon_controller, library for robust daemon management
|
2
|
-
# Copyright (c)
|
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
|
-
|
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.
|
129
|
-
#
|
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
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
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
|
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
|
-
|
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
|
-
|
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)
|
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)
|
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 =
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
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 =>
|
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)
|