class RemoteRails::Server
server to run rails/rake command.
@example server = RemoteRails::Server.new(:rails_env => "development") server.start
Constants
- PAGE_SIZE
Public Class Methods
new(options={})
click to toggle source
# File lib/rrails/server.rb, line 24 def initialize(options={}) @rails_env = options[:rails_env] || ENV['RAILS_ENV'] || "development" @pidfile = "#{options[:pidfile] || './tmp/pids/rrails-'}#{@rails_env}.pid" @background = options[:background] || false if (options[:host] || options[:port]) && !options[:socket] @socket = nil @host = options[:host] || 'localhost' @port = options[:port] || DEFAULT_PORT[@rails_env] else @socket = "#{options[:socket] || './tmp/sockets/rrails-'}#{@rails_env}.socket" end @app_path = File.expand_path('./config/application') @logger = Logger.new(options[:logfile] ? options[:logfile] : (@background ? nil : STDERR)) @logger.level = options[:loglevel] || 0 end
Public Instance Methods
alive?()
click to toggle source
# File lib/rrails/server.rb, line 60 def alive? previous_instance ? true : false end
boot_rails()
click to toggle source
# File lib/rrails/server.rb, line 149 def boot_rails @logger.info("prepare rails environment (#{@rails_env})") ENV["RAILS_ENV"] = @rails_env # make IRB = Pry hacks (https://gist.github.com/941174) work: # pre-require all irb compoments needed in rails/commands # otherwise 'module IRB' will cause 'IRB is not a module' error. require 'irb' require 'irb/completion' require File.expand_path('./config/environment') unless Rails.application.config.cache_classes ActionDispatch::Reloader.cleanup! ActionDispatch::Reloader.prepare! end @logger.info("finished preparing rails environment") end
dispatch(sock, line, pty=false)
click to toggle source
# File lib/rrails/server.rb, line 168 def dispatch(sock, line, pty=false) if pty m_out, c_out = PTY.open c_in = c_err = c_out m_fds = [m_out, c_out] c_fds = [c_out] clisocks = {in: m_out, out: m_out} else c_in, m_in = IO.pipe m_out, c_out = IO.pipe m_err, c_err = IO.pipe m_fds = [m_in, m_out, m_err] c_fds = [c_in, c_out, c_err] clisocks = {in: m_in, out: m_out, err: m_err} end running = true heartbeat = 0 pid = fork do m_fds.map(&:close) if not pty STDIN.reopen(c_in) STDOUT.reopen(c_out) STDERR.reopen(c_err) ActiveRecord::Base.establish_connection if defined?(ActiveRecord::Base) execute *Shellwords.shellsplit(line) end c_fds.map(&:close) if not pty # pump input. since it will block, make it in another thread thread = Thread.start do while running do begin input = sock.__send__(pty ? :getc : :gets) rescue => ex @logger.debug "input thread got #{ex}" running = false end clisocks[:in].write(input) rescue nil end end loop do [:out, :err].each do |channel| next if not clisocks[channel] begin loop do response = clisocks[channel].read_nonblock(PAGE_SIZE) sock.puts("#{channel.upcase}\t#{response.bytes.to_a.join(',')}") sock.flush end rescue Errno::EAGAIN, EOFError => ex next end end if running _, stat = Process.waitpid2(pid, Process::WNOHANG) if stat @logger.debug "child exits. #{stat}" return stat end end # send heartbeat so that we got EPIPE immediately when client dies heartbeat += 1 if heartbeat > 20 sock.puts("PING") sock.flush heartbeat = 0 end # do not make CPU hot sleep 0.025 end ensure running = false [*c_fds, *m_fds].each {|io| io.close unless io.closed?} if pid begin Process.kill 0, pid @logger.debug "killing pid #{pid}" Process.kill 'TERM', pid rescue nil rescue Errno::ESRCH end end thread.kill if thread end
reload()
click to toggle source
# File lib/rrails/server.rb, line 55 def reload pid = previous_instance Process.kill :HUP, pid end
restart()
click to toggle source
# File lib/rrails/server.rb, line 50 def restart stop && sleep(1) start end
start()
click to toggle source
# File lib/rrails/server.rb, line 73 def start # check previous process raise RuntimeError.new('rrails is already running') if alive? if @background pid = Process.fork do @background = false start end Process.detach(pid) return end # make 'bundle exec' not necessary for most time. require 'bundler/setup' begin [@pidfile, @socket].compact.each do |path| FileUtils.rm_f path FileUtils.mkdir_p File.dirname(path) end File.write(@pidfile, $$) server = if @socket UNIXServer.open(@socket) else TCPServer.open(@host, @port) end server.close_on_exec = true @logger.info("starting rrails server: #{@socket || "#{@host}:#{@port}"}") [:INT, :TERM].each do |sig| trap(sig) do @logger.info("SIG#{sig} recieved. shutdown...") exit end end trap(:HUP) do @logger.info("SIGHUP recieved. reload...") ActionDispatch::Callbacks.new(Proc.new {}).call({}) self.boot_rails end self.boot_rails Thread.abort_on_exception = true loop do Thread.start(server.accept) do |s| @logger.debug("accepted") begin line = s.gets.chomp pty, line = (line[0] == 'P'), line[1..-1] @logger.info("invoke: #{line} (pty=#{pty})") status = nil time = Benchmark.realtime do status = dispatch(s, line, pty) end exitcode = status ? status.exitstatus || (status.termsig + 128) : 0 s.puts("EXIT\t#{exitcode}") s.flush @logger.info("finished: #{line} (#{time} seconds)") rescue Errno::EPIPE @logger.info("disconnected: #{line}") end end end ensure server.close unless server.closed? @logger.info("cleaning pid and socket files...") FileUtils.rm_f [@socket, @pidfile].compact end end
status()
click to toggle source
# File lib/rrails/server.rb, line 64 def status pid = previous_instance if pid puts "running \tpid = #{pid}" else puts 'stopped' end end
stop()
click to toggle source
# File lib/rrails/server.rb, line 40 def stop pid = previous_instance if pid @logger.info "stopping previous instance #{pid}" Process.kill :TERM, pid FileUtils.rm_f [@socket, @pidfile] return true end end
Private Instance Methods
execute(cmd, *args)
click to toggle source
# File lib/rrails/server.rb, line 260 def execute(cmd, *args) ARGV.clear ARGV.concat(args) $0 = cmd case cmd when 'rails' require 'rails/commands' when 'rake' # full path of rake $0 = Gem.bin_path('rake') ::Rake.application.run else # unknown binary, try to locate its location # try cmd and "#{cmd}-core" as gem names # rspec is in the gem named 'rspec-core' bin_path = nil [cmd, "#{cmd}-core"].each do |gem| begin bin_path = Gem.bin_path(gem, cmd) break rescue end end if not bin_path STDERR.puts "rrails: command not found: #{cmd}" STDERR.puts "Install missing gem executables with `bundle install`" exit(127) end # then load it load bin_path end end
previous_instance()
click to toggle source
# File lib/rrails/server.rb, line 295 def previous_instance begin previous_pid = File.read(@pidfile).to_i if previous_pid > 0 && Process.kill(0, previous_pid) return previous_pid end return false rescue Errno::ESRCH, Errno::ENOENT return false end end