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 CHANGED
@@ -1,4 +1,4 @@
1
- = Daemons Version 0.3.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
- For further documentation, refer to the module documentation of Daemons.
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 convert your script to a controllable daemon"
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
- * Exception backtrace functionality added
10
- * Exec functionality added
11
- * More examples added
12
- * New commands: status, zap
4
+ == Release 0.4.0: July 30, 2005
13
5
 
14
- == Release 0.2.1: Mar 21, 2005
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
+ }
@@ -1,4 +1,4 @@
1
- lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../lib'))
1
+ lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../../lib'))
2
2
 
3
3
  if File.exists?(File.join(lib_dir, 'daemons.rb'))
4
4
  $LOAD_PATH.unshift lib_dir
@@ -1,4 +1,4 @@
1
- lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../lib'))
1
+ lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../../lib'))
2
2
 
3
3
  if File.exists?(File.join(lib_dir, 'daemons.rb'))
4
4
  $LOAD_PATH.unshift lib_dir
@@ -1,4 +1,4 @@
1
- lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../lib'))
1
+ lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../../lib'))
2
2
 
3
3
  if File.exists?(File.join(lib_dir, 'daemons.rb'))
4
4
  $LOAD_PATH.unshift lib_dir
@@ -1,4 +1,4 @@
1
- lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../lib'))
1
+ lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../../lib'))
2
2
 
3
3
  if File.exists?(File.join(lib_dir, 'daemons.rb'))
4
4
  $LOAD_PATH.unshift lib_dir
@@ -1,4 +1,4 @@
1
- lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../lib'))
1
+ lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../../lib'))
2
2
 
3
3
  if File.exists?(File.join(lib_dir, 'daemons.rb'))
4
4
  $LOAD_PATH.unshift lib_dir
@@ -1,4 +1,4 @@
1
- lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../lib'))
1
+ lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../../lib'))
2
2
 
3
3
  if File.exists?(File.join(lib_dir, 'daemons.rb'))
4
4
  $LOAD_PATH.unshift lib_dir
@@ -1,4 +1,4 @@
1
- lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../lib'))
1
+ lib_dir = File.expand_path(File.join(File.split(__FILE__)[0], '../../lib'))
2
2
 
3
3
  if File.exists?(File.join(lib_dir, 'daemons.rb'))
4
4
  $LOAD_PATH.unshift lib_dir
@@ -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
- # The function you should me most interested in is Daemons#run, because it is
13
- # the only function you need to invoke directly from your scripts.
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>Pid-Files</i> are stored.
58
+ # called <i>PidFiles</i> are stored.
18
59
  #
19
60
  module Daemons
20
61
 
21
- VERSION = "0.3.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>:exec</tt>:: When given, do not start the application by <tt>load</tt>-ing the script file,
484
- # but by exec'ing the script file
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
- # :exec => true,
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
- @controller = Controller.new(script, ARGV)
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(options)
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