############################################### # mongrel_rails_svc # # This is where Win32::Daemon resides. ############################################### require 'rubygems' require 'mongrel' require 'mongrel/rails' require 'optparse' require 'win32/service' # We need to use OpenProcess and SetProcessAffinityMask on WinNT/2K/XP for # binding the process to each cpu. # Kernel32 Module Just for Win32 :D require 'dl/win32' module Kernel32 [ %w/OpenProcess LLL L/, %w/SetProcessAffinityMask LL L/, ].each do |fn| const_set fn[0].intern, Win32API.new('kernel32.dll', *fn) end PROCESS_ALL_ACCESS = 0x1f0fff module_function def set_affinity(pid, cpu) handle = OpenProcess.call(PROCESS_ALL_ACCESS, 0, pid) # CPU mask is a bit weird, hehehe :) # default mask for CPU 1 mask = 1 mask = %w{1 2 4 8 16 32 64 128}[cpu.to_i - 1] if cpu.to_i.between?(1, 8) SetProcessAffinityMask.call(handle, mask.to_i) end end # End Kernel32 Module DEBUG_LOG_FILE = File.expand_path(File.dirname(__FILE__) + '/debug.log') DEBUG_THREAD_LOG_FILE = File.expand_path(File.dirname(__FILE__) + '/debug_thread.log') def dbg(msg) File.open(DEBUG_LOG_FILE,"a+") { |f| f.puts("#{Time.now} - #{msg}") } end def dbg_th(msg) File.open(DEBUG_THREAD_LOG_FILE,"a+") { |f| f.puts("#{Time.now} - #{msg}") } end # This class encapsulate the handler registering, http_server and working thread # Is standalone, so using MongrelRails in your app get everything runnig # (in case you don't want use mongrel_rails script) class MongrelRails def initialize(ip, port, rails_root, docroot, environment, mime_map, num_procs, timeout) dbg "mongrelrails_initialize entered" @ip = ip @port = port @rails_root = rails_root @docroot = docroot @environment = environment @mime_map = mime_map @num_procs = num_procs @timeout = timeout dbg "mongrelrails_initialize left" end def delayed_initialize dbg "delayed_initialize entered" @rails = configure_rails # start up mongrel with the right configurations @server = Mongrel::HttpServer.new(@ip, @port, @num_procs.to_i, @timeout.to_i) @server.register("/", @rails) dbg "delayed_initialize left" end def load_mime_map dbg "load_mime_map entered" mime = {} # configure any requested mime map if @mime_map puts "Loading additional MIME types from #@mime_map" mime.merge!(YAML.load_file(@mime_map)) # check all the mime types to make sure they are the right format mime.each {|k,v| puts "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 } end dbg "load_mime_map left" return mime end def configure_rails dbg "configure_rails entered" Dir.chdir(@rails_root) ENV['RAILS_ENV'] = @environment require 'config/environment' # configure the rails handler rails = Mongrel::Rails::RailsHandler.new(@docroot, load_mime_map) dbg "configure_rails left" return rails end def start_serve begin dbg "start_serve entered" @runner = Thread.new do dbg_th "runner_thread suspended" Thread.stop dbg_th "runner_thread resumed" dbg_th "runner_thread acceptor.join" @server.acceptor.join end dbg "server.run" @server.run dbg "runner.run" @runner.run dbg "start_serve left" rescue dbg "ERROR: #$!\r\n" dbg $!.backtrace.join("\r\n") end end def stop_serve dbg "stop_serve entered" if @runner.alive? dbg "killing thread" @runner.kill end @server.stop dbg "stop_serve left" end end class RailsDaemon < Win32::Daemon def initialize(rails) dbg "daemon_initialize entered" @rails = rails dbg "daemon_initialize left" end def service_init dbg "service_init entered" @rails.delayed_initialize dbg "service_init left" end def service_main dbg "service_main entered" dbg "rails.start_serve" @rails.start_serve dbg "while RUNNING" while state == RUNNING sleep 1 end dbg "state !RUNNING" dbg "rails.stop_serve" @rails.stop_serve dbg "service_main left" end def service_stop dbg "service_stop entered" dbg "service_stop left" end end begin if ARGV[0] == 'service' ARGV.shift # default options OPTIONS = { :rails_root => Dir.pwd, :environment => 'production', :ip => '0.0.0.0', :port => 3000, :mime_map => nil, :num_procs => 1024, :timeout => 0, :cpu => nil } ARGV.options do |opts| opts.on('-r', '--root PATH', "Set the root path where your rails app resides.") { |OPTIONS[:rails_root]| } opts.on('-e', '--environment ENV', "Rails environment to run as. (default: production)") { |OPTIONS[:environment]| } opts.on('-b', '--binding ADDR', "Address to bind to") { |OPTIONS[:ip]| } opts.on('-p', '--port PORT', "Which port to bind to") { |OPTIONS[:port]| } opts.on('-m', '--mime PATH', "A YAML file that lists additional MIME types") { |OPTIONS[:mime_map]| } opts.on('-P', '--num-procs INT', "Number of processor threads to use") { |OPTIONS[:num_procs]| } opts.on('-t', '--timeout SECONDS', "Timeout all requests after SECONDS time") { |OPTIONS[:timeout]| } opts.on('-c', '--cpu CPU', "Bind the process to specific cpu") { |OPTIONS[:cpu]| } opts.parse! end #expand RAILS_ROOT OPTIONS[:rails_root] = File.expand_path(OPTIONS[:rails_root]) OPTIONS[:docroot] = File.expand_path(OPTIONS[:rails_root] + '/public') # We must bind to a specific cpu? if OPTIONS[:cpu] Kernel32.set_affinity(Process.pid, OPTIONS[:cpu]) end rails = MongrelRails.new(OPTIONS[:ip], OPTIONS[:port], OPTIONS[:rails_root], OPTIONS[:docroot], OPTIONS[:environment], OPTIONS[:mime_map], OPTIONS[:num_procs].to_i, OPTIONS[:timeout].to_i) rails_svc = RailsDaemon.new(rails) rails_svc.mainloop elsif ARGV[0] == 'debug' ARGV.shift # default options OPTIONS = { :rails_root => Dir.pwd, :environment => 'production', :ip => '0.0.0.0', :port => 3000, :mime_map => nil, :num_procs => 20, :timeout => 120, :cpu => nil } ARGV.options do |opts| opts.on('-r', '--root PATH', "Set the root path where your rails app resides.") { |OPTIONS[:rails_root]| } opts.on('-e', '--environment ENV', "Rails environment to run as.") { |OPTIONS[:environment]| } opts.on('-b', '--binding ADDR', "Address to bind to") { |OPTIONS[:ip]| } opts.on('-p', '--port PORT', "Which port to bind to") { |OPTIONS[:port]| } opts.on('-m', '--mime PATH', "A YAML file that lists additional MIME types") { |OPTIONS[:mime_map]| } opts.on('-P', '--num-procs INT', "Number of processor threads to use") { |OPTIONS[:num_procs]| } opts.on('-t', '--timeout SECONDS', "Timeout all requests after SECONDS time") { |OPTIONS[:timeout]| } opts.on('-c', '--cpu CPU', "Bind the process to specific cpu") { |OPTIONS[:cpu]| } opts.parse! end #expand RAILS_ROOT OPTIONS[:rails_root] = File.expand_path(OPTIONS[:rails_root]) OPTIONS[:docroot] = File.expand_path(OPTIONS[:rails_root] + '/public') # We must bind to a specific cpu? if OPTIONS[:cpu] Kernel32.set_affinity(Process.pid, OPTIONS[:cpu]) end rails = MongrelRails.new(OPTIONS[:ip], OPTIONS[:port], OPTIONS[:rails_root], OPTIONS[:docroot], OPTIONS[:environment], OPTIONS[:mime_map], OPTIONS[:num_procs].to_i, OPTIONS[:timeout].to_i) rails.delayed_initialize rails.start_serve begin sleep rescue Interrupt dbg "ERROR: #$!\r\n" dbg $!.backtrace.join("\r\n") puts "graceful shutdown?" end begin rails.stop_serve rescue dbg "ERROR: #$!\r\n" dbg $!.backtrace.join("\r\n") end end rescue dbg "ERROR: #$!\r\n" dbg $!.backtrace.join("\r\n") end