Skip to content

Instantly share code, notes, and snippets.

@ahoward
Created October 10, 2011 01:55
Show Gist options
  • Select an option

  • Save ahoward/1274473 to your computer and use it in GitHub Desktop.

Select an option

Save ahoward/1274473 to your computer and use it in GitHub Desktop.

Revisions

  1. ahoward revised this gist Oct 10, 2011. 2 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
  2. ahoward revised this gist Oct 10, 2011. 1 changed file with 30 additions and 0 deletions.
    30 changes: 30 additions & 0 deletions passenger.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,30 @@
    #! /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)
    }
  3. ahoward revised this gist Oct 10, 2011. 1 changed file with 0 additions and 30 deletions.
    30 changes: 0 additions & 30 deletions passenger.rb
    Original file line number Diff line number Diff line change
    @@ -1,30 +0,0 @@
    #! /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)
    }
  4. ahoward revised this gist Oct 10, 2011. 1 changed file with 433 additions and 0 deletions.
    433 changes: 433 additions & 0 deletions background.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,433 @@
    ##
    # 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
  5. ahoward created this gist Oct 10, 2011.
    30 changes: 30 additions & 0 deletions passenger.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,30 @@
    #! /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)
    }