-
-
Save ahoward/1274473 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
# 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 |
Author
ahoward
commented
Apr 21, 2012
- this works on any machine, heroku, aws, home-spun, localhost....
- upstart is stupid with restarts, this hooks into the 'normal' rails' restart mechanism that passenger, et al, use - ./tmp/restart.txt
- this requires zero config: deploy, forget about it.
- i like having all background logs/pids, etc centralized in my app dir, not scatter around on some unix fs layout
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment