class Expedite::Application
Attributes
env[R]
manager[R]
variant[R]
Public Class Methods
new(variant:, manager:, env:)
click to toggle source
# File lib/expedite/application.rb, line 30 def initialize(variant:, manager:, env:) @variant = variant @manager = manager @env = env @mutex = Mutex.new @waiting = Set.new @preloaded = false @state = :initialized @interrupt = IO.pipe end
Public Instance Methods
app_name()
click to toggle source
# File lib/expedite/application.rb, line 65 def app_name env.app_name end
boot()
click to toggle source
# File lib/expedite/application.rb, line 41 def boot # This is necessary for the terminal to work correctly when we reopen stdin. Process.setsid rescue Errno::EPERM Expedite.app = self Signal.trap("TERM") { terminate } env.load_helper eager_preload if false #if ENV.delete("SPRING_PRELOAD") == "1" run end
connect_database()
click to toggle source
# File lib/expedite/application.rb, line 231 def connect_database ActiveRecord::Base.establish_connection if active_record_configured? end
disconnect_database()
click to toggle source
# File lib/expedite/application.rb, line 227 def disconnect_database ActiveRecord::Base.remove_connection if active_record_configured? end
eager_preload()
click to toggle source
# File lib/expedite/application.rb, line 102 def eager_preload with_pty { preload } end
exit()
click to toggle source
# File lib/expedite/application.rb, line 210 def exit state :exiting manager.shutdown(:RDWR) exit_if_finished sleep end
exit_if_finished()
click to toggle source
# File lib/expedite/application.rb, line 217 def exit_if_finished @mutex.synchronize { Kernel.exit if exiting? && @waiting.empty? } end
exiting?()
click to toggle source
# File lib/expedite/application.rb, line 81 def exiting? @state == :exiting end
initialized?()
click to toggle source
# File lib/expedite/application.rb, line 89 def initialized? @state == :initialized end
invoke_after_fork_callbacks()
click to toggle source
# File lib/expedite/application.rb, line 223 def invoke_after_fork_callbacks # TODO: end
log(message)
click to toggle source
# File lib/expedite/application.rb, line 69 def log(message) env.log "[application:#{variant}] #{message}" end
preload()
click to toggle source
# File lib/expedite/application.rb, line 93 def preload log "preloading app" @preloaded = :success rescue Exception => e @preloaded = :failure raise e unless initialized? end
preload_failed?()
click to toggle source
# File lib/expedite/application.rb, line 77 def preload_failed? @preloaded == :failure end
preloaded?()
click to toggle source
# File lib/expedite/application.rb, line 73 def preloaded? @preloaded end
print_exception(stream, error)
click to toggle source
# File lib/expedite/application.rb, line 254 def print_exception(stream, error) first, rest = error.backtrace.first, error.backtrace.drop(1) stream.puts("#{first}: #{error} (#{error.class})") rest.each { |line| stream.puts("\tfrom #{line}") } end
reset_streams()
click to toggle source
# File lib/expedite/application.rb, line 273 def reset_streams [STDOUT, STDERR].each do |stream| stream.reopen(env.log_file) end STDIN.reopen("/dev/null") end
run()
click to toggle source
# File lib/expedite/application.rb, line 106 def run $0 = "expedite variant | #{app_name} | #{variant}" Expedite::Variants.lookup(variant).after_fork(variant) state :running manager.puts loop do IO.select [manager, @interrupt.first] if terminating? || preload_failed? exit else serve manager.recv_io(UNIXSocket) end end end
serve(client)
click to toggle source
# File lib/expedite/application.rb, line 125 def serve(client) log "got client" manager.puts _stdout, stderr, _stdin = streams = 3.times.map { client.recv_io } [STDOUT, STDERR, STDIN].zip(streams).each { |a, b| a.reopen(b) } preload unless preloaded? args, env = JSON.load(client.read(client.gets.to_i)).values_at("args", "env") exec_name = args.shift command = Expedite::Commands.lookup(exec_name) command.setup(client) connect_database pid = fork { Process.setsid IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") } trap("TERM", "DEFAULT") ARGV.replace(args) $0 = exec_name # Load in the current env vars, except those which *were* changed when Spring started env.each { |k, v| ENV[k] = v } # requiring is faster, so if config.cache_classes was true in # the environment's config file, then we can respect that from # here on as we no longer need constant reloading. if @original_cache_classes ActiveSupport::Dependencies.mechanism = :require Rails.application.config.cache_classes = true end connect_database srand invoke_after_fork_callbacks shush_backtraces command.call } disconnect_database log "forked #{pid}" manager.puts pid # Boot makes a new application, so we don't wait for it if command.is_a?(Expedite::Command::Boot) Process.detach(pid) else wait pid, streams, client end rescue Exception => e log "exception: #{e} at #{e.backtrace.join("\n")}" manager.puts unless pid if streams && !e.is_a?(SystemExit) print_exception(stderr, e) streams.each(&:close) end client.puts(1) if pid client.close ensure # Redirect STDOUT and STDERR to prevent from keeping the original FDs # (i.e. to prevent `spring rake -T | grep db` from hanging forever), # even when exception is raised before forking (i.e. preloading). reset_streams end
shush_backtraces()
click to toggle source
This feels very naughty
# File lib/expedite/application.rb, line 236 def shush_backtraces Kernel.module_eval do old_raise = Kernel.method(:raise) remove_method :raise define_method :raise do |*args| begin old_raise.call(*args) ensure if $! lib = File.expand_path("..", __FILE__) $!.backtrace.reject! { |line| line.start_with?(lib) } end end end private :raise end end
state(val)
click to toggle source
# File lib/expedite/application.rb, line 54 def state(val) return if exiting? log "#{@state} -> #{val}" @state = val end
state!(val)
click to toggle source
# File lib/expedite/application.rb, line 60 def state!(val) state val @interrupt.last.write "." end
terminate()
click to toggle source
# File lib/expedite/application.rb, line 199 def terminate if exiting? # Ensure that we do not ignore subsequent termination attempts log "forced exit" @waiting.each { |pid| Process.kill("TERM", pid) } Kernel.exit else state! :terminating end end
terminating?()
click to toggle source
# File lib/expedite/application.rb, line 85 def terminating? @state == :terminating end
wait(pid, streams, client)
click to toggle source
# File lib/expedite/application.rb, line 280 def wait(pid, streams, client) @mutex.synchronize { @waiting << pid } # Wait in a separate thread so we can run multiple commands at once Expedite.failsafe_thread { begin _, status = Process.wait2 pid log "#{pid} exited with #{status.exitstatus}" streams.each(&:close) client.puts(status.exitstatus) client.close ensure @mutex.synchronize { @waiting.delete pid } exit_if_finished end } Expedite.failsafe_thread { while signal = client.gets.chomp begin Process.kill(signal, -Process.getpgid(pid)) client.puts(0) rescue Errno::ESRCH client.puts(1) end end } end
with_pty() { || ... }
click to toggle source
# File lib/expedite/application.rb, line 260 def with_pty PTY.open do |master, slave| [STDOUT, STDERR, STDIN].each { |s| s.reopen slave } reader_thread = Expedite.failsafe_thread { master.read } begin yield ensure reader_thread.kill reset_streams end end end
Private Instance Methods
active_record_configured?()
click to toggle source
# File lib/expedite/application.rb, line 312 def active_record_configured? defined?(ActiveRecord::Base) && ActiveRecord::Base.configurations.any? end