php.ini 和 php-fpm.conf 中有不少超時相關的配置,那麼這些配置到底有什麼做用呢?在源碼中又是怎麼實現的呢?這篇文章就來說講下面幾種超時配置:php
運行環境: Mac 10.14.2 + PHP 7.3.7react
php.ini的解析是在php_module_startup()
階段完成,ini_entry是在 main.c
中爲每一個php.ini配置定義的解析規則,格式以下:nginx
ZEND_INI_ENTRY3_EX(name, default_value, modifiable, on_modify, arg1, arg2, arg3, displayer)
複製代碼
PHP爲不一樣類型的配置定義了不少宏,ZEND_INI_ENTRY3_EX
是它們展開後的最終宏,好比PHP_INI_ENTRY
宏算法
PHP_INI_ENTRY(name, default_value, modifiable, on_modify)
複製代碼
name: 配置名稱windows
default_value: 配置默認值瀏覽器
modifiable: 配置的可被設定範圍服務器
這些模式決定着一個 PHP 的指令在什麼時候何地,是否可以被設定。手冊中的每一個指令都有其所屬的模式。例若有些指令能夠在 PHP 腳本中用 ini_set() 來設定,而有些則只能在 php.ini 或 httpd.conf 中。socket
例如 output_buffering 指令是屬於 PHP_INI_PERDIR,於是就不能用 ini_set() 來設定。可是 display_errors 指令是屬於 PHP_INI_ALL 於是就能夠在任何地方被設定,包括 ini_set()。ide
模式 含義 PHP_INI_USER 可在用戶腳本(例如 ini_set())或 Windows 註冊表(自 PHP 5.3 起)以及 .user.ini 中設定 PHP_INI_PERDIR 可在 php.ini,.htaccess 或 httpd.conf 中設定 PHP_INI_SYSTEM 可在 php.ini 或 httpd.conf 中設定 PHP_INI_ALL 可在任何地方設定
on_modify: 配置修改函數函數
由於max_input_time
和 max_execution_time
聯繫比較密切,因此放在一塊兒來說。
max_input_time
; Maximum amount of time each script may spend parsing request data. It's a good ; idea to limit this time on productions servers in order to eliminate unexpectedly ; long running scripts. ; Note: This directive is hardcoded to -1 for the CLI SAPI ; php.net/max-input-t…
翻譯過來就是:
max_input_time
是每一個腳本能夠花在解析請求數據上的最大時間。在生產服務器上經過限制max_input_time能夠清除掉長時間運行的腳本。在CLI模式下會硬編碼爲-1,即無限制。max_execution_time
; Maximum execution time of each script, in seconds ; php.net/max-executi… ; Note: This directive is hardcoded to 0 for the CLI SAPI
翻譯:
max_execution_time
是每一個腳本的最大可執行時間。在CLI模式下硬編碼爲0
// max_input_time,默認值爲無限制
STD_PHP_INI_ENTRY("max_input_time", "-1", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateLong, max_input_time, php_core_globals, core_globals)
// max_execution_time,默認值爲30s,修改函數爲OnUpdateTimeout
PHP_INI_ENTRY("max_execution_time", "30", PHP_INI_ALL, OnUpdateTimeout)
複製代碼
OnUpdateTimeout()
函數以下,由第二節可知配置解析發生在php_module_startup()
階段,此時EG(timeout_seconds)
被賦值爲了max_execution_time
,但尚未設置定時器。
// main.c
static PHP_INI_MH(OnUpdateTimeout) {
if (stage==PHP_INI_STAGE_STARTUP) {
/* Don't set a timeout on startup, only per-request */
/* EG(timeout_seconds) = max_execution_time */
ZEND_ATOL(EG(timeout_seconds), ZSTR_VAL(new_value));
return SUCCESS;
}
zend_unset_timeout();
ZEND_ATOL(EG(timeout_seconds), ZSTR_VAL(new_value));
zend_set_timeout(EG(timeout_seconds), 0);
return SUCCESS;
}
複製代碼
// main.c
int php_request_startup(void) {
......
if (PG(max_input_time) == -1) {
zend_set_timeout(EG(timeout_seconds), 1);
} else {
zend_set_timeout(PG(max_input_time), 1);
}
......
}
int php_execute_script(zend_file_handle *primary_file) {
......
if (PG(max_input_time) != -1) {
zend_set_timeout(INI_INT("max_execution_time"), 0);
}
......
}
複製代碼
從上面代碼能夠看到,若是設置了max_input_time
(即值不等於-1,-1能夠認爲是在CLI模式下),在php_request_startup()
階段會設置一個定時器,超時時間爲max_input_time
;在php_execute_script()
階段會從新設置一個定時器,超時時間爲max_execution_time
。那麼整個PHP腳本執行的最大執行時間就等於max_input_time
+ max_execution_time
。
若是沒有設置max_input_time
的話(即值等於-1),在php_request_startup()
階段也會設置一個定時器,但超時時間被設爲了EG(timeout_seconds)
,而EG(timeout_seconds)
已經在php_module_startup()
階段被賦值爲max_execution_time
,因此此時的超時時間就是max_execution_time
;在php_execute_script()
階段不會從新設置定時器,前一階段設置的max_execution_time
定時器仍然生效着。那麼整個PHP腳本的最大執行時間就是max_execution_time
。
zend_set_time()
使用setitimer(ITIMER_PROF, &t_r, NULL);
來實現定時器,ITIMER_PROF
會統計包括用戶態和內核態下所花費的時間,而像sleep()
這樣的系統調用會讓進程掛起,不佔用cpu時間片,因此這倆超時時間是不包括sleep()
時間的。
當定時器到時間後,ZendVM會拋出E_ERROR
,即Fatal error
錯誤。
; Time limit for child processes to wait for a reaction on signals from master. ; Available units: s(econds), m(inutes), h(ours), or d(ays) ; Default Unit: seconds
翻譯:
process_control_timeout
是留給子進程處理來自master進程信號的時間限制。
當master進程接收到SIGINT
、SIGTERM
、SIGQUIT
、SIGUSR2
這些信號時,會調用fpm_pctl()
來進行處理。
首先master進程會根據 接收到的信號 和 當前fpm的運行狀態 來決定發送給worker進程的是SIGQUIT
仍是SIGTERM
信號,同時註冊時間爲process_control_timeout
的定時事件。
若是在process_control_timeout
時間內子進程沒有退出,那麼master進程會升級SIGQUIT
爲SIGTERM
,SIGTERM
爲SIGKILL
,並註冊1s的定時事件。SIGKILL
就直接終止worker進程了,SIGTERM
還能再給worker進程1s的時間。
綜上,process_control_timeout
能夠理解爲master進程留給worker進程結束本身的時間,要是到時間worker還沒搞定那就開始master本身的策略了。
由於request_terminate_timeout
和 request_slowlog_timeout
聯繫比較密切,因此放在一塊兒來說。
request_terminate_timeout
; The timeout for serving a single request after which the worker process will ; be killed. This option should be used when the 'max_execution_time' ini option ; does not stop script execution for some reason. A value of '0' means 'off'. ; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) ; Default Value: 0
翻譯:執行一個請求的超時時間,在這以後worker進程將被終止。這個選項應該被用在
max_execution_time
這個ini選項因爲某些緣由不能中止腳本執行的時候。request_slowlog_timeout
; The timeout for serving a single request after which a PHP backtrace will be ; dumped to the 'slowlog' file. A value of '0s' means 'off'. ; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) ; Default Value: 0
翻譯:執行一個請求的超時時間,在這以後一個PHP的backtrace會被輸出到slowlog文件裏。
request_slowlog_timeout
和 request_terminate_timeout
用在master進程的心跳檢測中(fpm_pctl_heartbeat()
),心跳時間heartbeat
簡化後的算法是
request_terminate_timeout
狀況下:request_terminate_timeout/1000*3
request_terminate_timeout
狀況下:request_slowlog_timeout/1000*3
或者 0request_terminate_timeout >= request_slowlog_timeout
第三條規則是爲了保證slowlog不影響到正常的請求,heartbeat
取超時時間的1/3應該是爲了不心跳檢測過於頻繁,由於每次心跳檢測都須要遍歷全部worker進程。
若是超時事件發生了,那麼將直接kill掉worker進程,kill(child_pid, SIGTERM);
,以後內核回收資源關閉client_socket,nginx返回502錯誤給瀏覽器。