Class: Demon

Inherits:
Object
  • Object
show all
Defined in:
lib/demon.rb,
lib/demon.rb

Constant Summary collapse

Version =
'0.0.666'
Load =
Kernel.method(:load)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args, &block) ⇒ Demon

Returns a new instance of Demon.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/demon.rb', line 66

def initialize(*args, &block)
#
  @options =  Map.extract_options!(args)

#
  @__file__ = (
    @options[:script] or
    @options[:file] or
    @options[:__file__] or
    args.shift or
    (block ? eval('File.expand_path(__FILE__)', block.binding) : nil)
  )
  raise("no __FILE__ groked!") unless @__file__

  @root = @options[:root]

  @mode = @options[:mode]

#
  @script = File.expand_path(@__file__)
  raise("no script groked!") unless test(?s, @script)

#
  @cmdline = generate_cmdline

  @dirname = File.expand_path(File.dirname(@script))
  @basename = File.basename(@script)

  @script_root = File.expand_path(File.dirname(@script))

#
  rails_root = @script_root
  seems_to_be_a_rails_app = false

  42.times do
    seems_to_be_a_rails_app =
      %w( app/controllers app/models app/views config Rakefile ).all? do |subdir|
        test(?e, File.join(rails_root, subdir))
      end

    if seems_to_be_a_rails_app or rails_root == '/'
      break
    end

    rails_root = File.expand_path(File.dirname(rails_root))
  end

  if seems_to_be_a_rails_app
    @rails_root = rails_root

    @demon_dir = File.join(@rails_root, 'log', 'demon')
    @restart_txt = File.join(@rails_root, 'tmp', 'restart.txt')

    self.prefix = File.join(@demon_dir, @basename)

    @root = @rails_root
  else
    @rails_root = false

    @demon_dir = @root || "#{ @script }.demon"
    @restart_txt = File.join(@demon_dir, 'restart.txt')

    self.prefix = @demon_dir

    @root = @demon_dir
  end

#
  @signals    = []
  @started_at = Time.now
  @sleeping   = false
  @ppid       = Process.pid

#
  STDOUT.sync = true
  STDERR.sync = true

  self
end

Class Method Details

.dependenciesObject



30
31
32
33
34
# File 'lib/demon.rb', line 30

def dependencies
  {
    'map'               => [ 'map'               , ' >= 6.0.0' ]
  }
end

.helpObject



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/demon.rb', line 245

def Demon.help
  {
    'start'   => 'start in daemon mode',
    'run'     => 'run in the foreground, but otherwise like a daemon',
    'stop'    => 'stop any currently running daemon',
    'restart' => 'restart any currently running daemon, or start a new one',
    'pid'     => 'print the pid of the running daemon, iff any',
    'ping'    => 'ensure a daemon is running, start one iff not',
    'signal'  => 'hit the daemon, if any, with SIGUSR2',
    'tail'    => 'tail -F all auxillary files (lock files, logs, etc)',
    'fuser'   => 'report the fuser of any auxillary files (lock files, logs, etc)',
    'log'     => 'display the location of the log file',
    'root'    => 'display the location of the root daemon dir (lock files, logs, etc)',
    'modes'   => 'print all modes, even those without "help"',
    'help'    => 'this message'
  }
end

.libdir(*args, &block) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/demon.rb', line 12

def libdir(*args, &block)
  @libdir ||= File.expand_path(__FILE__).sub(/\.rb$/,'')
  libdir = args.empty? ? @libdir : File.join(@libdir, *args.map{|arg| arg.to_s})
ensure
  if block
    begin
      $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.first==libdir
      module_eval(&block)
    ensure
      $LOAD_PATH.shift() if $LOAD_PATH.first==libdir
    end
  end
end

.load(*args, &block) ⇒ Object



26
27
28
# File 'lib/demon.rb', line 26

def load(*args, &block)
  libdir{ Load.call(*args, &block) }
end

.modesObject



236
237
238
# File 'lib/demon.rb', line 236

def Demon.modes
  instance_methods.grep(/mode_(.*)/).map{|mode| mode.to_s.split('_').last}
end

.run(*args, &block) ⇒ Object



208
209
210
# File 'lib/demon.rb', line 208

def Demon.run(*args, &block)
  new(*args).tap{|demon| demon.run(&block)}
end

.start(*args, &block) ⇒ Object



198
199
200
# File 'lib/demon.rb', line 198

def Demon.start(*args, &block)
  new(*args).tap{|demon| demon.start(&block)}
end

.versionObject



8
9
10
# File 'lib/demon.rb', line 8

def version
  Demon::Version
end

Instance Method Details

#boot!Object



470
471
472
473
474
475
476
# File 'lib/demon.rb', line 470

def boot!
  if @rails_root
    Dir.chdir(@rails_root)
    require File.join(@rails_root, 'config', 'boot')
    require File.join(@rails_root, 'config', 'environment')
  end
end

#cap?(&block) ⇒ Boolean

Returns:

  • (Boolean)


565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
# File 'lib/demon.rb', line 565

def cap?(&block)
  realpath = proc do |path|
    begin
      (path.is_a?(Pathname) ? path : Pathname.new(path.to_s)).realpath.to_s
    rescue Errno::ENOENT
      nil
    end
  end

  cap_root = realpath[@rails_root || @root]

  shared_path = File.expand_path('../../shared', cap_root)
  cap_path = File.dirname(shared_path)
  shared_public_system_path = File.expand_path('../../shared/system')
  public_path = File.join(cap_root, 'public')

  public_system_path = File.join(public_path.to_s, 'system')
 
  is_cap_deploy =
    test(?e, shared_public_system_path) and
    test(?l, public_system_path) and
    realpath[shared_public_system_path] == realpath[public_system_path]

  return false unless is_cap_deploy

  args = 
    if block
      [cap_path].slice(block.arity > 0 ? (0 ... block.arity) : (0 .. -1))
    else
      []
    end
  block ? block.call(*args) : cap_path
end

#cmdline!Object



505
506
507
508
509
# File 'lib/demon.rb', line 505

def cmdline!
  open(@cmdline_file, 'w+') do |fd|
    fd.puts(Array(@cmdline).join(' '))
  end
end

#current_path_for(path) ⇒ Object



554
555
556
# File 'lib/demon.rb', line 554

def current_path_for(path)
  path.to_s.gsub(%r|\breleases/\d+\b|, 'current')
end

#daemonize!(options = {}, &block) ⇒ Object

daemonize{|pid| puts “the pid of the daemon is #{ pid }”}



645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
# File 'lib/demon.rb', line 645

def daemonize!(options = {}, &block)
# optional directory and umask
#
  chdir = options[:chdir] || options['chdir'] || '.'
  umask = options[:umask] || options['umask'] || 0

# drop to the background avoiding the possibility of zombies..
#
  detach!(&block)

# close all open io handles *except* these ones
#
  keep_ios(STDIN, STDOUT, STDERR, @lock)

# sane directory and umask
#
  Dir::chdir(chdir)
  File::umask(umask)

# global daemon flag
#
  $DAEMON = true
end

#detach!(&block) ⇒ Object



669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
# File 'lib/demon.rb', line 669

def detach!(&block)
# setup a pipe to relay the grandchild pid through
#
  a, b = IO.pipe

# in the parent we wait for the pid, wait on our child to avoid zombies, and
# then exit
#
  if fork
    b.close
    pid = Integer(a.read.strip)
    a.close
    block.call(pid) if block
    Process.waitall
    exit!
  end

# the child simply exits so it can be reaped - avoiding zombies.  the pipes
# are inherited in the grandchild
#
  if fork
    exit!
  end

# finally, the grandchild sends it's pid back up the pipe to the parent is
# aware of the pid
#
  a.close
  b.puts(Process.pid)
  b.close

# might as well nohup too...
#
  Process::setsid rescue nil
end

#generate_cmdlineObject



549
550
551
552
# File 'lib/demon.rb', line 549

def generate_cmdline
  current_script = current_path_for(@script)
  [which_ruby, current_script, 'start']
end

#keep_ios(*ios) ⇒ Object



764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
# File 'lib/demon.rb', line 764

def keep_ios(*ios)
  filenos = []

  ios.flatten.compact.each do |io|
    begin
      fileno = io.respond_to?(:fileno) ? io.fileno : Integer(io)
      filenos.push(fileno)
    rescue Object
      next
    end
  end

  ObjectSpace.each_object(IO) do |io|
    begin
      fileno = io.fileno
      next if filenos.include?(fileno)
      io.close unless io.closed?
    rescue Object
      next
    end
  end
end

#lock!(options = {}) ⇒ Object



478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'lib/demon.rb', line 478

def lock!(options = {})
  complain = options['complain'] || options[:complain]
  fd = open(@lock_file, 'r+')
  status = fd.flock(File::LOCK_EX|File::LOCK_NB)

  unless status == 0
    if complain
      pid = Integer(IO.read(@pid_file)) rescue '?'
      warn("instance(#{ pid }) is already running!")
    end
    exit(42)
  end
  @lock = fd # prevent garbage collection from closing the file!
  at_exit{ unlock! }
end

#log!Object



537
538
539
540
541
542
# File 'lib/demon.rb', line 537

def log!
  logger.info("START - #{ Process.pid }")
  at_exit do
    logger.info("STOP - #{ Process.pid }") rescue nil
  end
end

#loggerObject



619
620
621
622
623
624
# File 'lib/demon.rb', line 619

def logger
  @logger ||= (
    require 'logger' unless defined?(Logger)
    Logger.new(STDERR)
  )
end

#logger=(logger) ⇒ Object



626
627
628
# File 'lib/demon.rb', line 626

def logger=(logger)
  @logger = logger
end

#logging!Object



737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
# File 'lib/demon.rb', line 737

def logging!
  number_rolled = 7
  megabytes     = 2 ** 20
  max_size      = 42 * megabytes

  @logger =
    if STDIN.tty?
      if defined?(Logging)
        ::Logging.logger(STDERR)
      else
        ::Logger.new(STDERR)
      end
    else
      if defined?(Logging)
        options = defined?(Lockfile) ? {:safe => true} : {}
        ::Logging.logger(@log_file, number_rolled, max_size, options)
      else
        ::Logger.new(@log_file, number_rolled, max_size)
      end
    end

  @logger.level = ::Logger::INFO rescue nil if production?
  @logger.level = ::Logger::DEBUG if STDERR.tty?

  @logger
end

#logging_errors(&block) ⇒ Object



630
631
632
633
634
635
636
637
638
639
# File 'lib/demon.rb', line 630

def logging_errors(&block)
  begin
    block.call()
  rescue SignalException => e
    logger.info(e)
    exit(0)
  rescue => e
    logger.error(e)
  end
end

#mode_fuserObject



356
357
358
# File 'lib/demon.rb', line 356

def mode_fuser
  exec("fuser #{ @lock_file.inspect }")
end

#mode_helpObject



263
264
265
266
# File 'lib/demon.rb', line 263

def mode_help
  puts(Demon.help.to_yaml)
  exit(42)
end

#mode_logObject



413
414
415
416
# File 'lib/demon.rb', line 413

def mode_log
  puts(@log_file)
  exit(42)
end

#mode_modesObject



240
241
242
243
# File 'lib/demon.rb', line 240

def mode_modes
  puts Demon.modes.join('|')
  exit(42)
end

#mode_pidObject



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/demon.rb', line 340

def mode_pid
  pid = Integer(IO.read(@pid_file)) rescue nil
  if pid
    begin
      Process.kill(0, pid)
      puts(pid)
      exit(0)
    rescue Errno::ESRCH
      exit(1)
    end
  else
    exit(1)
  end
  exit(1)
end

#mode_pingObject



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/demon.rb', line 268

def mode_ping
  pid = Integer(IO.read(@pid_file)) rescue nil

  if pid
    signaled = false

    begin
      Process.kill('SIGALRM', pid)
      signaled = true
    rescue Object
      nil
    end

    if signaled
      STDOUT.puts(pid)
      exit
    end
  end

  Kernel.exec("#{ @script } start")
end

#mode_restartObject



328
329
330
331
332
333
334
335
336
337
338
# File 'lib/demon.rb', line 328

def mode_restart
  begin
    pid = Integer(IO.read(@pid_file)) rescue nil
    Process.kill('HUP', pid)
    puts "Process #{pid} signaled to restart"
    exit(0)
  rescue
    puts "No running process found. Starting a new one."
    mode_start
  end
end

#mode_rootObject



418
419
420
421
# File 'lib/demon.rb', line 418

def mode_root
  puts(@root)
  exit(42)
end

#mode_runObject



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/demon.rb', line 290

def mode_run
  lock!(:complain => true)

  pid!

  cmdline!

  trap!

  boot!

  logging!

  log!
end

#mode_signal(signal = 'SIGUSR2') ⇒ Object



403
404
405
406
407
408
409
410
411
# File 'lib/demon.rb', line 403

def mode_signal(signal = 'SIGUSR2')
  pid = Integer(IO.read(@pid_file)) rescue nil
  if pid
    Process.kill(signal, pid)
    puts(pid)
    exit(0)
  end
  exit(42)
end

#mode_startObject



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/demon.rb', line 306

def mode_start
  lock!(:complain => true)

  daemonize!{|pid| puts(pid)}

  redirect_io!

  pid!

  cmdline!

  trap!

  boot!

  logging!

  signal_if_redeployed!

  log!
end

#mode_stopObject



360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/demon.rb', line 360

def mode_stop
  pid = Integer(IO.read(@pid_file)) rescue nil
  if pid
    alive = true

    %w( QUIT TERM ).each do |signal|
      begin
        Process.kill(signal, pid)
      rescue Errno::ESRCH
        nil
      end

      42.times do
        begin
          Process.kill(0, pid)
          sleep(rand)
        rescue Errno::ESRCH
          alive = false
          puts(pid)
          exit(0)
        end
      end
    end

    if alive
      begin
        Process.kill(-9, pid)
        sleep(rand)
      rescue Errno::ESRCH
        nil
      end

      begin
        Process.kill(0, pid)
      rescue Errno::ESRCH
        puts(pid)
        exit(0)
      end
    end
  end
  exit(1)
end

#mode_tailObject



423
424
425
426
# File 'lib/demon.rb', line 423

def mode_tail
  system("tail -F #{ @stdout_file.inspect } #{ @stderr_file.inspect } #{ @log_file.inspect }")
  exit(42)
end

#pid!Object



498
499
500
501
502
503
# File 'lib/demon.rb', line 498

def pid!
  open(@pid_file, 'w+') do |fd|
    fd.puts(Process.pid)
  end
  at_exit{ FileUtils.rm_f(@pid_file) }
end

#prefixObject



146
147
148
# File 'lib/demon.rb', line 146

def prefix
  @prefix
end

#prefix=(prefix) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/demon.rb', line 150

def prefix=(prefix)
  @prefix = File.expand_path(prefix.to_s)

  @lock_file    = File.join(@prefix, 'lock')
  @log_file     = File.join(@prefix, 'log')
  @pid_file     = File.join(@prefix, 'pid')
  @cmdline_file = File.join(@prefix, 'cmdline')
  @stdin_file   = File.join(@prefix, 'stdin')
  @stdout_file  = File.join(@prefix, 'stdout')
  @stderr_file  = File.join(@prefix, 'stderr')

  FileUtils.mkdir_p(@prefix)

  %w( lock log pid cmdline stdin stdout stderr ).each do |which|
    file = instance_variable_get("@#{ which }_file")
    FileUtils.touch(file)
  end

  @prefix
end

#process_signalsObject



429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# File 'lib/demon.rb', line 429

def process_signals
  if signaled?
    signals.uniq.each do |signal|
      case signal.to_s
        when /HUP/i
          logger.info('RESTART - signal')
          restart!
        when /USR1/i
          logger.info('RESTART - deploy')
          restart!
        when /USR2/i
          nil
        when /ALRM/i
          nil
      end
    end
    signals.clear
  end
end

#production?Boolean

Returns:

  • (Boolean)


599
600
601
602
603
604
605
# File 'lib/demon.rb', line 599

def production?
  if defined?(Rails.env)
    Rails.env.production?
  else
    true
  end
end

#redeployed?Boolean

Returns:

  • (Boolean)


544
545
546
547
# File 'lib/demon.rb', line 544

def redeployed?
  t = File.stat(current_path_for(@restart_txt)).mtime rescue @started_at
  t > @started_at
end

#redirect_io!(options = {}) ⇒ Object



705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
# File 'lib/demon.rb', line 705

def redirect_io!(options = {})
  stdin = options[:stdin] || @stdin_file
  stdout = options[:stdout] || @stdout_file
  stderr = options[:stderr] || @stderr_file

  {
    STDIN => stdin, STDOUT => stdout, STDERR => stderr
  }.each do |io, file|
    opened = false

    fd =
      case
        when file.is_a?(IO)
          file
        when file.to_s == 'null'
          opened = true
          open('/dev/null', 'ab+')
        else
          opened = true
          open(file, 'ab+')
      end

    begin
      fd.sync = true rescue nil
      fd.truncate(0) rescue nil
      io.reopen(fd)
    ensure
      fd.close rescue nil if opened
    end
  end
end

#restart!Object



458
459
460
461
462
463
464
465
466
467
468
# File 'lib/demon.rb', line 458

def restart!
  exit!(0) if fork

  logger.info('CMD - %s' % Array(@cmdline).join(' '))

  unlock!

  keep_ios(STDIN, STDOUT, STDERR)

  Kernel.exec(*@cmdline)
end

#run(which = :run, &block) ⇒ Object



202
203
204
205
206
# File 'lib/demon.rb', line 202

def run(which = :run, &block)
  mode = "mode_#{ which }".downcase
  send(mode) if respond_to?(mode)
  run_forever_handling_signals_and_logging_errors!(&block)
end

#run_forever_handling_signals_and_logging_errors!(&block) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/demon.rb', line 220

def run_forever_handling_signals_and_logging_errors!(&block)
  loop do
    catch(:signals) do
      process_signals

      begin
        block.call() if block
      rescue => e
        logger.error(e)
      ensure
        wait(420) unless $! # NOTE: signals wake this up!
      end
    end
  end
end

#signal_if_redeployed!Object



525
526
527
528
529
530
531
532
533
534
535
# File 'lib/demon.rb', line 525

def signal_if_redeployed!
  seconds = production? ? 10 : 1

  Thread.new do
    Thread.current.abort_on_exception = true
    loop do
      Kernel.sleep(seconds)
      Process.kill(:USR1, Process.pid) if redeployed?
    end
  end
end

#signaled?Boolean

Returns:

  • (Boolean)


615
616
617
# File 'lib/demon.rb', line 615

def signaled?
  !signals.empty?
end

#sleeping?(&block) ⇒ Boolean

Returns:

  • (Boolean)


607
608
609
610
611
612
613
# File 'lib/demon.rb', line 607

def sleeping?(&block)
  if block
    block.call if @sleeping
  else
    @sleeping == true
  end
end

#start(which = :start, &block) ⇒ Object



192
193
194
195
196
# File 'lib/demon.rb', line 192

def start(which = :start, &block)
  mode = "mode_#{ which }".downcase
  send(mode) if respond_to?(mode)
  run_forever_handling_signals_and_logging_errors!(&block)
end

#trap!Object



511
512
513
514
515
516
517
518
519
520
521
522
523
# File 'lib/demon.rb', line 511

def trap!
  %w( SIGHUP SIGALRM SIGUSR1 SIGUSR2 ).each do |signal|
    trap(signal) do |sig|
      signals.push(signal)
      logger.debug("SIGNAL - #{ signal }")
      throw(:signals, signal) if sleeping?
    end
  end

  trap('SIGQUIT'){ exit(42) }
  trap('SIGTERM'){ exit(42) }
  trap('SIGINT'){ exit(42) }
end

#unlock!Object



494
495
496
# File 'lib/demon.rb', line 494

def unlock!
  @lock.flock(File::LOCK_UN|File::LOCK_NB) if @lock
end

#wait(seconds) ⇒ Object



449
450
451
452
453
454
455
456
# File 'lib/demon.rb', line 449

def wait(seconds)
  begin
    @sleeping = true
    Kernel.sleep(seconds)
  ensure
    @sleeping = false
  end
end

#which_rubyObject



558
559
560
561
562
563
# File 'lib/demon.rb', line 558

def which_ruby
  c = ::RbConfig::CONFIG
  ruby = File::join(c['bindir'], c['ruby_install_name']) << c['EXEEXT']
  raise "ruby @ #{ ruby } not executable!?" unless test(?e, ruby)
  ruby
end