daemons 1.1.9 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -1
  3. data/README.md +207 -0
  4. data/Releases +85 -24
  5. data/examples/call/call.rb +13 -16
  6. data/examples/call/call_monitor.rb +13 -17
  7. data/examples/daemonize/daemonize.rb +4 -8
  8. data/examples/run/ctrl_crash.rb +0 -1
  9. data/examples/run/ctrl_custom_logfiles.rb +18 -0
  10. data/examples/run/ctrl_exec.rb +0 -1
  11. data/examples/run/ctrl_exit.rb +0 -1
  12. data/examples/run/ctrl_keep_pid_files.rb +1 -3
  13. data/examples/run/ctrl_monitor.rb +0 -1
  14. data/examples/run/ctrl_monitor_multiple.rb +17 -0
  15. data/examples/run/ctrl_monitor_nocrash.rb +15 -0
  16. data/examples/run/ctrl_multiple.rb +0 -1
  17. data/examples/run/ctrl_ontop.rb +0 -1
  18. data/examples/run/ctrl_optionparser.rb +5 -7
  19. data/examples/run/ctrl_proc.rb +8 -9
  20. data/examples/run/ctrl_proc_multiple.rb +4 -6
  21. data/examples/run/ctrl_proc_rand.rb +2 -4
  22. data/examples/run/ctrl_proc_simple.rb +0 -1
  23. data/examples/run/myserver.rb +0 -1
  24. data/examples/run/myserver_crashing.rb +5 -5
  25. data/examples/run/myserver_exiting.rb +2 -2
  26. data/examples/run/myserver_hanging.rb +4 -5
  27. data/examples/run/myserver_slowstop.rb +5 -6
  28. data/lib/daemons/application.rb +235 -229
  29. data/lib/daemons/application_group.rb +115 -100
  30. data/lib/daemons/change_privilege.rb +2 -4
  31. data/lib/daemons/cmdline.rb +75 -62
  32. data/lib/daemons/controller.rb +36 -54
  33. data/lib/daemons/daemonize.rb +74 -75
  34. data/lib/daemons/etc_extension.rb +3 -4
  35. data/lib/daemons/exceptions.rb +11 -13
  36. data/lib/daemons/monitor.rb +57 -77
  37. data/lib/daemons/pid.rb +26 -56
  38. data/lib/daemons/pidfile.rb +49 -44
  39. data/lib/daemons/pidmem.rb +5 -9
  40. data/lib/daemons/reporter.rb +54 -0
  41. data/lib/daemons/syslogio.rb +240 -0
  42. data/lib/daemons/version.rb +3 -0
  43. data/lib/daemons.rb +87 -77
  44. metadata +111 -46
  45. data/README +0 -214
  46. data/Rakefile +0 -90
  47. data/TODO +0 -2
  48. data/setup.rb +0 -1360
@@ -2,80 +2,110 @@ require 'daemons/pidfile'
2
2
  require 'daemons/pidmem'
3
3
  require 'daemons/change_privilege'
4
4
  require 'daemons/daemonize'
5
+ require 'daemons/exceptions'
6
+ require 'daemons/reporter'
5
7
 
6
8
  require 'timeout'
7
9
 
8
-
9
10
  module Daemons
10
-
11
11
  class Application
12
-
13
12
  attr_accessor :app_argv
14
13
  attr_accessor :controller_argv
15
-
14
+
16
15
  # the Pid instance belonging to this application
17
16
  attr_reader :pid
18
-
17
+
19
18
  # the ApplicationGroup the application belongs to
20
19
  attr_reader :group
21
-
20
+
22
21
  # my private options
23
22
  attr_reader :options
24
-
25
-
23
+
26
24
  SIGNAL = (RUBY_PLATFORM =~ /win32/ ? 'KILL' : 'TERM')
27
-
28
-
25
+
29
26
  def initialize(group, add_options = {}, pid = nil)
30
27
  @group = group
31
28
  @options = group.options.dup
32
29
  @options.update(add_options)
33
-
30
+
31
+ ['dir', 'log_dir', 'logfilename', 'output_logfilename'].each do |k|
32
+ @options[k] = File.expand_path(@options[k]) if @options.key?(k)
33
+ end
34
+
34
35
  @dir_mode = @dir = @script = nil
35
-
36
+
36
37
  @force_kill_waittime = @options[:force_kill_waittime] || 20
37
-
38
+
39
+ @signals_and_waits = parse_signals_and_waits(@options[:signals_and_waits])
40
+
41
+ @show_status_callback = method(:default_show_status)
42
+
43
+ @report = Reporter.new(@options)
44
+
38
45
  unless @pid = pid
39
46
  if @options[:no_pidfiles]
40
47
  @pid = PidMem.new
41
48
  elsif dir = pidfile_dir
42
- @pid = PidFile.new(dir, @group.app_name, @group.multiple)
49
+ @pid = PidFile.new(dir, @group.app_name, @group.multiple, @options[:pid_delimiter])
43
50
  else
44
51
  @pid = PidMem.new
45
52
  end
46
53
  end
47
54
  end
48
-
55
+
56
+ def show_status_callback=(function)
57
+ @show_status_callback =
58
+ if function.respond_to?(:call)
59
+ function
60
+ else
61
+ method(function)
62
+ end
63
+ end
64
+
49
65
  def change_privilege
50
66
  user = options[:user]
51
67
  group = options[:group]
52
- CurrentProcess.change_privilege(user, group) if user
68
+ if user
69
+ @report.changing_process_privilege(user, group)
70
+ CurrentProcess.change_privilege(user, group)
71
+ end
53
72
  end
54
-
73
+
55
74
  def script
56
- @script || @group.script
75
+ @script or group.script
57
76
  end
58
-
77
+
59
78
  def pidfile_dir
60
- Pid.dir(@dir_mode || @group.dir_mode, @dir || @group.dir, @script || @group.script)
79
+ Pid.dir dir_mode, dir, script
61
80
  end
62
-
81
+
63
82
  def logdir
64
- logdir = options[:log_dir]
65
- unless logdir
66
- logdir = options[:dir_mode] == :system ? '/var/log' : pidfile_dir
67
- end
68
- logdir
83
+ options[:log_dir] or
84
+ options[:dir_mode] == :system ? '/var/log' : pidfile_dir
69
85
  end
70
-
86
+
87
+ def output_logfilename
88
+ options[:output_logfilename] or "#{@group.app_name}.output"
89
+ end
90
+
71
91
  def output_logfile
72
- (options[:log_output] && logdir) ? File.join(logdir, @group.app_name + '.output') : nil
92
+ if log_output_syslog?
93
+ 'SYSLOG'
94
+ elsif log_output?
95
+ File.join logdir, output_logfilename
96
+ end
97
+ end
98
+
99
+ def logfilename
100
+ options[:logfilename] or "#{@group.app_name}.log"
73
101
  end
74
-
102
+
75
103
  def logfile
76
- logdir ? File.join(logdir, @group.app_name + '.log') : nil
104
+ if logdir
105
+ File.join logdir, logfilename
106
+ end
77
107
  end
78
-
108
+
79
109
  # this function is only used to daemonize the currently running process (Daemons.daemonize)
80
110
  def start_none
81
111
  unless options[:ontop]
@@ -83,163 +113,158 @@ module Daemons
83
113
  else
84
114
  Daemonize.simulate(output_logfile)
85
115
  end
86
-
116
+
87
117
  @pid.pid = Process.pid
88
-
89
-
118
+
90
119
  # We need this to remove the pid-file if the applications exits by itself.
91
- # Note that <tt>at_text</tt> will only be run if the applications exits by calling
120
+ # Note that <tt>at_text</tt> will only be run if the applications exits by calling
92
121
  # <tt>exit</tt>, and not if it calls <tt>exit!</tt> (so please don't call <tt>exit!</tt>
93
122
  # in your application!
94
123
  #
95
- at_exit {
124
+ at_exit do
96
125
  begin; @pid.cleanup; rescue ::Exception; end
97
-
126
+
98
127
  # If the option <tt>:backtrace</tt> is used and the application did exit by itself
99
128
  # create a exception log.
100
- if options[:backtrace] and not options[:ontop] and not $daemons_sigterm
101
- begin; exception_log(); rescue ::Exception; end
129
+ if options[:backtrace] && !options[:ontop] && !$daemons_sigterm
130
+ begin; exception_log; rescue ::Exception; end
102
131
  end
103
-
104
- }
105
-
106
- # This part is needed to remove the pid-file if the application is killed by
132
+
133
+ end
134
+
135
+ # This part is needed to remove the pid-file if the application is killed by
107
136
  # daemons or manually by the user.
108
137
  # Note that the applications is not supposed to overwrite the signal handler for
109
138
  # 'TERM'.
110
139
  #
111
- trap(SIGNAL) {
140
+ trap(SIGNAL) do
112
141
  begin; @pid.cleanup; rescue ::Exception; end
113
142
  $daemons_sigterm = true
114
-
143
+
115
144
  if options[:hard_exit]
116
145
  exit!
117
146
  else
118
147
  exit
119
148
  end
120
- }
149
+ end
121
150
  end
122
-
151
+
123
152
  def start_exec
124
153
  if options[:backtrace]
125
- puts "option :backtrace is not supported with :mode => :exec, ignoring"
154
+ @report.backtrace_not_supported
126
155
  end
127
-
156
+
128
157
  unless options[:ontop]
129
158
  Daemonize.daemonize(output_logfile, @group.app_name)
130
159
  else
131
160
  Daemonize.simulate(output_logfile)
132
161
  end
133
-
162
+
134
163
  # note that we cannot remove the pid file if we run in :ontop mode (i.e. 'ruby ctrl_exec.rb run')
135
164
  @pid.pid = Process.pid
136
-
137
- ENV['DAEMONS_ARGV'] = @controller_argv.join(' ')
138
- # haven't tested yet if this is really passed to the exec'd process...
139
-
140
- started()
141
- Kernel.exec(script(), *(@app_argv || []))
165
+
166
+ ENV['DAEMONS_ARGV'] = @controller_argv.join(' ')
167
+
168
+ started
169
+ Kernel.exec(script, *(@app_argv || []))
142
170
  end
143
-
171
+
144
172
  def start_load
145
173
  unless options[:ontop]
146
174
  Daemonize.daemonize(output_logfile, @group.app_name)
147
175
  else
148
176
  Daemonize.simulate(output_logfile)
149
177
  end
150
-
178
+
151
179
  @pid.pid = Process.pid
152
-
153
-
180
+
154
181
  # We need this to remove the pid-file if the applications exits by itself.
155
- # Note that <tt>at_exit</tt> will only be run if the applications exits by calling
182
+ # Note that <tt>at_exit</tt> will only be run if the applications exits by calling
156
183
  # <tt>exit</tt>, and not if it calls <tt>exit!</tt> (so please don't call <tt>exit!</tt>
157
184
  # in your application!
158
185
  #
159
- at_exit {
186
+ at_exit do
160
187
  begin; @pid.cleanup; rescue ::Exception; end
161
-
188
+
162
189
  # If the option <tt>:backtrace</tt> is used and the application did exit by itself
163
190
  # create a exception log.
164
- if options[:backtrace] and not options[:ontop] and not $daemons_sigterm
165
- begin; exception_log(); rescue ::Exception; end
191
+ if options[:backtrace] && !options[:ontop] && !$daemons_sigterm
192
+ begin; exception_log; rescue ::Exception; end
166
193
  end
167
-
168
- }
169
-
170
- # This part is needed to remove the pid-file if the application is killed by
194
+
195
+ end
196
+
197
+ # This part is needed to remove the pid-file if the application is killed by
171
198
  # daemons or manually by the user.
172
199
  # Note that the applications is not supposed to overwrite the signal handler for
173
200
  # 'TERM'.
174
201
  #
175
202
  $daemons_stop_proc = options[:stop_proc]
176
- trap(SIGNAL) {
203
+ trap(SIGNAL) do
177
204
  begin
178
- if $daemons_stop_proc
179
- $daemons_stop_proc.call
180
- end
205
+ if $daemons_stop_proc
206
+ $daemons_stop_proc.call
207
+ end
181
208
  rescue ::Exception
182
209
  end
183
-
210
+
184
211
  begin; @pid.cleanup; rescue ::Exception; end
185
212
  $daemons_sigterm = true
186
-
213
+
187
214
  if options[:hard_exit]
188
215
  exit!
189
216
  else
190
217
  exit
191
218
  end
192
- }
193
-
219
+ end
220
+
194
221
  # Now we really start the script...
195
222
  $DAEMONS_ARGV = @controller_argv
196
223
  ENV['DAEMONS_ARGV'] = @controller_argv.join(' ')
197
-
224
+
198
225
  ARGV.clear
199
226
  ARGV.concat @app_argv if @app_argv
200
-
201
- started()
202
- # TODO: begin - rescue - end around this and exception logging
203
- load script()
227
+
228
+ started
229
+ # TODO: exception logging
230
+ load script
204
231
  end
205
-
232
+
206
233
  def start_proc
207
234
  return unless p = options[:proc]
208
-
209
- myproc = proc do
210
-
211
- @pid.pid = Process.pid
212
-
235
+
236
+ myproc = proc do
237
+
213
238
  # We need this to remove the pid-file if the applications exits by itself.
214
- # Note that <tt>at_text</tt> will only be run if the applications exits by calling
239
+ # Note that <tt>at_text</tt> will only be run if the applications exits by calling
215
240
  # <tt>exit</tt>, and not if it calls <tt>exit!</tt> (so please don't call <tt>exit!</tt>
216
241
  # in your application!
217
242
  #
218
- at_exit {
243
+ at_exit do
219
244
  begin; @pid.cleanup; rescue ::Exception; end
220
245
 
221
246
  # If the option <tt>:backtrace</tt> is used and the application did exit by itself
222
247
  # create a exception log.
223
- if options[:backtrace] and not options[:ontop] and not $daemons_sigterm
224
- begin; exception_log(); rescue ::Exception; end
248
+ if options[:backtrace] && !options[:ontop] && !$daemons_sigterm
249
+ begin; exception_log; rescue ::Exception; end
225
250
  end
226
251
 
227
- }
252
+ end
228
253
 
229
- # This part is needed to remove the pid-file if the application is killed by
254
+ # This part is needed to remove the pid-file if the application is killed by
230
255
  # daemons or manually by the user.
231
256
  # Note that the applications is not supposed to overwrite the signal handler for
232
257
  # 'TERM'.
233
258
  #
234
259
  $daemons_stop_proc = options[:stop_proc]
235
- trap(SIGNAL) {
260
+ trap(SIGNAL) do
236
261
  begin
237
- if $daemons_stop_proc
238
- $daemons_stop_proc.call
239
- end
262
+ if $daemons_stop_proc
263
+ $daemons_stop_proc.call
264
+ end
240
265
  rescue ::Exception
241
266
  end
242
-
267
+
243
268
  begin; @pid.cleanup; rescue ::Exception; end
244
269
  $daemons_sigterm = true
245
270
 
@@ -248,42 +273,28 @@ module Daemons
248
273
  else
249
274
  exit
250
275
  end
251
- }
252
-
253
- started()
254
-
255
- p.call()
276
+ end
277
+ p.call
256
278
  end
257
-
279
+
258
280
  unless options[:ontop]
259
- Daemonize.call_as_daemon(myproc, output_logfile, @group.app_name)
260
-
281
+ @pid.pid = Daemonize.call_as_daemon(myproc, output_logfile, @group.app_name)
282
+
261
283
  else
262
284
  Daemonize.simulate(output_logfile)
263
-
285
+
264
286
  myproc.call
265
-
266
- # why did we use this??
267
- # Thread.new(&options[:proc])
268
-
269
- # why did we use the code below??
270
- # unless pid = Process.fork
271
- # @pid.pid = pid
272
- # Daemonize.simulate(logfile)
273
- # options[:proc].call
274
- # exit
275
- # else
276
- # Process.detach(@pid.pid)
277
- # end
278
287
  end
279
-
288
+ started
280
289
  end
281
-
282
-
283
- def start
290
+
291
+ def start(restart = false)
284
292
  change_privilege
285
- @group.create_monitor(@group.applications[0] || self) unless options[:ontop] # we don't monitor applications in the foreground
286
-
293
+
294
+ unless restart
295
+ @group.create_monitor(self) unless options[:ontop] # we don't monitor applications in the foreground
296
+ end
297
+
287
298
  case options[:mode]
288
299
  when :none
289
300
  # this is only used to daemonize the currently running process
@@ -298,32 +309,14 @@ module Daemons
298
309
  start_load
299
310
  end
300
311
  end
301
-
312
+
302
313
  def started
303
314
  if pid = @pid.pid
304
- puts "#{self.group.app_name}: process with pid #{pid} started."
305
- STDOUT.flush
315
+ @report.process_started(group.app_name, pid)
306
316
  end
307
317
  end
308
-
309
-
310
- # def run
311
- # if @group.controller.options[:exec]
312
- # run_via_exec()
313
- # else
314
- # run_via_load()
315
- # end
316
- # end
317
- #
318
- # def run_via_exec
319
- #
320
- # end
321
- #
322
- # def run_via_load
323
- #
324
- # end
325
-
326
- def reload
318
+
319
+ def reload
327
320
  if @pid.pid == 0
328
321
  zap
329
322
  start
@@ -342,122 +335,112 @@ module Daemons
342
335
  # one cannot catch exceptions that are thrown in threads other than the main
343
336
  # thread.
344
337
  #
345
- # This function searches for all exceptions in memory and outputs them to STDERR
338
+ # This function searches for all exceptions in memory and outputs them to $stderr
346
339
  # (if it is connected) and to a log file in the pid-file directory.
347
340
  #
348
341
  def exception_log
349
342
  return unless logfile
350
-
343
+
351
344
  require 'logger'
352
-
345
+
353
346
  l_file = Logger.new(logfile)
354
-
347
+
355
348
  # the code below finds the last exception
356
349
  e = nil
357
-
358
- ObjectSpace.each_object {|o|
350
+
351
+ ObjectSpace.each_object do |o|
359
352
  if ::Exception === o
360
353
  e = o
361
354
  end
362
- }
363
-
364
- l_file.info "*** below you find the most recent exception thrown, this will be likely (but not certainly) the exception that made the application exit abnormally ***"
355
+ end
356
+
357
+ l_file.info '*** below you find the most recent exception thrown, this will be likely (but not certainly) the exception that made the application exit abnormally ***'
365
358
  l_file.error e
366
-
367
- l_file.info "*** below you find all exception objects found in memory, some of them may have been thrown in your application, others may just be in memory because they are standard exceptions ***"
368
-
359
+
360
+ l_file.info '*** below you find all exception objects found in memory, some of them may have been thrown in your application, others may just be in memory because they are standard exceptions ***'
361
+
369
362
  # this code logs every exception found in memory
370
- ObjectSpace.each_object {|o|
363
+ ObjectSpace.each_object do |o|
371
364
  if ::Exception === o
372
365
  l_file.error o
373
366
  end
374
- }
375
-
367
+ end
368
+
376
369
  l_file.close
377
370
  end
378
-
379
-
371
+
380
372
  def stop(no_wait = false)
381
- if not running?
382
- self.zap
373
+ unless running?
374
+ zap
383
375
  return
384
376
  end
385
-
377
+
378
+ # confusing: pid is also a attribute_reader
386
379
  pid = @pid.pid
387
-
380
+
388
381
  # Catch errors when trying to kill a process that doesn't
389
382
  # exist. This happens when the process quits and hasn't been
390
383
  # restarted by the monitor yet. By catching the error, we allow the
391
384
  # pid file clean-up to occur.
392
385
  begin
393
- Process.kill(SIGNAL, pid)
386
+ wait_and_retry_kill_harder(pid, @signals_and_waits, no_wait)
394
387
  rescue Errno::ESRCH => e
395
- puts "#{e} #{pid}"
396
- puts "deleting pid-file."
388
+ @report.output_message("#{e} #{pid}")
389
+ @report.output_message('deleting pid-file.')
397
390
  end
398
-
399
- if not no_wait
400
- if @force_kill_waittime > 0
401
- puts "#{self.group.app_name}: trying to stop process with pid #{pid}..."
402
- STDOUT.flush
403
-
404
- begin
405
- Timeout::timeout(@force_kill_waittime) {
406
- while Pid.running?(pid)
407
- sleep(0.2)
408
- end
409
- }
410
- rescue Timeout::Error
411
- puts "#{self.group.app_name}: process with pid #{pid} won't stop, we forcefully kill it..."
412
- STDOUT.flush
413
-
414
- begin
415
- Process.kill('KILL', pid)
416
- rescue Errno::ESRCH
417
- end
418
-
419
- begin
420
- Timeout::timeout(20) {
421
- while Pid.running?(pid)
422
- sleep(1)
423
- end
424
- }
425
- rescue Timeout::Error
426
- puts "#{self.group.app_name}: unable to forcefully kill process with pid #{pid}."
427
- STDOUT.flush
428
- end
429
- end
430
- end
431
-
432
-
433
- end
434
-
391
+
435
392
  sleep(0.1)
436
393
  unless Pid.running?(pid)
437
394
  # We try to remove the pid-files by ourselves, in case the application
438
395
  # didn't clean it up.
439
- begin; @pid.cleanup; rescue ::Exception; end
440
-
441
- puts "#{self.group.app_name}: process with pid #{pid} successfully stopped."
442
- STDOUT.flush
396
+ zap!
397
+
398
+ @report.stopped_process(group.app_name, pid)
443
399
  end
444
-
445
400
  end
446
-
401
+
402
+ # @param Hash remaing_signals
403
+ # @param Boolean no_wait Send first Signal and return
404
+ def wait_and_retry_kill_harder(pid, remaining_signals, no_wait = false)
405
+ sig_wait = remaining_signals.shift
406
+ sig = sig_wait[:sig]
407
+ wait = sig_wait[:wait]
408
+ Process.kill(sig, pid)
409
+ return if no_wait || !wait.positive?
410
+
411
+ @report.stopping_process(group.app_name, pid, sig, wait)
412
+
413
+ begin
414
+ Timeout.timeout(wait, TimeoutError) do
415
+ sleep(0.2) while Pid.running?(pid)
416
+ end
417
+ rescue TimeoutError
418
+ if remaining_signals.any?
419
+ wait_and_retry_kill_harder(pid, remaining_signals)
420
+ else
421
+ @report.cannot_stop_process(group.app_name, pid)
422
+ end
423
+ end
424
+ end
425
+
447
426
  def zap
448
- @pid.cleanup
427
+ @pid.zap
449
428
  end
450
-
429
+
451
430
  def zap!
452
- begin; @pid.cleanup; rescue ::Exception; end
431
+ begin; @pid.zap; rescue ::Exception; end
453
432
  end
454
-
433
+
455
434
  def show_status
456
- running = self.running?
457
-
458
- puts "#{self.group.app_name}: #{running ? '' : 'not '}running#{(running and @pid.exist?) ? ' [pid ' + @pid.pid.to_s + ']' : ''}#{(@pid.exist? and not running) ? ' (but pid-file exists: ' + @pid.pid.to_s + ')' : ''}"
435
+ @show_status_callback.call(self)
436
+ end
437
+
438
+ def default_show_status(daemon = self)
439
+ running = daemon.running?
440
+
441
+ @report.status(group.app_name, running, daemon.pid.exist?, daemon.pid.pid.to_s)
459
442
  end
460
-
443
+
461
444
  # This function implements a (probably too simle) method to detect
462
445
  # whether the program with the pid found in the pid-file is still running.
463
446
  # It just searches for the pid in the output of <tt>ps ax</tt>, which
@@ -466,12 +449,35 @@ module Daemons
466
449
  # system.
467
450
  #
468
451
  def running?
469
- if @pid.exist?
470
- return Pid.running?(@pid.pid)
452
+ @pid.exist? and Pid.running? @pid.pid
453
+ end
454
+
455
+ private
456
+
457
+ def log_output?
458
+ options[:log_output] && logdir
459
+ end
460
+
461
+ def log_output_syslog?
462
+ options[:log_output_syslog]
463
+ end
464
+
465
+ def dir_mode
466
+ @dir_mode or group.dir_mode
467
+ end
468
+
469
+ def dir
470
+ @dir or group.dir
471
+ end
472
+
473
+ def parse_signals_and_waits(argv)
474
+ unless argv
475
+ return [
476
+ { sig: 'TERM', wait: @force_kill_waittime },
477
+ { sig: 'KILL', wait: 20 }
478
+ ]
471
479
  end
472
-
473
- return false
480
+ argv.split('|').collect{ |part| splitted = part.split(':'); {sig: splitted[0], wait: splitted[1].to_i}}
474
481
  end
475
482
  end
476
-
477
483
  end