puma如何加载rack应用并将请求传递给它
TL;DR
- 默认使用
Rack::Builder
解析config.ru - 多进程模式(
workers N
)下可选择preload_app!
,使其在创建worker进程前先加载rackup文件
过程
搭建环境,参考 [[puma环境搭建]],在config.ru加入
pp caller
启动,得调用栈
单进程模式
单进程模式的调用栈如下
[".../3.1.1/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/builder.rb:116:in `eval'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/builder.rb:116:in `new_from_string'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/builder.rb:105:in `load_file'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/builder.rb:66:in `parse_file'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/configuration.rb:348:in `load_rackup'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/configuration.rb:270:in `app'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/runner.rb:150:in `load_and_bind'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/single.rb:44:in `run'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/launcher.rb:193:in `run'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cli.rb:81:in `run'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/bin/puma:10:in `'",
".../3.1.1/bin/puma:25:in `load'",
".../3.1.1/bin/puma:25:in `'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'",
".../3.1.1/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'",
".../3.1.1/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'",
".../3.1.1/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'",
".../3.1.1/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in '",
".../3.1.1/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `'",
".../3.1.1/bin/bundle:25:in `load'",
".../3.1.1/bin/bundle:25:in `'"]
它会使用
::Rack::Builder
解析config.ru文件(可通过rackup DSL命令改成别的路径),然后包装成ConfigMiddleware
# lib/puma/configuration.rb
module Puma
class Configuration
class ConfigMiddleware
def initialize(config, app)
@config = config
@app = app
end
def call(env)
env[Const::PUMA_CONFIG] = @config
@app.call(env)
end
end
def app
found = options[:app] || load_rackup
if @options[:log_requests]
require 'puma/commonlogger'
logger = @options[:logger]
found = CommonLogger.new(found, logger)
end
ConfigMiddleware.new(self, found)
end
end
end
返回给
Runner
(Single
和Cluster::Worker
的基类),再包装在Puma::Server
里# lib/puma/runner.rb
def start_server
server = Puma::Server.new app, @launcher.events, @options
server.inherit_binder @launcher.binder
server
end
Server
接收请求以后,就会丢进线程池。而线程池的处理方法,就是交给这个@app
处理# puma-5.6.5/lib/puma/server.rb
module Puma
class Server
include Request
def run(background=true, thread_name: 'srv')
# ...
@thread_pool = ThreadPool.new(
thread_name,
@min_threads,
@max_threads,
::Puma::IOBuffer,
&method(:process_client)
)
# ...
handle_servers
end
def handle_servers
# ...
while @status == :run || (drain && shutting_down?)
# ...
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
break unless ios
ios.first.each do |sock|
# ...
pool << Client.new(io, @binder.env(sock)).tap { |c|
c.listener = sock
c.send(addr_send_name, addr_value) if addr_value
}
end
end
end
end
def process_client(client, buffer)
# ...
while true
@requests_count += 1
handle_request(client, buffer, requests + 1)
end
# ...
end
end
end
(
handle_request
方法来自Puma::Request
)# puma-5.6.5/lib/puma/request.rb
module Puma
module Request
def handle_request(client, lines, requests)
env = client.env
# ...
status, headers, res_body = @thread_pool.with_force_shutdown do
@app.call(env)
end
end
end
end
多进程模式
非
preload_app!
的调用栈如下[".../3.1.1/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/builder.rb:116:in `eval'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/builder.rb:116:in `new_from_string'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/builder.rb:105:in `load_file'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/builder.rb:66:in `parse_file'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/configuration.rb:348:in `load_rackup'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/configuration.rb:270:in `app'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/runner.rb:161:in `app'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/runner.rb:165:in `start_server'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cluster/worker.rb:58:in `run'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cluster.rb:204:in `worker'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cluster.rb:97:in `block in spawn_worker'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cluster.rb:97:in `fork'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cluster.rb:97:in `spawn_worker'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cluster.rb:79:in `block in spawn_workers'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cluster.rb:72:in `times'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cluster.rb:72:in `spawn_workers'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cluster.rb:415:in `run'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/launcher.rb:193:in `run'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cli.rb:81:in `run'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/bin/puma:10:in `'",
".../3.1.1/bin/puma:25:in `load'",
".../3.1.1/bin/puma:25:in `'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'",
".../3.1.1/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'",
".../3.1.1/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'",
".../3.1.1/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'",
".../3.1.1/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in '",
".../3.1.1/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `'",
".../3.1.1/bin/bundle:25:in `load'",
".../3.1.1/bin/bundle:25:in `'"]
preload_app!
的调用栈如下[".../3.1.1/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/builder.rb:116:in `eval'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/builder.rb:116:in `new_from_string'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/builder.rb:105:in `load_file'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/builder.rb:66:in `parse_file'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/configuration.rb:348:in `load_rackup'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/configuration.rb:270:in `app'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/runner.rb:150:in `load_and_bind'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cluster.rb:357:in `run'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/launcher.rb:193:in `run'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/lib/puma/cli.rb:81:in `run'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.5/bin/puma:10:in `'",
".../3.1.1/bin/puma:25:in `load'",
".../3.1.1/bin/puma:25:in `'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'",
".../3.1.1/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'",
".../3.1.1/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'",
".../3.1.1/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'",
".../3.1.1/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'",
".../3.1.1/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in '",
".../3.1.1/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'",
".../3.1.1/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `'",
".../3.1.1/bin/bundle:25:in `load'",
".../3.1.1/bin/bundle:25:in `'"]
区别在于,设置了
preload_app!
,就会在spawn_workers
前加载rack应用,否则是在新进程里加载rack应用# puma-5.6.5/lib/puma/cluster.rb
module PumaG
class Cluster < Runner
def run
# ...
if preload?
# ...
load_and_bind
# ...
else
# ...
end
# ...
spawn_workers
# ...
end
def spawn_workers
diff = @options[:workers] - @workers.size
# ...
diff.times do
# ...
spawn_worker(idx, master)
# ...
end
# ...
end
def spawn_worker(idx, master)
# ...
fork { worker(idx, master) }
# ...
end
def worker(index, master)
# ...
server = start_server if preload?
new_worker = Worker.new index: index,
master: master,
launcher: @launcher,
pipes: pipes,
server: server
new_worker.run
end
end
end
Cluster::Worker#run
时,发现未有@server
,于是再调用一次Runner#start_server
产生rack应用# puma-5.6.5/lib/puma/cluster/worker.rb
module Puma
class Cluster < Puma::Runner
class Worker < Puma::Runner
def run
# ...
server = @server ||= start_server
# ...
end
end
end
end