我们从加载spring的入口开始看代码:
# bin/rails
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
# bin/spring
unless defined?(Spring)
require 'rubygems'
require 'bundler'
lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
spring = lockfile.specs.detect { |spec| spec.name == "spring" }
if spring
Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
gem 'spring', spring.version
require 'spring/binstub'
end
end
binstub的代码主要是为了根据命令类型处理ARGV,然后加载bin/spring:
# binstub
load bin_path
sprin里主要是调用了Client.run
lib = File.expand_path("../../lib", __FILE__)
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib) # enable local development
require 'spring/client'
Spring::Client.run(ARGV)
然后根据给予的ARGV运行相应的call,接下在我们主要使用console作为代码学习的路线.
# spring/client
require "spring/errors"
require "spring/json"
require "spring/client/command"
require "spring/client/run"
require "spring/client/help"
require "spring/client/binstub"
require "spring/client/stop"
require "spring/client/status"
require "spring/client/rails"
require "spring/client/version"
require "spring/client/server"
module Spring
module Client
COMMANDS = {
"help" => Client::Help,
"-h" => Client::Help,
"--help" => Client::Help,
"binstub" => Client::Binstub,
"stop" => Client::Stop,
"status" => Client::Status,
"rails" => Client::Rails,
"-v" => Client::Version,
"--version" => Client::Version,
"server" => Client::Server,
}
def self.run(args)
command_for(args.first).call(args)
rescue CommandNotFound
Client::Help.call(args)
rescue ClientError => e
$stderr.puts e.message
exit 1
end
def self.command_for(name)
COMMANDS[name] || Client::Run
end
end
end
# allow users to add hooks that do not run in the server
# or modify start/stop
if File.exist?("config/spring_client.rb")
require "./config/spring_client.rb"
end
这里找到Client::Rails文件,然后调用他的call方法:
require "set"
module Spring
module Client
class Rails < Command
COMMANDS = Set.new %w(console runner generate destroy test)
ALIASES = {
"c" => "console",
"r" => "runner",
"g" => "generate",
"d" => "destroy",
"t" => "test"
}
def self.description
"Run a rails command. The following sub commands will use spring: #{COMMANDS.to_a.join ', '}."
end
def call
command_name = ALIASES[args[1]] || args[1]
if COMMANDS.include?(command_name)
Run.call(["rails_#{command_name}", *args.drop(2)])
else
require "spring/configuration"
ARGV.shift
load Dir.glob(Spring.application_root_path.join("{bin,script}/rails")).first
exit
end
end
end
end
end
注意,以上这个文件里的call实际上调用的是Comand的self.call方法, 而主要作用就是实例化Client::Rails然后调用他的call:
require "spring/env"
module Spring
module Client
class Command
def self.call(args)
new(args).call
end
attr_reader :args, :env
def initialize(args)
@args = args
@env = Env.new
end
end
end
end
Client::Rails的call调用了Run的call方法:
def call
begin
connect
rescue Errno::ENOENT, Errno::ECONNRESET, Errno::ECONNREFUSED
cold_run
else
warm_run
end
ensure
server.close if server
end
这个方法存在两种情况当spring服务没有打开的时候会跑cold_run:
def cold_run
boot_server
connect
run
end
开启spring的服务
def boot_server
env.socket_path.unlink if env.socket_path.exist?
pid = Process.spawn(gem_env, env.server_command, out: File::NULL)
timeout = Time.now + BOOT_TIMEOUT
@server_booted = true
until env.socket_path.exist?
_, status = Process.waitpid2(pid, Process::WNOHANG)
if status
exit status.exitstatus
elsif Time.now > timeout
$stderr.puts "Starting Spring server with `#{env.server_command}` " \
"timed out after #{BOOT_TIMEOUT} seconds"
exit 1
end
sleep 0.1
end
end
连上spring服务的connect
def connect
@server = UNIXSocket.open(env.socket_name)
end
def run
verify_server_version
application, client = UNIXSocket.pair
queue_signals
connect_to_application(client)
run_command(client, application)
rescue Errno::ECONNRESET
exit 1
end
def connect_to_application(client)
server.send_io client
send_json server, "args" => args, "default_rails_env" => default_rails_env
if IO.select([server], [], [], CONNECT_TIMEOUT)
server.gets or raise CommandNotFound
else
raise "Error connecting to Spring server"
end
end
def run_command(client, application)
log "sending command"
application.send_io STDOUT
application.send_io STDERR
application.send_io STDIN
send_json application, "args" => args, "env" => ENV.to_hash
pid = server.gets
pid = pid.chomp if pid
# We must not close the client socket until we are sure that the application has
# received the FD. Otherwise the FD can end up getting closed while it's in the server
# socket buffer on OS X. This doesn't happen on Linux.
client.close
if pid && !pid.empty?
log "got pid: #{pid}"
forward_signals(application)
status = application.read.to_i
log "got exit status #{status}"
exit status
else
log "got no pid"
exit 1
end
ensure
application.close
end
UNIXSocket.pair主要是打开了一对相互连接的socket.然后在connect_to_application里我们看到了将我们的client对象发送给spring的服务,以及在run_command里的send_io将标准输出流、标准输入流和标准错流发送给client,这样我们的客户端的输出输入就完全被spring服务的预加载好的进程代理了。
在server打开的情况下就跑warn_run,主要就是spring服务开好了,之后的代码执行流程和cold_run的后半部分相同:
def warm_run
run
rescue CommandNotFound
require "spring/commands"
if Spring.command?(args.first)
# Command installed since spring started
stop_server
cold_run
else
raise
end
end
网友评论