blue-daemons 1.1.11

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