daemons 0.2.1 → 0.3.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.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: []