daemons 0.2.1 → 0.3.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.2.1
1
+ = Daemons Version 0.3.0
2
2
 
3
3
  (See Releases for release-specific information)
4
4
 
@@ -7,6 +7,10 @@
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
+ Besides this basic functionality, daemons offers many advanced features like <i>exception backtracing</i>
11
+ and logging (in case your ruby script crashes) and <i>monitoring</i> and automatic restarting of your processes
12
+ if they crash.
13
+
10
14
  Daemons includes the <tt>daemonize.rb</tt> script written by <i>Travis Whitton</i> to do the daemonization
11
15
  process.
12
16
 
data/Releases CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  * Initial release
6
6
 
7
-
8
7
  == Release 0.2.0: Mar 21, 2005
9
8
 
10
9
  * Exception backtrace functionality added
@@ -15,3 +14,10 @@
15
14
  == Release 0.2.1: Mar 21, 2005
16
15
 
17
16
  * Bugfix for a problem with the 'status' command
17
+
18
+ == Release 0.3.0: April 21, 2005
19
+
20
+ * New monitor functionality: automatic restarting of your applications if they crash
21
+ * 'restart' command fixed
22
+ * '--force' command modifier (please refer to the documentation)
23
+ * Some more bugfixes and improvements
data/TODO CHANGED
@@ -1,3 +1,6 @@
1
1
  * write the README (2005-02-07) *DONE*
2
2
  * write some real tests (2005-02-08)
3
3
  * document the new options (2005-03-14)
4
+ * start/stop with --force options (2005-04-05)
5
+ * option to give some console output on start/stop commands (2005-04-05)
6
+
@@ -10,6 +10,7 @@ require 'daemons'
10
10
 
11
11
 
12
12
  options = {
13
+ :log_output => true,
13
14
  :backtrace => true
14
15
  }
15
16
 
@@ -0,0 +1,15 @@
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
+ require 'daemons'
10
+
11
+
12
+ options = {
13
+ }
14
+
15
+ Daemons.run(File.join(File.split(__FILE__)[0], 'myserver_exiting.rb'), options)
@@ -0,0 +1,16 @@
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
+ require 'daemons'
10
+
11
+
12
+ options = {
13
+ :monitor => true
14
+ }
15
+
16
+ Daemons.run(File.join(File.split(__FILE__)[0], 'myserver_crashing.rb'), options)
@@ -0,0 +1,16 @@
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
+ require 'daemons'
10
+
11
+
12
+ options = {
13
+ :multiple => true
14
+ }
15
+
16
+ Daemons.run(File.join(File.split(__FILE__)[0], 'myserver.rb'), options)
@@ -0,0 +1,15 @@
1
+ /home/uehli/Desktop/daemons-current/examples/myserver_crashing.rb:13: CRASH! (RuntimeError)
2
+ from /home/uehli/Desktop/daemons-current/examples/myserver_crashing.rb:6:in `loop'
3
+ from /home/uehli/Desktop/daemons-current/examples/myserver_crashing.rb:6
4
+ from /home/uehli/Desktop/daemons-current/lib/daemons.rb:116:in `load'
5
+ from /home/uehli/Desktop/daemons-current/lib/daemons.rb:116:in `run_via_load'
6
+ from /home/uehli/Desktop/daemons-current/lib/daemons.rb:90:in `start'
7
+ from /home/uehli/Desktop/daemons-current/lib/daemons.rb:359:in `run'
8
+ from /home/uehli/Desktop/daemons-current/lib/daemons.rb:469:in `run'
9
+ from /home/uehli/Desktop/daemons-current/lib/daemons.rb:468:in `call'
10
+ from /home/uehli/Desktop/daemons-current/lib/daemons/cmdline.rb:94:in `catch_exceptions'
11
+ from /home/uehli/Desktop/daemons-current/lib/daemons.rb:468:in `run'
12
+ from ctrl_crash.rb:17
13
+ ping from myserver.rb!
14
+ this example server will crash in 3 seconds...
15
+ CRASH!
@@ -0,0 +1,8 @@
1
+ loop do
2
+ puts 'ping from myserver.rb!'
3
+ puts 'this example server will exit in 3 seconds...'
4
+
5
+ sleep(3)
6
+
7
+ Process.exit!
8
+ end
@@ -4,6 +4,7 @@ require 'optparse/time'
4
4
  require 'daemons/pidfile'
5
5
  require 'daemons/cmdline'
6
6
  require 'daemons/exceptions'
7
+ require 'daemons/monitor'
7
8
 
8
9
 
9
10
  # All functions and classes that Daemons provides reside in this module.
@@ -17,7 +18,7 @@ require 'daemons/exceptions'
17
18
  #
18
19
  module Daemons
19
20
 
20
- VERSION = "0.2.1"
21
+ VERSION = "0.3.0"
21
22
 
22
23
  require 'daemons/daemonize'
23
24
 
@@ -48,11 +49,11 @@ module Daemons
48
49
  PidFile.dir(@dir_mode || @group.dir_mode, @dir || @group.dir, @script || @group.script)
49
50
  end
50
51
 
51
- def start
52
+ def real_start
52
53
  opts = @group.controller.options
53
54
 
54
55
  unless opts[:ontop]
55
- Daemonize.daemonize()
56
+ Daemonize.daemonize(opts[:log_output] ? File.join(pidfile_dir(), @group.app_name + '.output') : nil)
56
57
  end
57
58
 
58
59
  @pid_file.write
@@ -90,6 +91,13 @@ module Daemons
90
91
  run_via_load()
91
92
  end
92
93
  end
94
+ private :real_start
95
+
96
+ def start
97
+ @group.create_monitor(@group.applications[0] || self)
98
+
99
+ real_start
100
+ end
93
101
 
94
102
  def run
95
103
  if @group.controller.options[:exec]
@@ -132,31 +140,34 @@ module Daemons
132
140
 
133
141
 
134
142
  # the code below only logs the last exception
135
- e = nil
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
136
153
 
154
+ # this code logs every exception found in memory
137
155
  ObjectSpace.each_object {|o|
138
156
  if ::Exception === o
139
- e = o
157
+ l_file.error o
140
158
  end
141
159
  }
142
160
 
143
- l_file.error e
144
161
  l_file.close
145
-
146
- e = nil
147
-
148
- # this code logs every exception found in memory
149
- # ObjectSpace.each_object {|o|
150
- # if ::Exception === o
151
- # l_file.error o
152
- # end
153
- # }
154
- #
155
- # l_file.close
156
162
  end
157
163
 
158
164
 
159
165
  def stop
166
+ if @group.controller.options[:force] and not running?
167
+ self.zap
168
+ return
169
+ end
170
+
160
171
  Process.kill('TERM', @pid_file.read)
161
172
 
162
173
  # We try to remove the pid-files by ourselves, in case the application
@@ -169,10 +180,14 @@ module Daemons
169
180
  @pid_file.remove
170
181
  end
171
182
 
183
+ def zap!
184
+ @pid_file.remove rescue nil
185
+ end
186
+
172
187
  def show_status
173
188
  running = self.running?
174
189
 
175
- 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 + ')' : ''}"
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 + ')' : ''}"
176
191
  end
177
192
 
178
193
  # This function implements a (probably too simle) method to detect
@@ -184,7 +199,7 @@ module Daemons
184
199
  #
185
200
  def running?
186
201
  if @pid_file.exists?
187
- return /#{@pid_file.read} / =~ `ps ax`
202
+ return PidFile.running?(@pid_file.read)
188
203
  end
189
204
 
190
205
  return false
@@ -197,6 +212,7 @@ module Daemons
197
212
  attr_reader :app_name
198
213
  attr_reader :script
199
214
 
215
+ attr_reader :monitor
200
216
  attr_reader :controller
201
217
 
202
218
  attr_reader :applications
@@ -215,6 +231,7 @@ module Daemons
215
231
  @app_name = app_name
216
232
  @script = script
217
233
  @controller = controller
234
+ @monitor = nil
218
235
 
219
236
  options = controller.options
220
237
 
@@ -223,6 +240,14 @@ module Daemons
223
240
  @dir_mode = options[:dir_mode] || :script
224
241
  @dir = options[:dir] || ''
225
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
226
251
  @applications = find_applications(pidfile_dir())
227
252
  end
228
253
 
@@ -235,33 +260,76 @@ module Daemons
235
260
 
236
261
  #pp pid_files
237
262
 
238
- return pid_files.map {|f| Application.new(self, PidFile.existing(f))}
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
+ }
239
272
  end
240
273
 
241
274
  def new_application(script = nil)
242
275
  if @applications.size > 0 and not @multiple
243
- raise RuntimeException.new('there is already one or more instance(s) of the program running')
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?
244
286
  end
245
287
 
246
288
  app = Application.new(self)
247
289
 
248
- app.controller_argv = @controller_argv
249
- app.app_argv = @app_argv
290
+ setup_app(app)
250
291
 
251
292
  @applications << app
252
293
 
253
294
  return app
254
295
  end
255
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
+
256
313
  def start_all
257
- @applications.each {|a| fork { a.start } }
314
+ @monitor.stop if @monitor
315
+ @monitor = nil
316
+
317
+ @applications.each {|a|
318
+ fork {
319
+ a.start
320
+ }
321
+ }
258
322
  end
259
323
 
260
324
  def stop_all
325
+ @monitor.stop if @monitor
326
+
261
327
  @applications.each {|a| a.stop}
262
328
  end
263
329
 
264
330
  def zap_all
331
+ @monitor.stop if @monitor
332
+
265
333
  @applications.each {|a| a.zap}
266
334
  end
267
335
 
@@ -323,6 +391,8 @@ module Daemons
323
391
  @group.controller_argv = @controller_part
324
392
  @group.app_argv = @app_part
325
393
 
394
+ @group.setup
395
+
326
396
  case @command
327
397
  when 'start'
328
398
  @group.new_application.start
@@ -331,9 +401,11 @@ module Daemons
331
401
  when 'stop'
332
402
  @group.stop_all
333
403
  when 'restart'
334
- @group.stop_all
335
- sleep 1
336
- @group.start_all
404
+ unless @group.applications.empty?
405
+ @group.stop_all
406
+ sleep 1
407
+ @group.start_all
408
+ end
337
409
  when 'zap'
338
410
  @group.zap_all
339
411
  when 'status'
@@ -412,7 +484,7 @@ module Daemons
412
484
  # but by exec'ing the script file
413
485
  # <tt>:backtrace</tt>:: Write a backtrace of the last exceptions to the file '[app_name].log' in the
414
486
  # pid-file directory if the application exits due to an uncaught exception
415
- #
487
+ # <tt>:monitor</tt>:: Monitor the programs and restart crashed instances
416
488
  # -----
417
489
  #
418
490
  # === Example:
@@ -422,7 +494,8 @@ module Daemons
422
494
  # :multiple => true,
423
495
  # :ontop => true,
424
496
  # :exec => true,
425
- # :backtrace => true
497
+ # :backtrace => true,
498
+ # :monitor => true
426
499
  # }
427
500
  #
428
501
  # Daemons.run(File.join(File.split(__FILE__)[0], 'myscript.rb'), options)
@@ -22,6 +22,10 @@ module Daemons
22
22
  @options[:ontop] = t
23
23
  end
24
24
 
25
+ opts.on("-f", "--force", "Force operation") do |t|
26
+ @options[:force] = t
27
+ end
28
+
25
29
  #opts.separator ""
26
30
  #opts.separator "Specific options:"
27
31
 
@@ -113,7 +113,7 @@ module Daemonize
113
113
 
114
114
 
115
115
  # This method causes the current running process to become a daemon
116
- def daemonize(oldmode=0)
116
+ def daemonize(logfile_name = nil, oldmode=0)
117
117
  srand # Split rand streams between spawning and daemonized process
118
118
  safefork and exit # Fork and exit from the parent
119
119
 
@@ -134,15 +134,34 @@ module Daemonize
134
134
  # Make sure all file descriptors are closed
135
135
  ObjectSpace.each_object(IO) do |io|
136
136
  unless [STDIN, STDOUT, STDERR].include?(io)
137
- unless io.closed?
138
- io.close rescue nil
137
+ begin
138
+ unless io.closed?
139
+ io.close
140
+ end
141
+ rescue ::Exception
139
142
  end
140
143
  end
141
144
  end
142
145
 
143
- STDIN.reopen "/dev/null" # Free file descriptors and
144
- STDOUT.reopen "/dev/null", "a" # point them somewhere sensible
145
- STDERR.reopen STDOUT # STDOUT/STDERR should go to a logfile
146
+ # Free file descriptors and
147
+ # point them somewhere sensible
148
+ # STDOUT/STDERR should go to a logfile
149
+
150
+ STDIN.reopen "/dev/null" rescue nil
151
+
152
+ if logfile_name
153
+ begin
154
+ STDOUT.reopen logfile_name, "a"
155
+ rescue ::Exception
156
+ STDOUT.reopen "/dev/null" rescue nil
157
+ end
158
+ else
159
+ STDOUT.reopen "/dev/null" rescue nil
160
+ end
161
+
162
+ STDERR.reopen STDOUT rescue nil
163
+
164
+
146
165
  return oldmode ? sess_id : 0 # Return value is mostly irrelevant
147
166
  end
148
167
  module_function :daemonize
@@ -0,0 +1,101 @@
1
+
2
+ module Daemons
3
+
4
+ require 'daemons/daemonize'
5
+
6
+ class Monitor
7
+
8
+ def self.find(dir, app_name)
9
+ pid_file = PidFile.find_files(dir, app_name)[0]
10
+
11
+ if pid_file
12
+ pid_file = PidFile.existing(pid_file)
13
+
14
+ unless PidFile.running?(pid_file.read)
15
+ pid_file.remove rescue nil
16
+ return
17
+ end
18
+
19
+ monitor = self.allocate
20
+
21
+ monitor.instance_variable_set(:@pid_file, pid_file)
22
+
23
+ return monitor
24
+ end
25
+
26
+ return nil
27
+ end
28
+
29
+
30
+ def initialize(an_app)
31
+ @pid_file = PidFile.new(an_app.pidfile_dir, an_app.group.app_name + '_monitor', false)
32
+ end
33
+
34
+ def start(applications)
35
+ return if applications.empty?
36
+
37
+ fork do
38
+ Daemonize.daemonize
39
+
40
+ begin
41
+ @pid_file.write
42
+
43
+ # at_exit {
44
+ # @pid_file.remove rescue nil
45
+ # }
46
+
47
+ # This part is needed to remove the pid-file if the application is killed by
48
+ # daemons or manually by the user.
49
+ # Note that the applications is not supposed to overwrite the signal handler for
50
+ # 'TERM'.
51
+ #
52
+ # trap('TERM') {
53
+ # @pid_file.remove rescue nil
54
+ # exit
55
+ # }
56
+
57
+ sleep(60)
58
+
59
+ loop do
60
+ applications.each {|a|
61
+ sleep(10)
62
+
63
+ unless a.running?
64
+ a.zap!
65
+
66
+ Process.detach(fork { a.start })
67
+
68
+ sleep(10)
69
+ end
70
+ }
71
+
72
+ sleep(30)
73
+ end
74
+ rescue ::Exception => e
75
+ begin
76
+ File.open(File.join(@pid_file.dir, @pid_file.progname + '.log'), 'a') {|f|
77
+ f.puts Time.now
78
+ f.puts e
79
+ f.puts e.backtrace.inspect
80
+ }
81
+ ensure
82
+ @pid_file.remove rescue nil
83
+ exit!
84
+ end
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+
91
+ def stop
92
+ Process.kill('TERM', @pid_file.read) rescue nil
93
+
94
+ # We try to remove the pid-files by ourselves, in case the application
95
+ # didn't clean it up.
96
+ @pid_file.remove rescue nil
97
+ end
98
+
99
+ end
100
+
101
+ end
@@ -40,6 +40,12 @@ module Daemons
40
40
  end
41
41
 
42
42
 
43
+ def PidFile.running?(pid, additional = nil)
44
+ output = `ps ax`
45
+ return (/#{pid} / =~ output and (additional ? /#{additional}/ =~ output : true))
46
+ end
47
+
48
+
43
49
  # Returns the directory that should be used to write the pid file to
44
50
  # depending on the given mode.
45
51
  #
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.8
3
3
  specification_version: 1
4
4
  name: daemons
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.2.1
7
- date: 2005-03-21
6
+ version: 0.3.0
7
+ date: 2005-04-21
8
8
  summary: A toolkit to convert your script to a controllable daemon
9
9
  require_paths:
10
10
  - lib
@@ -37,16 +37,22 @@ files:
37
37
  - lib/daemons/exceptions.rb
38
38
  - lib/daemons/cmdline.rb
39
39
  - lib/daemons/pidfile.rb
40
+ - lib/daemons/monitor.rb
41
+ - test/tmp
40
42
  - test/testapp.rb
41
43
  - test/tc_main.rb
42
44
  - test/test1.rb
43
- - test/tmp
44
45
  - examples/myserver.rb
45
46
  - examples/myserver_crashing.rb
46
47
  - examples/ctrl_crash.rb
47
48
  - examples/ctrl_ontop.rb
48
49
  - examples/ctrl_exec.rb
49
50
  - examples/ctrl_normal.rb
51
+ - examples/ctrl_multiple.rb
52
+ - examples/myserver_exiting.rb
53
+ - examples/ctrl_exit.rb
54
+ - examples/myserver_crashing.rb.output
55
+ - examples/ctrl_monitor.rb
50
56
  test_files:
51
57
  - test/tc_main.rb
52
58
  rdoc_options: []