流程解讀
Puma 進程(可以有一個或多個)通過 Reactor類中的線程來接受來自套接字的連接。連接一旦完全緩沖并讀取,就會移到 todo 列表中,可用的線程會從那里拾取它
在集群模式下,首先啟動一個 master 進程,它會準備應用程序,然后使用 fork() 系統調用創建一個或多個 child 進程。這些 child 進程都監聽同一個套接字。master 進程不監聽套接字或處理請求,它的主要目的是管理和監聽 UNIX 信號,并可能kill或啟動 child 進程。
我們有時將 child 進程(或單進程模式下的 Puma 進程)稱為 worker,有時將 Puma 的 ThreadPool:創建的線程稱為 worker 線程。
啟動時,Puma 會監聽一個 TCP 或 UNIX 套接字。
該套接字的 backlog 默認配置為 1024,但實際的 backlog 值會受到 net.core.somaxconn sysctl 值的限制。
backlog 決定了未接受連接的隊列大小。如果 backlog 已滿,操作系統將不會接受新的連接。
當至少有一個 worker 線程可用于工作時,reactor 線程會監聽套接字并接受請求(如果有等待的請求)。
- reactor 線程會等待整個 HTTP 請求接收完成。
- Puma 會將等待接收 HTTP 請求體所花費的時間以毫秒為單位暴露給 Rack 應用,作為 env['puma.request_body_wait']。
- 一旦完全緩沖并接收,連接就會被推送到 "todo" 集合中。
- worker 線程從 "todo" 集合中彈出工作進行處理。
- worker 線程通過調用配置的 Rack 應用來處理請求。Rack 應用生成 HTTP 響應。
- worker 線程將響應寫入到連接。雖然 Puma 通過單獨的線程緩沖請求,但它不會為響應使用單獨的線程。
- 完成之后,線程變為可用,可以處理 "todo" 集合中的另一個連接。
源碼解讀
cli里初始化Launcher并調用#run
方法
Launcher#run里調用runner(cluster)的run
Cluster#run
調用 #spawn_workers
啟動進程
啟動配置workers數量 - 已啟動workers數量
(工作進程)
spawn_workers方法在cluster
的run
方法內循環調用
cluster
的spawn_worker
繼續調用worker方法,初始化worker(這里fork進程執行)并執行run
worker方法里start_server
啟動server,并給到worker
worker run方法里處理一些信號量;主要是啟動了server.run
start_server獲取到binder(io,如socket和tcp_listener等)
server.run 方法,啟動線程池;
調用server.handle_servers,開始io多路復用(select)
這里我們先記住,同時啟動了 清理空閑線程
使用client 并放入線程池
thread_pool里 <<
把任務放入@todo
里(使用mutex等保證線程安全),且 線程未創建滿時(小于最大數量),再起新線程(span_thread)
spawn_thread后線程內
while true
無限循環,從todo里取出work,并執行block.call(work)
block是創建時傳入的process_client
process_client又調用
handle_request
,handle_request里最終出現了我們熟悉的@app.call
process_client會接著調用prepare_response -> fast_write_response 最終往io里寫入了http response,整個流程完成
自動清理空閑線程
循環中遇到 todo為空,且有空閑線程清理標識,結束當前線程