module Demon; end # intelligent fork based demonizer class Demon::Base def self.start(count=1) @demons ||= {} count.times do |i| (@demons["#{prefix}_#{i}"] ||= new(i)).start end end def self.stop return unless @demons @demons.values.each do |demon| demon.stop end end def self.ensure_running @demons.values.each do |demon| demon.ensure_running end end def initialize(index) @index = index @pid = nil @parent_pid = Process.pid @started = false end def pid_file "#{Rails.root}/tmp/pids/#{self.class.prefix}_#{@index}.pid" end def stop @started = false if @pid Process.kill("HUP",@pid) @pid = nil end end def ensure_running return unless @started if !@pid @started = false start return end dead = Process.waitpid(@pid, Process::WNOHANG) rescue -1 if dead STDERR.puts "Detected dead worker #{@pid}, restarting..." @pid = nil @started = false start end end def start return if @pid || @started if existing = already_running? # should not happen ... so kill violently STDERR.puts "Attempting to kill pid #{existing}" Process.kill("TERM",existing) end @started = true run end def run if @pid = fork write_pid_file return end monitor_parent establish_app after_fork end def already_running? if File.exists? pid_file pid = File.read(pid_file).to_i if alive?(pid) return pid end end nil end private def write_pid_file FileUtils.mkdir_p(Rails.root + "tmp/pids") File.open(pid_file,'w') do |f| f.write(@pid) end end def delete_pid_file File.delete(pid_file) end def monitor_parent Thread.new do while true unless alive?(@parent_pid) Process.kill "QUIT", Process.pid end sleep 1 end end end def alive?(pid) begin Process.kill(0, pid) true rescue false end end def suppress_stdout true end def suppress_stderr true end def establish_app Discourse.after_fork Signal.trap("HUP") do begin delete_pid_file ensure exit end end # keep stuff simple for now $stdout.reopen("/dev/null", "w") if suppress_stdout $stderr.reopen("/dev/null", "w") if suppress_stderr end def after_fork end end