daemons 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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