Skip to content

Instantly share code, notes, and snippets.

@eduardo
Forked from ahoward/a.rb
Created May 10, 2012 03:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eduardo/2650770 to your computer and use it in GitHub Desktop.
Save eduardo/2650770 to your computer and use it in GitHub Desktop.
#! /usr/local/rbenv/versions/1.8.7-p352-redmine/bin/ruby
### file: ./script/passenger
##
# get busy!
#
Background.logger.info("process #{ Process.pid } starting...")
logger = Background.logger
loop do
cmd = "passenger start -p 4200"
Background.logger.info("cmd: #{ cmd }")
system(cmd)
seconds = Rails.env == 'production' ? 42 : 2
sleep(seconds) # NOTE - signals will wake this up!
end
BEGIN {
ENV['PATH'] = "/usr/local/rbenv/versions/1.8.7-p352-redmine/bin:#{ ENV['PATH'] }"
script = __FILE__
require(File.dirname(script) + '/../lib/background.rb')
Background.run!(script)
}
##
# background.rb is magic. it let's you have background rails code running
# with
#
# . your app's env loaded
# . that restarts like magic on a re-deploy
#
# usage in, say, ./script/run_jobs_in_the_background.rb, looks like:
#
# #! /usr/bin/env ruby
#
# Background.logger.info("process #{ Process.pid } starting...")
#
# logger = Background.logger
#
# loop do
# begin
# Job.each_that_needs_to_be_run do |job|
# job.run!
# end
# rescue => e
# Background.logger.error(e)
# seconds = Rails.env == 'production' ? 42 : 2
# sleep(seconds) # NOTE - signals will wake this up!
# end
# end
#
# BEGIN {
# script = __FILE__
# require(File.dirname(script) + '/../lib/background.rb')
# Background.run!(script)
# }
#
# it includes some basic daemon-y stuff including a lock that prevents the
# script from running twice so cron'ing it every one minute will NOT stack
# processes
#
module Background
## libs
#
require 'fileutils'
require 'ostruct'
require 'rbconfig'
SIGHUP = Signal.list['HUP']
%w(
script
mode
dirname
basename
script_dir
rails_root
public_system
private_system
basename_dir
lock_file
log_file
pid_file
restart_txt
started_at
signals
).each{|a| attr(a)}
def run!(*args)
setup(*args).run()
end
## setup
#
def setup(script)
@script = File.expand_path(script)
@exec = command_line
@dirname = File.expand_path(File.dirname(@script))
@basename = File.basename(@script)
@script_dir = File.expand_path(File.dirname(@script))
@rails_root = File.dirname(@script_dir)
@public_system = File.expand_path(File.join(@rails_root, 'public', 'system'))
@private_system = File.expand_path(File.join(@rails_root, 'private', 'system'))
if test(?d, @private_system)
@background_dir = File.join(@private_system, 'background')
else
@background_dir = File.join(@public_system, 'background')
end
@basename_dir = File.join(@background_dir, @basename)
@lock_file = File.join(@basename_dir, 'lock')
@log_file = File.join(@basename_dir, 'log')
@pid_file = File.join(@basename_dir, 'pid')
@mode = (ARGV.shift || 'RUN').upcase
FileUtils.mkdir_p(@basename_dir) rescue nil
FileUtils.touch(@lock_file) rescue nil
FileUtils.touch(@log_file) rescue nil
@signals = []
@restart_txt = File.join(@rails_root, 'tmp', 'restart.txt')
@started_at = Time.now
@monitor = new_monitor
self
end
def command_line
[which_ruby, @script, *ARGV.map{|arg| arg.dup}]
end
def new_monitor
Thread.new(self) do |bg|
Thread.current.abort_on_exception = true
loop do
#if bg.updated?
#bg.logger.info('RESTART - update') if bg.logger
#bg.restart!
#end
if bg.redeployed?
bg.logger.info('RESTART - deploy') if bg.logger
bg.restart!
end
if bg.signaled?
bg.logger.info('RESTART - signal') if bg.logger
bg.restart!
end
sleep(42)
end
end
end
def restart!
if fork
unlock!
Kernel.exec(*@exec)
else
exit(42)
end
end
def updated?
a = File.stat(__FILE__).mtime rescue @started_at
b = File.stat(@script).mtime rescue @started_at
a > @started_at or b > @started_at
end
def redeployed?
t = File.stat(@restart_txt).mtime rescue @started_at
t > @started_at
end
def signaled?
!signals.empty?
end
def which_ruby
c = ::Config::CONFIG
ruby = File::join(c['bindir'], c['ruby_install_name']) << c['EXEEXT']
raise "ruby @ #{ ruby } not executable!?" unless test(?e, ruby)
ruby
end
# run based on mode
#
def run
case @mode
when /RUN/i
# aquire the lock and run, otherwise exit with 42 (already running)
#
lock!(:complain => true)
# record the pid
#
pid!
# setup signal handling
#
trap!
# boot the rails app
#
boot!
# setup logging
#
$logger = Logging.logger(STDERR)
when /RESTART/i
# try hard to find the running process by pid and signal it to restart
#
pid = Integer(IO.read(@pid_file)) rescue nil
Process.kill('HUP', pid) rescue nil if pid
exit(0)
when /START/i
# aquire the lock and run, otherwise exit with 42 (already running)
#
lock!(:complain => true)
# boot the rails app
#
boot!
# daemonize
#
daemonize!
# dump pid to file
#
pid!
# setup signal handling
#
trap!
# setup logging
#
number_rolled = 7
megabytes = 2 ** 20
max_size = 42 * megabytes
options = { :safe => true } # for multi-process safe rolling, needs lockfile gem!
logger = ::Logging.logger(@log_file, number_rolled, max_size, options)
$logger = logger # HACK
# redirect io to log_file
#
open(@log_file, 'r+') do |fd|
fd.sync = true
STDOUT.reopen(fd)
STDERR.reopen(fd)
end
STDOUT.sync = true
STDERR.sync = true
when /PID/i
# report the pid of running instance iff possible
#
pid = Integer(IO.read(@pid_file)) rescue nil
if pid
puts(pid)
exit(0)
else
exit(1)
end
exit(1)
when /FUSER/i
# try to use fuser to display processes holding the lock
#
exec("fuser #{ @lock_file.inspect }")
when /STOP/i
# try hard to find the running process by pid and kill it
#
pid = Integer(IO.read(@pid_file)) rescue nil
if pid
10.times do
Process.kill('TERM', pid) rescue nil
begin
Process.kill(0, pid)
sleep(rand)
rescue Errno::ESRCH
puts(pid)
exit(0)
end
end
Process.kill(-9, pid) rescue nil
sleep(rand)
begin
Process.kill(0, pid)
rescue Errno::ESRCH
puts(pid)
exit(0)
end
end
exit(1)
when /SIGNAL/i
# signal a running process, if any
#
pid = Integer(IO.read(@pid_file)) rescue nil
if pid
signal = ARGV.shift || 'HUP'
Process.kill(signal, pid)
puts(pid)
exit(0)
end
exit(42)
when /LOG/i
# display the log file
#
puts(@log_file)
exit(42)
when /TAIL/i
# tail the log file
#
system("tail -F #{ @log_file.inspect }")
exit(42)
end
end
# we'll be needing these
#
def boot!
Dir.chdir(@rails_root)
require File.join(@rails_root, 'config', 'boot')
require File.join(@rails_root, 'config', 'environment')
end
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!
end
def unlock!
@lock.flock(File::LOCK_UN|File::LOCK_NB) if @lock
end
def pid!
open(@pid_file, 'w+') do |fd|
fd.puts(Process.pid)
end
at_exit{ FileUtils.rm_f(@pid_file) }
end
def trap!
%w( SIGHUP SIGALRM SIGUSR1 SIGUSR2 ).each do |signal|
trap(signal){|s| Background.signals.push(s) }
end
trap('SIGTERM'){ exit(0) }
trap('SIGINT'){ exit(0) }
end
def logger
$logger || (
require 'logger' unless defined?(Logger)
Logger.new(STDERR)
)
end
def logging_errors(&block)
begin
block.call()
rescue SignalException => e
logger.info(e)
exit(0)
rescue => e
logger.error(e)
end
end
def daemonize!(options = {})
# setup
#
chdir = options[:chdir] || options['chdir'] || '.'
umask = options[:umask] || options['umask'] || 0
# setup pipes we'll use for IPC
#
ra, wa = IO.pipe
rb, wb = IO.pipe
pid = fork
parent = !!pid
child = !parent
# in the parent we wait for the child to send back the grandchild's pid
#
if parent
at_exit{ exit! }
wa.close
r = ra
rb.close
w = wb
pid = r.gets
w.puts(pid)
exit(0)
end
# in the child start a grandchild. handshake the pid with the parent.
#
if child
ra.close
w = wa
wb.close
r = rb
open("/dev/null", "r+") do |fd|
#STDIN.reopen(fd)
#STDOUT.reopen(fd)
#STDERR.reopen(fd)
end
Process::setsid rescue nil
pid = fork
if pid
w.puts(pid)
r.gets
exit!
else
Dir::chdir(chdir)
File::umask(umask)
$DAEMON = true
at_exit{ exit! }
end
end
end
extend(self)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment