daemon_controller 0.2.4 → 0.2.5

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 = "0.2.4"
5
- s.date = "2009-11-13"
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
@@ -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 == :timeout
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
- Process.kill('SIGTERM', pid)
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])
@@ -22,6 +22,6 @@
22
22
  class DaemonController
23
23
  MAJOR = 0
24
24
  MINOR = 2
25
- TINY = 4
25
+ TINY = 5
26
26
  VERSION_STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
27
27
  end # class DaemonController
@@ -103,7 +103,8 @@ describe DaemonController, "#start" do
103
103
  end
104
104
  end
105
105
 
106
- it "kills the daemon with a signal if the daemon doesn't start in time and there's a PID file" do
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 }.should raise_error(DaemonController::StartTimeout)
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
- "forked yet, on platforms where Ruby supports fork()" do
128
- new_controller(:start_command => '(echo $$ > spec/echo_server.pid && sleep 5)',
129
- :start_timeout => 0.3)
130
- lambda { @controller.start }.should raise_error(DaemonController::StartTimeout)
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
- it "keeps the file descriptors in 'keep_ios' open" do
192
- a, b = IO.pipe
193
- begin
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
- @controller.start
197
- b.close
198
- select([a], nil, nil, 0).should be_nil
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
- @controller.stop
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
- 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
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 .. 0.6).should === result.real
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
@@ -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
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: 2009-11-13 00:00:00 +01:00
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: []