daemon_controller 0.2.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +1 -1
- data/README.markdown +37 -9
- data/daemon_controller.gemspec +3 -3
- data/lib/daemon_controller/spawn.rb +1 -1
- data/lib/daemon_controller/version.rb +4 -4
- data/lib/daemon_controller.rb +57 -8
- data/spec/daemon_controller_spec.rb +24 -0
- metadata +16 -6
data/LICENSE.txt
CHANGED
data/README.markdown
CHANGED
@@ -31,10 +31,9 @@ It provides the following functionality:
|
|
31
31
|
|
32
32
|
## Resources
|
33
33
|
|
34
|
-
* [Website](
|
34
|
+
* [Website](https://github.com/FooBarWidget/daemon_controller)
|
35
35
|
* [RDoc](http://rdoc.info/projects/FooBarWidget/daemon_controller)
|
36
36
|
* [Git repository](git://github.com/FooBarWidget/daemon_controller.git)
|
37
|
-
* [RubyForge project](http://rubyforge.org/projects/daemoncontrol/)
|
38
37
|
|
39
38
|
|
40
39
|
What is it for?
|
@@ -317,7 +316,7 @@ That can be done with the following code:
|
|
317
316
|
controller = DaemonController.new(
|
318
317
|
:identifier => 'Apache web server',
|
319
318
|
:start_command => 'apachectl -f apache.conf -k start',
|
320
|
-
:ping_command =>
|
319
|
+
:ping_command => [:tcp, 'localhost', 1234],
|
321
320
|
:pid_file => 'apache.pid',
|
322
321
|
:log_file => 'apache.log',
|
323
322
|
:start_timeout => 25
|
@@ -401,7 +400,7 @@ This can be achieved with the following code:
|
|
401
400
|
:identifier => 'Sphinx search server',
|
402
401
|
:start_command => "searchd -c config/sphinx.conf",
|
403
402
|
:before_start => method(:before_start),
|
404
|
-
:ping_command =>
|
403
|
+
:ping_command => [:tcp, 'localhost', SEARCH_SERVER_PORT],
|
405
404
|
:pid_file => 'tmp/pids/sphinx.pid',
|
406
405
|
:log_file => 'log/sphinx.log')
|
407
406
|
end
|
@@ -458,11 +457,40 @@ when the daemon is designed from the beginning with such abilities in mind, but
|
|
458
457
|
it's compatible with virtually all daemons, and is easy to use.
|
459
458
|
|
460
459
|
|
461
|
-
Concurrency notes
|
462
|
-
|
463
|
-
DaemonController
|
464
|
-
|
465
|
-
|
460
|
+
Concurrency and compatibility notes
|
461
|
+
===================================
|
462
|
+
DaemonController uses a lock file and the Ruby `File#flock` API to guarantee
|
463
|
+
synchronization. This has a few implications:
|
464
|
+
|
465
|
+
* On most Ruby implementations, including MRI, `File#flock` is implemented
|
466
|
+
with the POSIX `flock()` system call or the Windows file locking APIs.
|
467
|
+
This kind of file locking works pretty much the way we expect it would.
|
468
|
+
Multiple threads can safely use daemon_controller concurrently. Multiple
|
469
|
+
processes can safely use daemon_controller concurrently. There will be no
|
470
|
+
race conditions.
|
471
|
+
|
472
|
+
However `flock()` is not implemented on Solaris. daemon_controller, if
|
473
|
+
used in MRI does not currently work on Solaris. You need to use JRuby
|
474
|
+
which does not use `flock()` to implement `File#flock`.
|
475
|
+
|
476
|
+
* On JRuby `File#flock` is implemented through the Java file locking API,
|
477
|
+
which on Unix is implemented with the `fcntl()` system calls. This is a
|
478
|
+
different kind of lock with very strange semantics.
|
479
|
+
|
480
|
+
* If *any* process/thread closes the lock file, then the lock on that file
|
481
|
+
will be removed even if that process/thread never requested a lock.
|
482
|
+
* Fcntl locks are usually implemented indepedently from `flock()` locks so
|
483
|
+
if a file is already locked with `flock()` then `fcntl()` will not block
|
484
|
+
when.
|
485
|
+
* The JVM's file locking API only allows inter-process synchronization. It
|
486
|
+
cannot be used to synchronize threads. If a thread has obtained a file
|
487
|
+
lock, then another thread within the same JVM process will not block upon
|
488
|
+
trying to lock the same file.
|
489
|
+
|
490
|
+
In other words, if you're on JRuby then don't concurrently access
|
491
|
+
daemon_controller from multiple threads without manual locking. Also be
|
492
|
+
careful with mixing MRI processes that use daemon_controller with JRuby
|
493
|
+
processes that use daemon_controller.
|
466
494
|
|
467
495
|
|
468
496
|
API documentation
|
data/daemon_controller.gemspec
CHANGED
@@ -1,11 +1,11 @@
|
|
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.
|
5
|
-
s.date = "
|
4
|
+
s.version = "1.0.0"
|
5
|
+
s.date = "2012-02-04"
|
6
6
|
s.summary = "A library for implementing daemon management capabilities"
|
7
7
|
s.email = "hongli@phusion.nl"
|
8
|
-
s.homepage = "
|
8
|
+
s.homepage = "https://github.com/FooBarWidget/daemon_controller"
|
9
9
|
s.description = "A library for robust daemon management."
|
10
10
|
s.has_rdoc = true
|
11
11
|
s.authors = ["Hongli Lai"]
|
@@ -19,6 +19,6 @@
|
|
19
19
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
20
|
# THE SOFTWARE.
|
21
21
|
|
22
|
-
# Used on Ruby 1.9 because forking
|
22
|
+
# Used on Ruby 1.9 because forking may not be safe/supported on all platforms.
|
23
23
|
Process.setsid
|
24
24
|
Process.spawn(ARGV[0])
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# daemon_controller, library for robust daemon management
|
2
|
-
# Copyright (c) 2010 Phusion
|
2
|
+
# Copyright (c) 2010, 2011, 2012 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
|
@@ -20,8 +20,8 @@
|
|
20
20
|
# THE SOFTWARE.
|
21
21
|
|
22
22
|
class DaemonController
|
23
|
-
MAJOR =
|
24
|
-
MINOR =
|
25
|
-
TINY =
|
23
|
+
MAJOR = 1
|
24
|
+
MINOR = 0
|
25
|
+
TINY = 0
|
26
26
|
VERSION_STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
|
27
27
|
end # class DaemonController
|
data/lib/daemon_controller.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# daemon_controller, library for robust daemon management
|
2
|
-
# Copyright (c) 2010 Phusion
|
2
|
+
# Copyright (c) 2010, 2011, 2012 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
|
@@ -85,14 +85,19 @@ class DaemonController
|
|
85
85
|
# The value may be a command string. This command must exit with an exit code of
|
86
86
|
# 0 if the daemon can be successfully connected to, or exit with a non-0 exit
|
87
87
|
# code on failure.
|
88
|
+
#
|
89
|
+
# The value may also be an Array which specifies the socket address of the daemon.
|
90
|
+
# It must be in one of the following forms:
|
91
|
+
# - [:tcp, host_name, port]
|
92
|
+
# - [:unix, filename]
|
88
93
|
#
|
89
94
|
# The value may also be a Proc, which returns an expression that evaluates to
|
90
95
|
# true (indicating that the daemon can be connected to) or false (failure).
|
91
96
|
# If the Proc raises Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::ETIMEDOUT
|
92
|
-
#
|
97
|
+
# Errno::ECONNRESET, Errno::EINVAL or Errno::EADDRNOTAVAIL then that also
|
93
98
|
# means that the daemon cannot be connected to.
|
94
99
|
# <b>NOTE:</b> if the ping command returns an object which responds to
|
95
|
-
# <tt>#close</tt>, then that method will be called on
|
100
|
+
# <tt>#close</tt>, then that method will be called on it.
|
96
101
|
# This makes it possible to specify a ping command such as
|
97
102
|
# <tt>lambda { TCPSocket.new('localhost', 1234) }</tt>, without having to worry
|
98
103
|
# about closing it afterwards.
|
@@ -101,6 +106,10 @@ class DaemonController
|
|
101
106
|
# [:pid_file]
|
102
107
|
# The PID file that the daemon will write to. Used to check whether the daemon
|
103
108
|
# is running.
|
109
|
+
#
|
110
|
+
# [:lock_file]
|
111
|
+
# The lock file to use for serializing concurrent daemon management operations.
|
112
|
+
# Defaults to "(filename of PID file).lock".
|
104
113
|
#
|
105
114
|
# [:log_file]
|
106
115
|
# The log file that the daemon will write to. It will be consulted to see
|
@@ -183,7 +192,7 @@ class DaemonController
|
|
183
192
|
@log_file_activity_timeout = options[:log_file_activity_timeout] || 7
|
184
193
|
@daemonize_for_me = options[:daemonize_for_me]
|
185
194
|
@keep_ios = options[:keep_ios] || []
|
186
|
-
@lock_file = determine_lock_file(@identifier, @pid_file)
|
195
|
+
@lock_file = determine_lock_file(options, @identifier, @pid_file)
|
187
196
|
end
|
188
197
|
|
189
198
|
# Start the daemon and wait until it can be pinged.
|
@@ -538,8 +547,12 @@ private
|
|
538
547
|
return nil
|
539
548
|
end
|
540
549
|
|
541
|
-
def determine_lock_file(identifier, pid_file)
|
542
|
-
|
550
|
+
def determine_lock_file(options, identifier, pid_file)
|
551
|
+
if options[:lock_file]
|
552
|
+
return LockFile.new(File.expand_path(options[:lock_file]))
|
553
|
+
else
|
554
|
+
return LockFile.new(File.expand_path(pid_file + ".lock"))
|
555
|
+
end
|
543
556
|
end
|
544
557
|
|
545
558
|
def self.fork_supported?
|
@@ -650,11 +663,46 @@ private
|
|
650
663
|
rescue *ALLOWED_CONNECT_EXCEPTIONS
|
651
664
|
return false
|
652
665
|
end
|
666
|
+
elsif @ping_command.is_a?(Array)
|
667
|
+
type, *args = @ping_command
|
668
|
+
|
669
|
+
case type
|
670
|
+
when :tcp
|
671
|
+
socket_domain = Socket::Constants::AF_INET
|
672
|
+
hostname, port = args
|
673
|
+
sockaddr = Socket.pack_sockaddr_in(port, hostname)
|
674
|
+
when :unix
|
675
|
+
socket_domain = Socket::Constants::AF_LOCAL
|
676
|
+
sockaddr = Socket.pack_sockaddr_un(args[0])
|
677
|
+
else
|
678
|
+
raise ArgumentError, "Unknown ping command type #{type.inspect}"
|
679
|
+
end
|
680
|
+
|
681
|
+
begin
|
682
|
+
socket = Socket.new(socket_domain, Socket::Constants::SOCK_STREAM, 0)
|
683
|
+
begin
|
684
|
+
socket.connect_nonblock(sockaddr)
|
685
|
+
rescue Errno::ENOENT, Errno::EINPROGRESS, Errno::EAGAIN, Errno::EWOULDBLOCK
|
686
|
+
if select(nil, [socket], nil, 0.1)
|
687
|
+
begin
|
688
|
+
socket.connect_nonblock(sockaddr)
|
689
|
+
rescue Errno::EISCONN
|
690
|
+
end
|
691
|
+
else
|
692
|
+
raise Errno::ECONNREFUSED
|
693
|
+
end
|
694
|
+
end
|
695
|
+
return true
|
696
|
+
rescue Errno::ECONNREFUSED, Errno::ENOENT
|
697
|
+
return false
|
698
|
+
ensure
|
699
|
+
socket.close if socket
|
700
|
+
end
|
653
701
|
else
|
654
702
|
return system(@ping_command)
|
655
703
|
end
|
656
704
|
end
|
657
|
-
|
705
|
+
|
658
706
|
def safe_fork(double_fork)
|
659
707
|
pid = fork
|
660
708
|
if pid.nil?
|
@@ -674,8 +722,9 @@ private
|
|
674
722
|
"\tfrom " << e.backtrace.join("\n\tfrom ")
|
675
723
|
STDERR.write(e)
|
676
724
|
STDERR.flush
|
677
|
-
ensure
|
678
725
|
exit!
|
726
|
+
ensure
|
727
|
+
exit!(0)
|
679
728
|
end
|
680
729
|
else
|
681
730
|
if double_fork
|
@@ -361,4 +361,28 @@ describe DaemonController do
|
|
361
361
|
server.close
|
362
362
|
end
|
363
363
|
end
|
364
|
+
|
365
|
+
specify "the ping command may be [:tcp, hostname, port]" do
|
366
|
+
new_controller(:ping_command => [:tcp, "localhost", 8278])
|
367
|
+
@controller.send(:run_ping_command).should be_false
|
368
|
+
|
369
|
+
server = TCPServer.new('localhost', 8278)
|
370
|
+
begin
|
371
|
+
@controller.send(:run_ping_command).should be_true
|
372
|
+
ensure
|
373
|
+
server.close
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
specify "the ping command may be [:unix, filename]" do
|
378
|
+
new_controller(:ping_command => [:unix, "spec/foo.sock"])
|
379
|
+
@controller.send(:run_ping_command).should be_false
|
380
|
+
|
381
|
+
server = UNIXServer.new('spec/foo.sock')
|
382
|
+
begin
|
383
|
+
@controller.send(:run_ping_command).should be_true
|
384
|
+
ensure
|
385
|
+
server.close
|
386
|
+
end
|
387
|
+
end
|
364
388
|
end
|
metadata
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: daemon_controller
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
4
5
|
prerelease:
|
5
|
-
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
6
11
|
platform: ruby
|
7
12
|
authors:
|
8
13
|
- Hongli Lai
|
@@ -10,8 +15,7 @@ autorequire:
|
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
17
|
|
13
|
-
date:
|
14
|
-
default_executable:
|
18
|
+
date: 2012-02-04 00:00:00 Z
|
15
19
|
dependencies: []
|
16
20
|
|
17
21
|
description: A library for robust daemon management.
|
@@ -34,8 +38,7 @@ files:
|
|
34
38
|
- spec/daemon_controller_spec.rb
|
35
39
|
- spec/echo_server.rb
|
36
40
|
- spec/unresponsive_daemon.rb
|
37
|
-
|
38
|
-
homepage: http://github.com/FooBarWidget/daemon_controller/tree/master
|
41
|
+
homepage: https://github.com/FooBarWidget/daemon_controller
|
39
42
|
licenses: []
|
40
43
|
|
41
44
|
post_install_message:
|
@@ -48,19 +51,26 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
51
|
requirements:
|
49
52
|
- - ">="
|
50
53
|
- !ruby/object:Gem::Version
|
54
|
+
hash: 3
|
55
|
+
segments:
|
56
|
+
- 0
|
51
57
|
version: "0"
|
52
58
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
59
|
none: false
|
54
60
|
requirements:
|
55
61
|
- - ">="
|
56
62
|
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
57
66
|
version: "0"
|
58
67
|
requirements: []
|
59
68
|
|
60
69
|
rubyforge_project:
|
61
|
-
rubygems_version: 1.
|
70
|
+
rubygems_version: 1.8.15
|
62
71
|
signing_key:
|
63
72
|
specification_version: 3
|
64
73
|
summary: A library for implementing daemon management capabilities
|
65
74
|
test_files: []
|
66
75
|
|
76
|
+
has_rdoc: true
|