Class: Main::Daemon

Inherits:
Object show all
Defined in:
lib/main/daemon.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(main) ⇒ Daemon

Returns a new instance of Daemon.



26
27
28
29
# File 'lib/main/daemon.rb', line 26

def initialize(main)
  @main = main
  @script = @main.script
end

Class Method Details

.commandsObject



113
114
115
# File 'lib/main/daemon.rb', line 113

def Daemon.commands
  instance_methods.map{|method| method.to_s =~ /\Acmd_(.*)/ && $1}.compact
end

Instance Method Details

#alive?Boolean

Returns:

  • (Boolean)


363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/main/daemon.rb', line 363

def alive?
  pid = Integer(IO.read(@pid_file)) rescue nil
  alive = !!pid

  if pid
    alive =
      begin
        Process.kill(0, pid)
        true
      rescue Errno::ESRCH
        false 
      end
  end

  alive
end

#cmd(cmd, &block) ⇒ Object



64
65
66
67
68
# File 'lib/main/daemon.rb', line 64

def cmd(cmd, &block)
  setup!

  process_cmd!(cmd)
end

#cmd_dirObject



308
309
310
311
# File 'lib/main/daemon.rb', line 308

def cmd_dir
  puts(@daemon_dir)
  exit(42)
end

#cmd_infoObject



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/main/daemon.rb', line 130

def cmd_info
  info =
    {
      'main'      => @main.program,
      'script'    => @script,
      'dotdir'    => @dotdir
    }

  %w[
    daemon_dir

    lock_file
    log_file
    pid_file
    stdin_file
    stdout_file
    stderr_file
  ].each do |key|
    value = instance_variable_get("@#{ key }")
    info[key] = value
  end

  STDERR.puts(info.to_yaml)

  exit(42)
end

#cmd_logObject



303
304
305
306
# File 'lib/main/daemon.rb', line 303

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

#cmd_pidObject



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

def cmd_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

#cmd_pingObject



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/main/daemon.rb', line 262

def cmd_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
end

#cmd_restartObject



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/main/daemon.rb', line 223

def cmd_restart
  42.times do
    begin
      cmd_stop
      break
    rescue Object
      if alive?
        sleep(rand)
      else
        break
      end
    end
  end

  abort("could not stop #{ @script }!") if alive?

  sleep(rand)

  cmd_start
end

#cmd_runObject



282
283
284
285
286
287
288
289
290
# File 'lib/main/daemon.rb', line 282

def cmd_run
  lock!(:complain => true)

  pid!

  log!

  exec!
end

#cmd_signalObject



292
293
294
295
296
297
298
299
300
301
# File 'lib/main/daemon.rb', line 292

def cmd_signal
  pid = Integer(IO.read(@pid_file)) rescue nil
  if pid
    signal = ARGV.shift || 'SIGALRM'
    Process.kill(signal, pid)
    puts(pid)
    exit(0)
  end
  exit(42)
end

#cmd_startObject



157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/main/daemon.rb', line 157

def cmd_start
  lock!(:complain => true)

  daemonize!{|pid| puts(pid)}

  redirect_io!

  pid!

  log!

  exec!
end

#cmd_stopObject



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/main/daemon.rb', line 171

def cmd_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)
ensure
  unless alive?
    begin
      FileUtils.rm_f(@pid_file) rescue nil
    rescue Object
    end
  end
end

#cmd_tailObject



313
314
315
316
# File 'lib/main/daemon.rb', line 313

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

#cmd_usageObject



125
126
127
128
# File 'lib/main/daemon.rb', line 125

def cmd_usage
  STDERR.puts usage
  exit(42)
end

#commandsObject



117
118
119
# File 'lib/main/daemon.rb', line 117

def commands
  Daemon.commands
end

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

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



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/main/daemon.rb', line 410

def daemonize!(options = {}, &block)
# optional directory and umask
#
  chdir = options[:chdir] || options['chdir'] || @daemon_dir || '.'
  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



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# File 'lib/main/daemon.rb', line 434

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

#exec!Object



345
346
347
# File 'lib/main/daemon.rb', line 345

def exec!
  ::Kernel.exec(script_start_command)
end

#keep_ios(*ios) ⇒ Object



502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
# File 'lib/main/daemon.rb', line 502

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



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/main/daemon.rb', line 318

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



355
356
357
358
359
360
361
# File 'lib/main/daemon.rb', line 355

def log!
  logger.info("DAEMON START - #{ Process.pid }")

  at_exit do
    logger.info("DAEMON STOP - #{ Process.pid }") rescue nil
  end
end

#loggerObject



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/main/daemon.rb', line 387

def logger
  @logger ||= (
    require 'logger' unless defined?(Logger)

    if @log_file
      number_rolled = 7
      megabytes     = 2 ** 20
      max_size      = 42 * megabytes

      ::Logger.new(@log_file, number_rolled, max_size)
    else
      ::Logger.new(STDERR)
    end
  )
end

#logger=(logger) ⇒ Object



403
404
405
# File 'lib/main/daemon.rb', line 403

def logger=(logger)
  @logger = logger
end

#pid!Object



338
339
340
341
342
343
# File 'lib/main/daemon.rb', line 338

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

#process_cmd!(cmd) ⇒ Object



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
# File 'lib/main/daemon.rb', line 70

def process_cmd!(cmd)
  case cmd.to_s
    when /USAGE/i
      cmd_usage

    when /INFO/i
      cmd_info

    when /RESTART/i
      cmd_restart

    when /START/i
      cmd_start

    when /STOP/i
      cmd_stop

    when /PING/i
      cmd_ping

    when /RUN/i
      cmd_run

    when /PID/i
      cmd_pid

    when /SIGNAL/i
      cmd_signal

    when /LOG/i
      cmd_log

    when /DIR/i
      cmd_dir

    when /TAIL/i
      cmd_tail

    else
      cmd_usage
  end
end

#redirect_io!(options = {}) ⇒ Object



470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
# File 'lib/main/daemon.rb', line 470

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

#script_start_commandObject



349
350
351
352
353
# File 'lib/main/daemon.rb', line 349

def script_start_command
  argv = @main.argv.dup
  argv.shift if argv.first == "--"
  "#{ which_ruby } #{ @script.inspect } #{ argv.map{|arg| arg.inspect}.join(' ') }"
end

#setup!Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/main/daemon.rb', line 31

def setup!
  @dotdir = @main.dotdir

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

  @daemon_dir = File.join(@dotdir, 'daemon')

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

  FileUtils.mkdir_p(@daemon_dir) rescue nil

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

  @started_at = Time.now

  @ppid = Process.pid

  STDOUT.sync = true
  STDERR.sync = true

  self
end

#unlock!Object



334
335
336
# File 'lib/main/daemon.rb', line 334

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

#usageObject



121
122
123
# File 'lib/main/daemon.rb', line 121

def usage
  "#{ main.program } daemon #{ commands.join('|') }"
end

#which_rubyObject



380
381
382
383
384
385
# File 'lib/main/daemon.rb', line 380

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