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.
@@ -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: []