daemon_controller 0.2.4 → 0.2.5
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/daemon_controller.gemspec +5 -3
- data/lib/daemon_controller.rb +27 -7
- data/lib/daemon_controller/spawn.rb +24 -0
- data/lib/daemon_controller/version.rb +1 -1
- data/spec/daemon_controller_spec.rb +53 -30
- data/spec/test_helper.rb +23 -0
- data/spec/unresponsive_daemon.rb +12 -0
- metadata +4 -2
data/daemon_controller.gemspec
CHANGED
@@ -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 = "0.2.
|
5
|
-
s.date = "
|
4
|
+
s.version = "0.2.5"
|
5
|
+
s.date = "2010-01-13"
|
6
6
|
s.summary = "A library for implementing daemon management capabilities"
|
7
7
|
s.email = "hongli@phusion.nl"
|
8
8
|
s.homepage = "http://github.com/FooBarWidget/daemon_controller/tree/master"
|
@@ -14,9 +14,11 @@ Gem::Specification.new do |s|
|
|
14
14
|
"README.markdown", "LICENSE.txt", "daemon_controller.gemspec",
|
15
15
|
"lib/daemon_controller.rb",
|
16
16
|
"lib/daemon_controller/lock_file.rb",
|
17
|
+
"lib/daemon_controller/spawn.rb",
|
17
18
|
"lib/daemon_controller/version.rb",
|
18
19
|
"spec/test_helper.rb",
|
19
20
|
"spec/daemon_controller_spec.rb",
|
20
|
-
"spec/echo_server.rb"
|
21
|
+
"spec/echo_server.rb",
|
22
|
+
"spec/unresponsive_daemon.rb"
|
21
23
|
]
|
22
24
|
end
|
data/lib/daemon_controller.rb
CHANGED
@@ -56,6 +56,8 @@ class DaemonController
|
|
56
56
|
end
|
57
57
|
class ConnectError < Error
|
58
58
|
end
|
59
|
+
class DaemonizationTimeout < TimeoutError
|
60
|
+
end
|
59
61
|
|
60
62
|
# Create a new DaemonController object.
|
61
63
|
#
|
@@ -347,17 +349,24 @@ private
|
|
347
349
|
started = run_ping_command
|
348
350
|
end
|
349
351
|
result = started
|
350
|
-
rescue Timeout::Error
|
352
|
+
rescue DaemonizationTimeout, Timeout::Error => e
|
351
353
|
start_timed_out
|
352
354
|
if pid_file_available?
|
353
|
-
kill_daemon_with_signal
|
355
|
+
kill_daemon_with_signal(true)
|
356
|
+
end
|
357
|
+
if e.is_a?(DaemonizationTimeout)
|
358
|
+
result = :daemonization_timeout
|
359
|
+
else
|
360
|
+
result = :start_timeout
|
354
361
|
end
|
355
|
-
result = :timeout
|
356
362
|
end
|
357
363
|
if !result
|
358
364
|
raise(StartError, differences_in_log_file ||
|
359
365
|
"Daemon '#{@identifier}' failed to start.")
|
360
|
-
elsif result == :
|
366
|
+
elsif result == :daemonization_timeout
|
367
|
+
raise(StartTimeout, differences_in_log_file ||
|
368
|
+
"Daemon '#{@identifier}' didn't daemonize in time.")
|
369
|
+
elsif result == :start_timeout
|
361
370
|
raise(StartTimeout, differences_in_log_file ||
|
362
371
|
"Daemon '#{@identifier}' failed to start in time.")
|
363
372
|
else
|
@@ -391,10 +400,14 @@ private
|
|
391
400
|
end
|
392
401
|
end
|
393
402
|
|
394
|
-
def kill_daemon_with_signal
|
403
|
+
def kill_daemon_with_signal(force = false)
|
395
404
|
pid = read_pid_file
|
396
405
|
if pid
|
397
|
-
|
406
|
+
if force
|
407
|
+
Process.kill('SIGKILL', pid)
|
408
|
+
else
|
409
|
+
Process.kill('SIGTERM', pid)
|
410
|
+
end
|
398
411
|
end
|
399
412
|
rescue Errno::ESRCH, Errno::ENOENT
|
400
413
|
end
|
@@ -482,6 +495,10 @@ private
|
|
482
495
|
def start_timed_out
|
483
496
|
end
|
484
497
|
|
498
|
+
# This method does nothing and only serves as a hook for the unit test.
|
499
|
+
def daemonization_timed_out
|
500
|
+
end
|
501
|
+
|
485
502
|
def save_log_file_information
|
486
503
|
@original_log_file_stat = File.stat(@log_file) rescue nil
|
487
504
|
@current_log_file_stat = @original_log_file_stat
|
@@ -584,6 +601,8 @@ private
|
|
584
601
|
# it.
|
585
602
|
return
|
586
603
|
rescue Timeout::Error
|
604
|
+
daemonization_timed_out
|
605
|
+
|
587
606
|
# If the daemon doesn't fork into the background
|
588
607
|
# in time, then kill it.
|
589
608
|
begin
|
@@ -604,7 +623,7 @@ private
|
|
604
623
|
rescue SystemCallError
|
605
624
|
end
|
606
625
|
end
|
607
|
-
raise
|
626
|
+
raise DaemonizationTimeout
|
608
627
|
end
|
609
628
|
if $?.exitstatus != 0
|
610
629
|
raise StartError, File.read(tempfile_path).strip
|
@@ -643,6 +662,7 @@ private
|
|
643
662
|
if double_fork
|
644
663
|
pid2 = fork
|
645
664
|
if pid2.nil?
|
665
|
+
Process.setsid
|
646
666
|
yield
|
647
667
|
end
|
648
668
|
else
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# daemon_controller, library for robust daemon management
|
2
|
+
# Copyright (c) 2010 Phusion
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
|
22
|
+
# Used on Ruby 1.9 because forking is not safe.
|
23
|
+
Process.setsid
|
24
|
+
Process.spawn(ARGV[0])
|
@@ -103,7 +103,8 @@ describe DaemonController, "#start" do
|
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
|
-
it "kills the daemon
|
106
|
+
it "kills the daemon forcefully if the daemon has forked but doesn't " <<
|
107
|
+
"become pingable in time, and there's a PID file" do
|
107
108
|
new_controller(:wait2 => 3, :start_timeout => 1)
|
108
109
|
pid = nil
|
109
110
|
@controller.should_receive(:start_timed_out).and_return do
|
@@ -113,21 +114,41 @@ describe DaemonController, "#start" do
|
|
113
114
|
pid = @controller.send(:read_pid_file)
|
114
115
|
end
|
115
116
|
begin
|
116
|
-
lambda { @controller.start }
|
117
|
+
block = lambda { @controller.start }
|
118
|
+
block.should raise_error(DaemonController::StartTimeout, /failed to start in time/)
|
119
|
+
eventually(1) do
|
120
|
+
!process_is_alive?(pid)
|
121
|
+
end
|
122
|
+
|
123
|
+
# The daemon should not be able to clean up its PID file since
|
124
|
+
# it's killed with SIGKILL.
|
125
|
+
File.exist?("spec/echo_server.pid").should be_true
|
117
126
|
ensure
|
118
|
-
# It's possible that because of a racing condition, the PID
|
119
|
-
# file doesn't get deleted before the next test is run. So
|
120
|
-
# here we ensure that the PID file is gone.
|
121
127
|
File.unlink("spec/echo_server.pid") rescue nil
|
122
128
|
end
|
123
129
|
end
|
124
130
|
|
125
|
-
if DaemonController.send(:fork_supported?)
|
126
|
-
it "kills the daemon if it doesn't start in time and hasn't " <<
|
127
|
-
"
|
128
|
-
|
129
|
-
:
|
130
|
-
|
131
|
+
if DaemonController.send(:fork_supported?) || Process.respond_to?(:spawn)
|
132
|
+
it "kills the daemon if it doesn't start in time and hasn't forked " <<
|
133
|
+
"yet, on platforms where Ruby supports fork() or Process.spawn" do
|
134
|
+
begin
|
135
|
+
new_controller(:start_command => "./spec/unresponsive_daemon.rb",
|
136
|
+
:start_timeout => 0.2)
|
137
|
+
pid = nil
|
138
|
+
@controller.should_receive(:daemonization_timed_out).and_return do
|
139
|
+
@controller.send(:wait_until) do
|
140
|
+
@controller.send(:pid_file_available?)
|
141
|
+
end
|
142
|
+
pid = @controller.send(:read_pid_file)
|
143
|
+
end
|
144
|
+
block = lambda { @controller.start }
|
145
|
+
block.should raise_error(DaemonController::StartTimeout, /didn't daemonize in time/)
|
146
|
+
eventually(1) do
|
147
|
+
!process_is_alive?(pid)
|
148
|
+
end
|
149
|
+
ensure
|
150
|
+
File.unlink("spec/echo_server.pid") rescue nil
|
151
|
+
end
|
131
152
|
end
|
132
153
|
end
|
133
154
|
|
@@ -188,28 +209,30 @@ describe DaemonController, "#start" do
|
|
188
209
|
log.should == ["before_start", "start_command"]
|
189
210
|
end
|
190
211
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
new_controller(:keep_ios => [b])
|
212
|
+
if DaemonController.send(:fork_supported?) || Process.respond_to?(:spawn)
|
213
|
+
it "keeps the file descriptors in 'keep_ios' open" do
|
214
|
+
a, b = IO.pipe
|
195
215
|
begin
|
196
|
-
|
197
|
-
|
198
|
-
|
216
|
+
new_controller(:keep_ios => [b])
|
217
|
+
begin
|
218
|
+
@controller.start
|
219
|
+
b.close
|
220
|
+
select([a], nil, nil, 0).should be_nil
|
221
|
+
ensure
|
222
|
+
@controller.stop
|
223
|
+
end
|
199
224
|
ensure
|
200
|
-
|
225
|
+
a.close if !a.closed?
|
226
|
+
b.close if !b.closed?
|
201
227
|
end
|
202
|
-
ensure
|
203
|
-
a.close if !a.closed?
|
204
|
-
b.close if !b.closed?
|
205
228
|
end
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
229
|
+
|
230
|
+
it "performs the daemonization on behalf of the daemon if 'daemonize_for_me' is set" do
|
231
|
+
new_controller(:no_daemonize => true, :daemonize_for_me => true)
|
232
|
+
@controller.start
|
233
|
+
ping_echo_server.should be_true
|
234
|
+
@controller.stop
|
235
|
+
end
|
213
236
|
end
|
214
237
|
end
|
215
238
|
|
@@ -235,7 +258,7 @@ describe DaemonController, "#stop" do
|
|
235
258
|
@controller.stop
|
236
259
|
end
|
237
260
|
@controller.should_not be_running
|
238
|
-
(0.3
|
261
|
+
result.real.should be_between(0.3, 0.6)
|
239
262
|
end
|
240
263
|
|
241
264
|
it "raises StopTimeout if the daemon does not stop in time" do
|
data/spec/test_helper.rb
CHANGED
@@ -50,6 +50,29 @@ module TestHelper
|
|
50
50
|
def exec_is_slow?
|
51
51
|
return RUBY_PLATFORM == "java"
|
52
52
|
end
|
53
|
+
|
54
|
+
def process_is_alive?(pid)
|
55
|
+
begin
|
56
|
+
Process.kill(0, pid)
|
57
|
+
return true
|
58
|
+
rescue Errno::ESRCH
|
59
|
+
return false
|
60
|
+
rescue SystemCallError => e
|
61
|
+
return true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def eventually(deadline_duration = 1, check_interval = 0.05)
|
66
|
+
deadline = Time.now + deadline_duration
|
67
|
+
while Time.now < deadline
|
68
|
+
if yield
|
69
|
+
return
|
70
|
+
else
|
71
|
+
sleep(check_interval)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
raise "Time limit exceeded"
|
75
|
+
end
|
53
76
|
end
|
54
77
|
|
55
78
|
# A thread which doesn't execute its block until the
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
dir = File.dirname(__FILE__)
|
3
|
+
Dir.chdir(File.dirname(dir))
|
4
|
+
begin
|
5
|
+
File.open("spec/echo_server.pid", "w") do |f|
|
6
|
+
f.puts(Process.pid)
|
7
|
+
end
|
8
|
+
sleep 30
|
9
|
+
rescue SignalException
|
10
|
+
File.unlink("spec/echo_server.pid") rescue nil
|
11
|
+
raise
|
12
|
+
end
|
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.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hongli Lai
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-01-13 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -27,10 +27,12 @@ files:
|
|
27
27
|
- daemon_controller.gemspec
|
28
28
|
- lib/daemon_controller.rb
|
29
29
|
- lib/daemon_controller/lock_file.rb
|
30
|
+
- lib/daemon_controller/spawn.rb
|
30
31
|
- lib/daemon_controller/version.rb
|
31
32
|
- spec/test_helper.rb
|
32
33
|
- spec/daemon_controller_spec.rb
|
33
34
|
- spec/echo_server.rb
|
35
|
+
- spec/unresponsive_daemon.rb
|
34
36
|
has_rdoc: true
|
35
37
|
homepage: http://github.com/FooBarWidget/daemon_controller/tree/master
|
36
38
|
licenses: []
|