daemons 0.3.0 → 0.4.0
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 +68 -2
- data/Rakefile +14 -1
- data/Releases +27 -11
- data/examples/call/call.rb +56 -0
- data/examples/call/call_monitor.rb +55 -0
- data/examples/daemonize/daemonize.rb +20 -0
- data/examples/{ctrl_crash.rb → run/ctrl_crash.rb} +1 -1
- data/examples/{ctrl_exec.rb → run/ctrl_exec.rb} +1 -1
- data/examples/{ctrl_exit.rb → run/ctrl_exit.rb} +1 -1
- data/examples/{ctrl_monitor.rb → run/ctrl_monitor.rb} +1 -1
- data/examples/{ctrl_multiple.rb → run/ctrl_multiple.rb} +1 -1
- data/examples/{ctrl_normal.rb → run/ctrl_normal.rb} +1 -1
- data/examples/{ctrl_ontop.rb → run/ctrl_ontop.rb} +1 -1
- data/examples/{myserver.rb → run/myserver.rb} +0 -0
- data/examples/{myserver_crashing.rb → run/myserver_crashing.rb} +0 -0
- data/examples/{myserver_crashing.rb.output → run/myserver_crashing.rb.output} +0 -0
- data/examples/{myserver_exiting.rb → run/myserver_exiting.rb} +0 -0
- data/lib/daemons.rb +151 -446
- data/lib/daemons/application.rb +289 -0
- data/lib/daemons/application_group.rb +152 -0
- data/lib/daemons/controller.rb +125 -0
- data/lib/daemons/daemonize.rb +98 -2
- data/lib/daemons/monitor.rb +58 -33
- data/lib/daemons/pid.rb +60 -0
- data/lib/daemons/pidfile.rb +7 -34
- data/lib/daemons/pidmem.rb +10 -0
- data/test/call_as_daemon.rb +12 -0
- metadata +40 -20
data/README
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= Daemons Version 0.
|
1
|
+
= Daemons Version 0.4.0
|
2
2
|
|
3
3
|
(See Releases for release-specific information)
|
4
4
|
|
@@ -7,6 +7,9 @@
|
|
7
7
|
Daemons provides an easy way to wrap existing ruby scripts (for example a self-written server)
|
8
8
|
to be <i>run as a daemon</i> and to be <i>controlled by simple start/stop/restart commands</i>.
|
9
9
|
|
10
|
+
If you want, you can also use daemons to <i>run blocks of ruby code in a daemon process</i> and to control
|
11
|
+
these processes from the main application.
|
12
|
+
|
10
13
|
Besides this basic functionality, daemons offers many advanced features like <i>exception backtracing</i>
|
11
14
|
and logging (in case your ruby script crashes) and <i>monitoring</i> and automatic restarting of your processes
|
12
15
|
if they crash.
|
@@ -16,6 +19,10 @@ process.
|
|
16
19
|
|
17
20
|
== Basic Usage
|
18
21
|
|
22
|
+
You can use Daemons in three differet ways:
|
23
|
+
|
24
|
+
=== 1. Create wrapper scripts for your server scripts or applications
|
25
|
+
|
19
26
|
Layout: suppose you have your self-written server <tt>myserver.rb</tt>:
|
20
27
|
|
21
28
|
# this is myserver.rb
|
@@ -55,7 +62,66 @@ should be daemonized by seperating them by two _hyphens_:
|
|
55
62
|
|
56
63
|
$ ruby myserver_control.rb start -- --file=anyfile --a_switch another_argument
|
57
64
|
|
58
|
-
|
65
|
+
|
66
|
+
=== 2. Control a bunch of daemons from another application
|
67
|
+
|
68
|
+
Layout: you have an application <tt>my_app.rb</tt> that wants to run a bunch of
|
69
|
+
server tasks as daemon processes.
|
70
|
+
|
71
|
+
# this is my_app.rb
|
72
|
+
|
73
|
+
require 'rubygems' # if you use RubyGems
|
74
|
+
require 'daemons'
|
75
|
+
|
76
|
+
task1 = Daemons.call(:multiple => true) do
|
77
|
+
# first server task
|
78
|
+
|
79
|
+
loop {
|
80
|
+
conn = accept_conn()
|
81
|
+
serve(conn)
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
task2 = Daemons.call do
|
86
|
+
# second server task
|
87
|
+
|
88
|
+
loop {
|
89
|
+
something_different()
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
# the parent process continues to run
|
94
|
+
|
95
|
+
# we can even control your tasks, for example stop them
|
96
|
+
task1.stop
|
97
|
+
task2.stop
|
98
|
+
|
99
|
+
exit
|
100
|
+
|
101
|
+
=== 3. Daemonize the currently running process
|
102
|
+
|
103
|
+
Layout: you have an application <tt>my_daemon.rb</tt> that wants to run as a daemon
|
104
|
+
(but without the ability to be controlled by daemons via start/stop commands)
|
105
|
+
|
106
|
+
# this is my_daemons.rb
|
107
|
+
|
108
|
+
require 'rubygems' # if you use RubyGems
|
109
|
+
require 'daemons'
|
110
|
+
|
111
|
+
# Initialize the app while we're not a daemon
|
112
|
+
init()
|
113
|
+
|
114
|
+
# Become a daemon
|
115
|
+
Daemons.daemonize
|
116
|
+
|
117
|
+
# The server loop
|
118
|
+
loop {
|
119
|
+
conn = accept_conn()
|
120
|
+
serve(conn)
|
121
|
+
}
|
122
|
+
|
123
|
+
|
124
|
+
<b>For further documentation, refer to the module documentation of Daemons.</b>
|
59
125
|
|
60
126
|
|
61
127
|
== Download and Installation
|
data/Rakefile
CHANGED
@@ -28,9 +28,22 @@ spec = Gem::Specification.new do |s|
|
|
28
28
|
s.version = Daemons::VERSION
|
29
29
|
s.author = "Thomas Uehlinger"
|
30
30
|
s.email = "th.uehlinger@gmx.ch"
|
31
|
+
s.rubyforge_project = "daemons"
|
31
32
|
s.homepage = "http://daemons.rubyforge.org"
|
32
33
|
s.platform = Gem::Platform::RUBY
|
33
|
-
s.summary = "A toolkit to
|
34
|
+
s.summary = "A toolkit to create and control daemons in different ways"
|
35
|
+
s.description = <<-EOF
|
36
|
+
Daemons provides an easy way to wrap existing ruby scripts (for example a self-written server)
|
37
|
+
to be run as a daemon and to be controlled by simple start/stop/restart commands.
|
38
|
+
|
39
|
+
You can also call blocks as daemons and control them from the parent or just daemonize the current
|
40
|
+
process.
|
41
|
+
|
42
|
+
Besides this basic functionality, daemons offers many advanced features like exception
|
43
|
+
backtracing and logging (in case your ruby script crashes) and monitoring and automatic
|
44
|
+
restarting of your processes if they crash.
|
45
|
+
EOF
|
46
|
+
|
34
47
|
#s.files = FileList["{test,lib}/**/*"].exclude("rdoc").to_a
|
35
48
|
s.files = PKG_FILES
|
36
49
|
s.require_path = "lib"
|
data/Releases
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
= Daemons Release History
|
2
2
|
|
3
|
-
== Release 0.0.1: Feb 8, 2005
|
4
|
-
|
5
|
-
* Initial release
|
6
|
-
|
7
|
-
== Release 0.2.0: Mar 21, 2005
|
8
3
|
|
9
|
-
|
10
|
-
* Exec functionality added
|
11
|
-
* More examples added
|
12
|
-
* New commands: status, zap
|
4
|
+
== Release 0.4.0: July 30, 2005
|
13
5
|
|
14
|
-
|
6
|
+
* Two completely new operation modes:
|
7
|
+
1. Call a block as a daemon (<tt>Daemons.call { my_daemon_code }</tt>)
|
8
|
+
and control it from the parent process.
|
9
|
+
2. Daemonize the currently running process (<tt>Daemons.daemonize</tt>)
|
10
|
+
plus the already existing mode to control your scripts (<tt>Daemons.run("script.rb")</tt>)
|
11
|
+
* Improved documentation (for example "How does the daemonization process work?")
|
12
|
+
* Improved "simulation mode" (<tt>:ontop</tt> option)
|
13
|
+
* Some minor bugfixes
|
15
14
|
|
16
|
-
* Bugfix for a problem with the 'status' command
|
17
15
|
|
18
16
|
== Release 0.3.0: April 21, 2005
|
19
17
|
|
@@ -21,3 +19,21 @@
|
|
21
19
|
* 'restart' command fixed
|
22
20
|
* '--force' command modifier (please refer to the documentation)
|
23
21
|
* Some more bugfixes and improvements
|
22
|
+
|
23
|
+
|
24
|
+
== Release 0.2.1: Mar 21, 2005
|
25
|
+
|
26
|
+
* Bugfix for a problem with the 'status' command
|
27
|
+
|
28
|
+
|
29
|
+
== Release 0.2.0: Mar 21, 2005
|
30
|
+
|
31
|
+
* Exception backtrace functionality added
|
32
|
+
* Exec functionality added
|
33
|
+
* More examples added
|
34
|
+
* New commands: status, zap
|
35
|
+
|
36
|
+
|
37
|
+
== Release 0.0.1: Feb 8, 2005
|
38
|
+
|
39
|
+
* Initial release
|
@@ -0,0 +1,56 @@
|
|
1
|
+
lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../../lib'))
|
2
|
+
|
3
|
+
if File.exists?(File.join(lib_dir, 'daemons.rb'))
|
4
|
+
$LOAD_PATH.unshift lib_dir
|
5
|
+
else
|
6
|
+
require 'rubygems' rescue nil
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
require 'daemons'
|
11
|
+
|
12
|
+
testfile = File.expand_path(__FILE__) + '.log'
|
13
|
+
|
14
|
+
|
15
|
+
# On the first call to <tt<call</tt>, an application group (accessible by <tt>Daemons.group</tt>)
|
16
|
+
# will be created an the options will be kept within, so you only have to specify
|
17
|
+
# <tt>:multiple</tt> once.
|
18
|
+
#
|
19
|
+
|
20
|
+
options = {
|
21
|
+
# :ontop => true,
|
22
|
+
:multiple => true
|
23
|
+
}
|
24
|
+
|
25
|
+
|
26
|
+
Daemons.call(options) do
|
27
|
+
File.open(testfile, 'w') {|f|
|
28
|
+
f.puts "test"
|
29
|
+
}
|
30
|
+
|
31
|
+
loop { puts "1"; sleep 5 }
|
32
|
+
end
|
33
|
+
puts "first task started"
|
34
|
+
|
35
|
+
Daemons.call do
|
36
|
+
loop { puts "2"; sleep 4 }
|
37
|
+
end
|
38
|
+
puts "second task started"
|
39
|
+
|
40
|
+
# NOTE: this process will exit after 5 seconds
|
41
|
+
Daemons.call do
|
42
|
+
puts "3"
|
43
|
+
sleep 5
|
44
|
+
end
|
45
|
+
puts "third task started"
|
46
|
+
|
47
|
+
puts "waiting 20 seconds..."
|
48
|
+
sleep(20)
|
49
|
+
|
50
|
+
# This call would result in an exception as it will try to kill the third process
|
51
|
+
# which has already terminated by that time; but using the 'true' parameter forces the
|
52
|
+
# stop_all procedure.
|
53
|
+
puts "trying to stop all tasks..."
|
54
|
+
Daemons.group.stop_all(true)
|
55
|
+
|
56
|
+
puts "done"
|
@@ -0,0 +1,55 @@
|
|
1
|
+
lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../../lib'))
|
2
|
+
|
3
|
+
if File.exists?(File.join(lib_dir, 'daemons.rb'))
|
4
|
+
$LOAD_PATH.unshift lib_dir
|
5
|
+
else
|
6
|
+
require 'rubygems' rescue nil
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
require 'daemons'
|
11
|
+
|
12
|
+
testfile = File.expand_path(__FILE__) + '.log'
|
13
|
+
|
14
|
+
|
15
|
+
# On the first call to <tt<call</tt>, an application group (accessible by <tt>Daemons.group</tt>)
|
16
|
+
# will be created an the options will be kept within, so you only have to specify
|
17
|
+
# <tt>:multiple</tt> once.
|
18
|
+
#
|
19
|
+
|
20
|
+
options = {
|
21
|
+
# :ontop => true,
|
22
|
+
:multiple => true,
|
23
|
+
:monitor => true
|
24
|
+
}
|
25
|
+
|
26
|
+
|
27
|
+
Daemons.call(options) do
|
28
|
+
loop { puts "1"; sleep 20 }
|
29
|
+
end
|
30
|
+
puts "first task started"
|
31
|
+
|
32
|
+
|
33
|
+
# NOTE: this process will exit after 5 seconds
|
34
|
+
Daemons.call do
|
35
|
+
File.open(testfile, 'a') {|f|
|
36
|
+
f.puts "started..."
|
37
|
+
puts "2"
|
38
|
+
|
39
|
+
sleep 5
|
40
|
+
|
41
|
+
f.puts "...exit"
|
42
|
+
}
|
43
|
+
end
|
44
|
+
puts "second task started"
|
45
|
+
|
46
|
+
puts "waiting 100 seconds..."
|
47
|
+
sleep(100)
|
48
|
+
|
49
|
+
# This call would result in an exception as it will try to kill the third process
|
50
|
+
# which has already terminated by that time; but using the 'true' parameter forces the
|
51
|
+
# stop_all procedure.
|
52
|
+
puts "trying to stop all tasks..."
|
53
|
+
Daemons.group.stop_all(true)
|
54
|
+
|
55
|
+
puts "done"
|
@@ -0,0 +1,20 @@
|
|
1
|
+
lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../../lib'))
|
2
|
+
|
3
|
+
if File.exists?(File.join(lib_dir, 'daemons.rb'))
|
4
|
+
$LOAD_PATH.unshift lib_dir
|
5
|
+
else
|
6
|
+
require 'rubygems' rescue nil
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
require 'daemons'
|
12
|
+
|
13
|
+
|
14
|
+
testfile = File.expand_path(__FILE__) + '.log'
|
15
|
+
|
16
|
+
Daemons.daemonize
|
17
|
+
|
18
|
+
File.open(testfile, 'w') {|f|
|
19
|
+
f.write("test")
|
20
|
+
}
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/lib/daemons.rb
CHANGED
@@ -1,464 +1,74 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'optparse/time'
|
3
3
|
|
4
|
+
|
4
5
|
require 'daemons/pidfile'
|
5
6
|
require 'daemons/cmdline'
|
6
7
|
require 'daemons/exceptions'
|
7
8
|
require 'daemons/monitor'
|
8
9
|
|
9
10
|
|
11
|
+
require 'daemons/application'
|
12
|
+
require 'daemons/application_group'
|
13
|
+
require 'daemons/controller'
|
14
|
+
|
15
|
+
|
10
16
|
# All functions and classes that Daemons provides reside in this module.
|
11
17
|
#
|
12
|
-
#
|
13
|
-
#
|
18
|
+
# Daemons is normally invoked by on of the following three ways:
|
19
|
+
#
|
20
|
+
# 1. <tt>Daemons.run(script, options)</tt>:
|
21
|
+
# This is used in wrapper-scripts that are supposed to control other ruby scripts or
|
22
|
+
# external applications. Control is completely passed to the daemons library.
|
23
|
+
# Such wrapper script should be invoked with command line options like 'start' or 'stop'
|
24
|
+
# to do anything useful.
|
25
|
+
#
|
26
|
+
# 2. <tt>Daemons.call(options) { block }</tt>:
|
27
|
+
# Execute the block in a new daemon. <tt>Daemons.call</tt> will return immediately
|
28
|
+
# after spawning the daemon with the new Application object as a return value.
|
29
|
+
#
|
30
|
+
# 3. <tt>Daemons.daemonize(options)</tt>:
|
31
|
+
# Daemonize the currently runnig process, i.e. the calling process will become a daemon.
|
32
|
+
#
|
33
|
+
# == What does daemons internally do with my daemons?
|
34
|
+
# *or*:: why do my daemons crash when they try to open a file?
|
35
|
+
# *or*:: why can I not see any output from the daemon on the console (when using for example +puts+?
|
36
|
+
#
|
37
|
+
# From a technical aspect of view, daemons does the following when creating a daemon:
|
38
|
+
#
|
39
|
+
# 1. Forks a child (and exits the parent process, if needed)
|
40
|
+
# 2. Becomes a session leader (which detaches the program from
|
41
|
+
# the controlling terminal).
|
42
|
+
# 3. Forks another child process and exits first child. This prevents
|
43
|
+
# the potential of acquiring a controlling terminal.
|
44
|
+
# 4. Changes the current working directory to "/".
|
45
|
+
# 5. Clears the file creation mask (sets +umask+ to +0000+).
|
46
|
+
# 6. Closes file descriptors (reopens +STDOUT+ and +STDERR+ to point to a logfile if
|
47
|
+
# possible).
|
48
|
+
#
|
49
|
+
# So what does this mean for your daemons:
|
50
|
+
# - the current directory is '/'
|
51
|
+
# - you cannot receive any input from the console (for example no +gets+)
|
52
|
+
# - you cannot output anything from the daemons with +puts+/+print+ unless a logfile is used
|
53
|
+
#
|
54
|
+
# == How do PidFiles work? Where are they stored?
|
14
55
|
#
|
15
56
|
# Also, you are maybe interested in reading the documentation for the class PidFile.
|
16
57
|
# There you can find out about how Daemons works internally and how and where the so
|
17
|
-
# called <i>
|
58
|
+
# called <i>PidFiles</i> are stored.
|
18
59
|
#
|
19
60
|
module Daemons
|
20
61
|
|
21
|
-
VERSION = "0.
|
62
|
+
VERSION = "0.4.0"
|
22
63
|
|
23
64
|
require 'daemons/daemonize'
|
24
|
-
|
25
|
-
|
26
|
-
class Application
|
27
|
-
|
28
|
-
attr_accessor :app_argv
|
29
|
-
attr_accessor :controller_argv
|
30
|
-
|
31
|
-
# the PidFile instance belonging to this application
|
32
|
-
attr_reader :pid_file
|
33
|
-
|
34
|
-
# the ApplicationGroup the application belongs to
|
35
|
-
attr_reader :group
|
36
|
-
|
37
|
-
|
38
|
-
def initialize(group, pid_file = nil)
|
39
|
-
@group = group
|
40
|
-
|
41
|
-
@pid_file = (pid_file || PidFile.new(pidfile_dir(), @group.app_name, @group.multiple))
|
42
|
-
end
|
43
|
-
|
44
|
-
def script
|
45
|
-
@script || @group.script
|
46
|
-
end
|
47
|
-
|
48
|
-
def pidfile_dir
|
49
|
-
PidFile.dir(@dir_mode || @group.dir_mode, @dir || @group.dir, @script || @group.script)
|
50
|
-
end
|
51
|
-
|
52
|
-
def real_start
|
53
|
-
opts = @group.controller.options
|
54
|
-
|
55
|
-
unless opts[:ontop]
|
56
|
-
Daemonize.daemonize(opts[:log_output] ? File.join(pidfile_dir(), @group.app_name + '.output') : nil)
|
57
|
-
end
|
58
|
-
|
59
|
-
@pid_file.write
|
60
|
-
|
61
|
-
if opts[:exec]
|
62
|
-
run_via_exec()
|
63
|
-
else
|
64
|
-
# We need this to remove the pid-file if the applications exits by itself.
|
65
|
-
# Note that <tt>at_text</tt> will only be run if the applications exits by calling
|
66
|
-
# <tt>exit</tt>, and not if it calls <tt>exit!</tt>.
|
67
|
-
#
|
68
|
-
at_exit {
|
69
|
-
@pid_file.remove rescue nil
|
70
|
-
|
71
|
-
# If the option <tt>:backtrace</tt> is used and the application did exit by itself
|
72
|
-
# create a exception log.
|
73
|
-
if opts[:backtrace] and not opts[:ontop] and not $daemons_sigterm
|
74
|
-
exception_log() rescue nil
|
75
|
-
end
|
76
|
-
|
77
|
-
}
|
78
|
-
|
79
|
-
# This part is needed to remove the pid-file if the application is killed by
|
80
|
-
# daemons or manually by the user.
|
81
|
-
# Note that the applications is not supposed to overwrite the signal handler for
|
82
|
-
# 'TERM'.
|
83
|
-
#
|
84
|
-
trap('TERM') {
|
85
|
-
@pid_file.remove rescue nil
|
86
|
-
$daemons_sigterm = true
|
87
|
-
|
88
|
-
exit
|
89
|
-
}
|
90
|
-
|
91
|
-
run_via_load()
|
92
|
-
end
|
93
|
-
end
|
94
|
-
private :real_start
|
95
|
-
|
96
|
-
def start
|
97
|
-
@group.create_monitor(@group.applications[0] || self)
|
98
|
-
|
99
|
-
real_start
|
100
|
-
end
|
101
|
-
|
102
|
-
def run
|
103
|
-
if @group.controller.options[:exec]
|
104
|
-
run_via_exec()
|
105
|
-
else
|
106
|
-
run_via_load()
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def run_via_exec
|
111
|
-
ENV['DAEMONS_ARGV'] = @controller_argv.join(' ') # haven't tested yet if this is really passed to the exec'd process...
|
112
|
-
|
113
|
-
Kernel.exec(script(), *ARGV)
|
114
|
-
end
|
115
|
-
|
116
|
-
def run_via_load
|
117
|
-
$DAEMONS_ARGV = @controller_argv
|
118
|
-
ENV['DAEMONS_ARGV'] = @controller_argv.join(' ')
|
119
|
-
|
120
|
-
ARGV.clear
|
121
|
-
ARGV.concat @app_argv if @app_argv
|
122
|
-
|
123
|
-
# TODO: begin - rescue - end around this and exception logging
|
124
|
-
load script()
|
125
|
-
end
|
126
|
-
|
127
|
-
# This is a nice little function for debugging purposes:
|
128
|
-
# In case a multi-threaded ruby script exits due to an uncaught exception
|
129
|
-
# it may be difficult to find out where the exception came from because
|
130
|
-
# one cannot catch exceptions that are thrown in threads other than the main
|
131
|
-
# thread.
|
132
|
-
#
|
133
|
-
# This function searches for all exceptions in memory and outputs them to STDERR
|
134
|
-
# (if it is connected) and to a log file in the pid-file directory.
|
135
|
-
#
|
136
|
-
def exception_log
|
137
|
-
require 'logger'
|
138
|
-
|
139
|
-
l_file = Logger.new(File.join(pidfile_dir(), @group.app_name + '.log'))
|
140
|
-
|
141
|
-
|
142
|
-
# the code below only logs the last exception
|
143
|
-
# e = nil
|
144
|
-
#
|
145
|
-
# ObjectSpace.each_object {|o|
|
146
|
-
# if ::Exception === o
|
147
|
-
# e = o
|
148
|
-
# end
|
149
|
-
# }
|
150
|
-
#
|
151
|
-
# l_file.error e
|
152
|
-
# l_file.close
|
153
|
-
|
154
|
-
# this code logs every exception found in memory
|
155
|
-
ObjectSpace.each_object {|o|
|
156
|
-
if ::Exception === o
|
157
|
-
l_file.error o
|
158
|
-
end
|
159
|
-
}
|
160
|
-
|
161
|
-
l_file.close
|
162
|
-
end
|
163
|
-
|
164
|
-
|
165
|
-
def stop
|
166
|
-
if @group.controller.options[:force] and not running?
|
167
|
-
self.zap
|
168
|
-
return
|
169
|
-
end
|
170
|
-
|
171
|
-
Process.kill('TERM', @pid_file.read)
|
172
|
-
|
173
|
-
# We try to remove the pid-files by ourselves, in case the application
|
174
|
-
# didn't clean it up.
|
175
|
-
@pid_file.remove rescue nil
|
176
|
-
|
177
|
-
end
|
178
|
-
|
179
|
-
def zap
|
180
|
-
@pid_file.remove
|
181
|
-
end
|
182
|
-
|
183
|
-
def zap!
|
184
|
-
@pid_file.remove rescue nil
|
185
|
-
end
|
186
|
-
|
187
|
-
def show_status
|
188
|
-
running = self.running?
|
189
|
-
|
190
|
-
puts "#{self.group.app_name}: #{running ? '' : 'not '}running#{(running and @pid_file.exists?) ? ' [pid ' + @pid_file.read.to_s + ']' : ''}#{(@pid_file.exists? and not running) ? ' (but pid-file exists: ' + @pid_file.read.to_s + ')' : ''}"
|
191
|
-
end
|
192
|
-
|
193
|
-
# This function implements a (probably too simle) method to detect
|
194
|
-
# whether the program with the pid found in the pid-file is still running.
|
195
|
-
# It just searches for the pid in the output of <tt>ps ax</tt>, which
|
196
|
-
# is probably not a good idea in some cases.
|
197
|
-
# Alternatives would be to use a direct access method the unix process control
|
198
|
-
# system.
|
199
|
-
#
|
200
|
-
def running?
|
201
|
-
if @pid_file.exists?
|
202
|
-
return PidFile.running?(@pid_file.read)
|
203
|
-
end
|
204
|
-
|
205
|
-
return false
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
|
210
|
-
class ApplicationGroup
|
211
|
-
|
212
|
-
attr_reader :app_name
|
213
|
-
attr_reader :script
|
214
|
-
|
215
|
-
attr_reader :monitor
|
216
|
-
attr_reader :controller
|
217
|
-
|
218
|
-
attr_reader :applications
|
219
|
-
|
220
|
-
attr_accessor :controller_argv
|
221
|
-
attr_accessor :app_argv
|
222
|
-
|
223
|
-
attr_accessor :dir_mode
|
224
|
-
attr_accessor :dir
|
225
|
-
|
226
|
-
# true if the application is supposed to run in multiple instances
|
227
|
-
attr_reader :multiple
|
228
|
-
|
229
|
-
|
230
|
-
def initialize(app_name, script, controller) #multiple = false)
|
231
|
-
@app_name = app_name
|
232
|
-
@script = script
|
233
|
-
@controller = controller
|
234
|
-
@monitor = nil
|
235
|
-
|
236
|
-
options = controller.options
|
237
|
-
|
238
|
-
@multiple = options[:multiple] || false
|
239
|
-
|
240
|
-
@dir_mode = options[:dir_mode] || :script
|
241
|
-
@dir = options[:dir] || ''
|
242
|
-
|
243
|
-
#@applications = find_applications(pidfile_dir())
|
244
|
-
end
|
245
|
-
|
246
|
-
# Setup the application group.
|
247
|
-
# Currently this functions calls <tt>find_applications</tt> which finds
|
248
|
-
# all running instances of the application and populates the application array.
|
249
|
-
#
|
250
|
-
def setup
|
251
|
-
@applications = find_applications(pidfile_dir())
|
252
|
-
end
|
253
|
-
|
254
|
-
def pidfile_dir
|
255
|
-
PidFile.dir(@dir_mode, @dir, script)
|
256
|
-
end
|
257
|
-
|
258
|
-
def find_applications(dir)
|
259
|
-
pid_files = PidFile.find_files(dir, app_name)
|
260
|
-
|
261
|
-
#pp pid_files
|
262
|
-
|
263
|
-
@monitor = Monitor.find(dir, app_name + '_monitor')
|
264
|
-
|
265
|
-
pid_files.reject! {|f| f =~ /_monitor.pid$/}
|
266
|
-
|
267
|
-
return pid_files.map {|f|
|
268
|
-
app = Application.new(self, PidFile.existing(f))
|
269
|
-
setup_app(app)
|
270
|
-
app
|
271
|
-
}
|
272
|
-
end
|
273
|
-
|
274
|
-
def new_application(script = nil)
|
275
|
-
if @applications.size > 0 and not @multiple
|
276
|
-
if @controller.options[:force]
|
277
|
-
@applications.delete_if {|a|
|
278
|
-
unless a.running?
|
279
|
-
a.zap
|
280
|
-
true
|
281
|
-
end
|
282
|
-
}
|
283
|
-
end
|
284
|
-
|
285
|
-
raise RuntimeException.new('there is already one or more instance(s) of the program running') unless @applications.empty?
|
286
|
-
end
|
287
|
-
|
288
|
-
app = Application.new(self)
|
289
|
-
|
290
|
-
setup_app(app)
|
291
|
-
|
292
|
-
@applications << app
|
293
|
-
|
294
|
-
return app
|
295
|
-
end
|
296
|
-
|
297
|
-
def setup_app(app)
|
298
|
-
app.controller_argv = @controller_argv
|
299
|
-
app.app_argv = @app_argv
|
300
|
-
end
|
301
|
-
private :setup_app
|
302
|
-
|
303
|
-
def create_monitor(an_app)
|
304
|
-
return if @monitor
|
305
|
-
|
306
|
-
if @controller.options[:monitor]
|
307
|
-
@monitor = Monitor.new(an_app)
|
308
|
-
|
309
|
-
@monitor.start(@applications)
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
def start_all
|
314
|
-
@monitor.stop if @monitor
|
315
|
-
@monitor = nil
|
316
|
-
|
317
|
-
@applications.each {|a|
|
318
|
-
fork {
|
319
|
-
a.start
|
320
|
-
}
|
321
|
-
}
|
322
|
-
end
|
323
|
-
|
324
|
-
def stop_all
|
325
|
-
@monitor.stop if @monitor
|
326
|
-
|
327
|
-
@applications.each {|a| a.stop}
|
328
|
-
end
|
329
|
-
|
330
|
-
def zap_all
|
331
|
-
@monitor.stop if @monitor
|
332
|
-
|
333
|
-
@applications.each {|a| a.zap}
|
334
|
-
end
|
335
|
-
|
336
|
-
def show_status
|
337
|
-
@applications.each {|a| a.show_status}
|
338
|
-
end
|
339
|
-
|
340
|
-
end
|
341
|
-
|
342
|
-
|
343
|
-
class Controller
|
344
|
-
|
345
|
-
attr_reader :app_name
|
346
|
-
attr_reader :options
|
347
|
-
|
348
|
-
|
349
|
-
COMMANDS = [
|
350
|
-
'start',
|
351
|
-
'stop',
|
352
|
-
'restart',
|
353
|
-
'run',
|
354
|
-
'zap',
|
355
|
-
'status'
|
356
|
-
]
|
357
|
-
|
358
|
-
def initialize(script, argv = [])
|
359
|
-
@argv = argv
|
360
|
-
@script = File.expand_path(script)
|
361
|
-
|
362
|
-
@app_name = File.split(@script)[1]
|
363
|
-
|
364
|
-
@command, @controller_part, @app_part = Controller.split_argv(argv)
|
365
|
-
|
366
|
-
#@options[:dir_mode] ||= :script
|
367
|
-
|
368
|
-
@optparse = Optparse.new(self)
|
369
|
-
end
|
370
|
-
|
371
|
-
|
372
|
-
# This function is used to do a final update of the options passed to the application
|
373
|
-
# before they are really used.
|
374
|
-
#
|
375
|
-
# Note that this function should only update <tt>@options</tt> and no other variables.
|
376
|
-
#
|
377
|
-
def setup_options
|
378
|
-
#@options[:ontop] ||= true
|
379
|
-
end
|
380
|
-
|
381
|
-
def run(options = {})
|
382
|
-
@options = options
|
383
|
-
|
384
|
-
@options.update @optparse.parse(@controller_part).delete_if {|k,v| !v}
|
385
|
-
|
386
|
-
setup_options()
|
387
|
-
|
388
|
-
#pp @options
|
389
|
-
|
390
|
-
@group = ApplicationGroup.new(@app_name, @script, self) #options)
|
391
|
-
@group.controller_argv = @controller_part
|
392
|
-
@group.app_argv = @app_part
|
393
|
-
|
394
|
-
@group.setup
|
395
|
-
|
396
|
-
case @command
|
397
|
-
when 'start'
|
398
|
-
@group.new_application.start
|
399
|
-
when 'run'
|
400
|
-
@group.new_application.run
|
401
|
-
when 'stop'
|
402
|
-
@group.stop_all
|
403
|
-
when 'restart'
|
404
|
-
unless @group.applications.empty?
|
405
|
-
@group.stop_all
|
406
|
-
sleep 1
|
407
|
-
@group.start_all
|
408
|
-
end
|
409
|
-
when 'zap'
|
410
|
-
@group.zap_all
|
411
|
-
when 'status'
|
412
|
-
unless @group.applications.empty?
|
413
|
-
@group.show_status
|
414
|
-
else
|
415
|
-
puts "#{@group.app_name}: no instances running"
|
416
|
-
end
|
417
|
-
when nil
|
418
|
-
raise CmdException.new('no command given')
|
419
|
-
#puts "ERROR: No command given"; puts
|
420
|
-
|
421
|
-
#print_usage()
|
422
|
-
#raise('usage function not implemented')
|
423
|
-
else
|
424
|
-
raise Error.new("command '#{@command}' not implemented")
|
425
|
-
end
|
426
|
-
end
|
427
|
-
|
428
|
-
|
429
|
-
# Split an _argv_ array.
|
430
|
-
# +argv+ is assumed to be in the following format:
|
431
|
-
# ['command', 'controller option 1', 'controller option 2', ..., '--', 'app option 1', ...]
|
432
|
-
#
|
433
|
-
# <tt>command</tt> must be one of the commands listed in <tt>COMMANDS</tt>
|
434
|
-
#
|
435
|
-
# *Returns*: the command as a string, the controller options as an array, the appliation options
|
436
|
-
# as an array
|
437
|
-
#
|
438
|
-
def Controller.split_argv(argv)
|
439
|
-
argv = argv.dup
|
440
|
-
|
441
|
-
command = nil
|
442
|
-
controller_part = []
|
443
|
-
app_part = []
|
444
|
-
|
445
|
-
if COMMANDS.include? argv[0]
|
446
|
-
command = argv.shift
|
447
|
-
end
|
448
|
-
|
449
|
-
if i = argv.index('--')
|
450
|
-
controller_part = argv[0..i-1]
|
451
|
-
app_part = argv[i+1..-1]
|
452
|
-
else
|
453
|
-
controller_part = argv[0..-1]
|
454
|
-
end
|
455
|
-
|
456
|
-
return command, controller_part, app_part
|
457
|
-
end
|
458
|
-
end
|
459
65
|
|
460
66
|
|
461
67
|
# Passes control to Daemons.
|
68
|
+
# This is used in wrapper-scripts that are supposed to control other ruby scripts or
|
69
|
+
# external applications. Control is completely passed to the daemons library.
|
70
|
+
# Such wrapper script should be invoked with command line options like 'start' or 'stop'
|
71
|
+
# to do anything useful.
|
462
72
|
#
|
463
73
|
# +script+:: This is the path to the script that should be run as a daemon.
|
464
74
|
# Please note that Daemons runs this script with <tt>load <script></tt>.
|
@@ -466,7 +76,7 @@ module Daemons
|
|
466
76
|
# script resides, so this has to be either an absolute path or you have to run
|
467
77
|
# the controlling script from the appropriate directory.
|
468
78
|
#
|
469
|
-
# +options+:: A hash that may contain one or more of options listed below
|
79
|
+
# +options+:: A hash that may contain one or more of the options listed below
|
470
80
|
#
|
471
81
|
# === Options:
|
472
82
|
# <tt>:dir_mode</tt>:: Either <tt>:script</tt> (the directory for writing the pid files to
|
@@ -480,8 +90,8 @@ module Daemons
|
|
480
90
|
# same time
|
481
91
|
# <tt>:ontop</tt>:: When given, stay on top, i.e. do not daemonize the application
|
482
92
|
# (but the pid-file and other things are written as usual)
|
483
|
-
# <tt>:
|
484
|
-
#
|
93
|
+
# <tt>:mode</tt>:: <tt>:load</tt> Load the script with <tt>Kernel.load</tt>;
|
94
|
+
# <tt>:exec</tt> Execute the script file with <tt>Kernel.exec</tt>
|
485
95
|
# <tt>:backtrace</tt>:: Write a backtrace of the last exceptions to the file '[app_name].log' in the
|
486
96
|
# pid-file directory if the application exits due to an uncaught exception
|
487
97
|
# <tt>:monitor</tt>:: Monitor the programs and restart crashed instances
|
@@ -493,22 +103,117 @@ module Daemons
|
|
493
103
|
# :dir => 'pids',
|
494
104
|
# :multiple => true,
|
495
105
|
# :ontop => true,
|
496
|
-
# :
|
106
|
+
# :mode => :exec,
|
497
107
|
# :backtrace => true,
|
498
|
-
# :monitor => true
|
108
|
+
# :monitor => true,
|
109
|
+
# :script => "path/to/script.rb"
|
499
110
|
# }
|
500
111
|
#
|
501
112
|
# Daemons.run(File.join(File.split(__FILE__)[0], 'myscript.rb'), options)
|
502
113
|
#
|
503
114
|
def run(script, options = {})
|
504
|
-
|
115
|
+
options[:script] = script
|
116
|
+
@controller = Controller.new(options, ARGV)
|
505
117
|
|
506
118
|
#pp @controller
|
507
119
|
|
508
120
|
@controller.catch_exceptions {
|
509
|
-
@controller.run
|
121
|
+
@controller.run
|
510
122
|
}
|
123
|
+
|
124
|
+
# I don't think anybody will ever use @group, as this location should not be reached under non-error conditions
|
125
|
+
@group = @controller.group
|
511
126
|
end
|
512
127
|
module_function :run
|
513
128
|
|
129
|
+
|
130
|
+
# Execute the block in a new daemon. <tt>Daemons.call</tt> will return immediately
|
131
|
+
# after spawning the daemon with the new Application object as a return value.
|
132
|
+
#
|
133
|
+
# +options+:: A hash that may contain one or more of the options listed below
|
134
|
+
#
|
135
|
+
# +block+:: The block to call in the daemon.
|
136
|
+
#
|
137
|
+
# === Options:
|
138
|
+
# <tt>:multiple</tt>:: Specifies whether multiple instances of the same script are allowed to run at the
|
139
|
+
# same time
|
140
|
+
# <tt>:ontop</tt>:: When given, stay on top, i.e. do not daemonize the application
|
141
|
+
# <tt>:backtrace</tt>:: Write a backtrace of the last exceptions to the file '[app_name].log' in the
|
142
|
+
# pid-file directory if the application exits due to an uncaught exception
|
143
|
+
# -----
|
144
|
+
#
|
145
|
+
# === Example:
|
146
|
+
# options = {
|
147
|
+
# :backtrace => true,
|
148
|
+
# :monitor => true,
|
149
|
+
# :ontop => true
|
150
|
+
# }
|
151
|
+
#
|
152
|
+
# Daemons.call(options) begin
|
153
|
+
# # Server loop:
|
154
|
+
# loop {
|
155
|
+
# conn = accept_conn()
|
156
|
+
# serve(conn)
|
157
|
+
# }
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
def call(options = {}, &block)
|
161
|
+
unless block_given?
|
162
|
+
raise "Daemons.call: no block given"
|
163
|
+
end
|
164
|
+
|
165
|
+
options[:proc] = block
|
166
|
+
options[:mode] = :proc
|
167
|
+
|
168
|
+
@group ||= ApplicationGroup.new('proc', options)
|
169
|
+
|
170
|
+
new_app = @group.new_application(options)
|
171
|
+
new_app.start
|
172
|
+
|
173
|
+
return new_app
|
174
|
+
end
|
175
|
+
module_function :call
|
176
|
+
|
177
|
+
|
178
|
+
# Daemonize the currently runnig process, i.e. the calling process will become a daemon.
|
179
|
+
#
|
180
|
+
# +options+:: A hash that may contain one or more of the options listed below
|
181
|
+
#
|
182
|
+
# === Options:
|
183
|
+
# <tt>:ontop</tt>:: When given, stay on top, i.e. do not daemonize the application
|
184
|
+
# <tt>:backtrace</tt>:: Write a backtrace of the last exceptions to the file '[app_name].log' in the
|
185
|
+
# pid-file directory if the application exits due to an uncaught exception
|
186
|
+
# -----
|
187
|
+
#
|
188
|
+
# === Example:
|
189
|
+
# options = {
|
190
|
+
# :backtrace => true,
|
191
|
+
# :ontop => true
|
192
|
+
# }
|
193
|
+
#
|
194
|
+
# Daemons.daemonize(options)
|
195
|
+
#
|
196
|
+
# # Server loop:
|
197
|
+
# loop {
|
198
|
+
# conn = accept_conn()
|
199
|
+
# serve(conn)
|
200
|
+
# }
|
201
|
+
#
|
202
|
+
def daemonize(options = {})
|
203
|
+
#Daemonize::daemonize
|
204
|
+
|
205
|
+
@group ||= ApplicationGroup.new('self', options)
|
206
|
+
|
207
|
+
@group.new_application(:mode => :none).start
|
208
|
+
|
209
|
+
end
|
210
|
+
module_function :daemonize
|
211
|
+
|
212
|
+
# Return the internal ApplicationGroup instance.
|
213
|
+
def group; @group; end
|
214
|
+
module_function :group
|
215
|
+
|
216
|
+
# Return the internal Controller instance.
|
217
|
+
def controller; @controller; end
|
218
|
+
module_function :controller
|
514
219
|
end
|