/vendor/cache/ruby/2.7.0/gems/spring-2.1.0/lib/spring/application_manager.rb
Ruby | 140 lines | 112 code | 20 blank | 8 comment | 8 complexity | 4b52e34f2a7e5b96ac4f2f6871c3194d MD5 | raw file
- module Spring
- class ApplicationManager
- attr_reader :pid, :child, :app_env, :spring_env, :status
- def initialize(app_env, spring_env)
- @app_env = app_env
- @spring_env = spring_env
- @mutex = Mutex.new
- @state = :running
- @pid = nil
- end
- def log(message)
- spring_env.log "[application_manager:#{app_env}] #{message}"
- end
- # We're not using @mutex.synchronize to avoid the weird "<internal:prelude>:10"
- # line which messes with backtraces in e.g. rspec
- def synchronize
- @mutex.lock
- yield
- ensure
- @mutex.unlock
- end
- def start
- start_child
- end
- def restart
- return if @state == :stopping
- start_child(true)
- end
- def alive?
- @pid
- end
- def with_child
- synchronize do
- if alive?
- begin
- yield
- rescue Errno::ECONNRESET, Errno::EPIPE
- # The child has died but has not been collected by the wait thread yet,
- # so start a new child and try again.
- log "child dead; starting"
- start
- yield
- end
- else
- log "child not running; starting"
- start
- yield
- end
- end
- end
- # Returns the pid of the process running the command, or nil if the application process died.
- def run(client)
- with_child do
- child.send_io client
- child.gets or raise Errno::EPIPE
- end
- pid = child.gets.to_i
- unless pid.zero?
- log "got worker pid #{pid}"
- pid
- end
- rescue Errno::ECONNRESET, Errno::EPIPE => e
- log "#{e} while reading from child; returning no pid"
- nil
- ensure
- client.close
- end
- def stop
- log "stopping"
- @state = :stopping
- if pid
- Process.kill('TERM', pid)
- Process.wait(pid)
- end
- rescue Errno::ESRCH, Errno::ECHILD
- # Don't care
- end
- private
- def start_child(preload = false)
- @child, child_socket = UNIXSocket.pair
- Bundler.with_original_env do
- @pid = Process.spawn(
- {
- "RAILS_ENV" => app_env,
- "RACK_ENV" => app_env,
- "SPRING_ORIGINAL_ENV" => JSON.dump(Spring::ORIGINAL_ENV),
- "SPRING_PRELOAD" => preload ? "1" : "0"
- },
- "ruby",
- "-I", File.expand_path("../..", $LOADED_FEATURES.grep(/bundler\/setup\.rb$/).first),
- "-I", File.expand_path("../..", __FILE__),
- "-e", "require 'spring/application/boot'",
- 3 => child_socket,
- 4 => spring_env.log_file,
- )
- end
- start_wait_thread(pid, child) if child.gets
- child_socket.close
- end
- def start_wait_thread(pid, child)
- Process.detach(pid)
- Spring.failsafe_thread {
- # The recv can raise an ECONNRESET, killing the thread, but that's ok
- # as if it does we're no longer interested in the child
- loop do
- IO.select([child])
- break if child.recv(1, Socket::MSG_PEEK).empty?
- sleep 0.01
- end
- log "child #{pid} shutdown"
- synchronize {
- if @pid == pid
- @pid = nil
- restart
- end
- }
- }
- end
- end
- end