首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

深度剖析:瀏覽器關閉後 PHP-FPM 進程是否繼續執行?(ignore_user_abort、連線狀態、SIGPIPE 實測)

     編輯:PHP 相關知識

核心結論

**在標準 Nginx + PHP-FPM (FastCGI) 架構下,瀏覽器關閉/斷開連線後,PHP 腳本預設會**繼續執行**直到正常結束或達到 max_execution_time。** 只有當腳本嘗試向已斷開的連線**輸出資料** (觸發 SIGPIPE / EPIPE) 時,PHP 才會偵測到客戶端中斷,並根據 ignore_user_abort 設定決定是否終止。

實驗驗證過程

實驗 1:無輸出的長時間腳本

<?php
// test_sleep.php
ignore_user_abort(false); // 預設值
file_put_contents('/tmp/test.log', '1 start ' . microtime(true) . PHP_EOL, FILE_APPEND);
sleep(10);
file_put_contents('/tmp/test.log', '2 end ' . microtime(true) . PHP_EOL, FILE_APPEND);
?>

操作:瀏覽器請求 test_sleep.php → 立即關閉分頁/取消請求。
結果:/tmp/test.log 顯示兩行日誌,**腳本完整執行 10 秒後結束**,未提前中止。

實驗 2:加上 ignore_user_abort(true)

ignore_user_abort(true); // 明確設定
// 其餘同實驗 1

結果:同樣完整執行,**ignore_user_abort 在 FastCGI 下對「無輸出腳本」無效**。

實驗 3:周期性輸出 (觸發連線檢測)

<?php
ignore_user_abort(false);
file_put_contents('/tmp/test.log', '1 start ' . connection_status() . PHP_EOL, FILE_APPEND);
sleep(3);
for ($i=0; $i<10; $i++) {
    echo "chunk $i\n";
    flush(); // 強制刷新輸出緩衝區到 FastCGI 通道
    sleep(1);
    file_put_contents('/tmp/test.log', '2 loop ' . $i . ' status=' . connection_status() . ' abort=' . connection_aborted() . PHP_EOL, FILE_APPEND);
}
?>

客戶端:Go 程式模擬 HTTP 請求,發送 Header 後 sleep(1) 主動 conn.Close() (發送 FIN/RST)。
結果:

1 start status=0 abort=0
2 loop 0 status=0 abort=0   # 第一次 flush 成功 (客戶端尚未處理 RST)
2 loop 1 status=1 abort=1   # 第二次 flush 觸發 SIGPIPE/EPIPE,PHP 偵測到中斷
2 loop 2 status=1 abort=1
...
腳本在第 2 次迴圈後被終止 (因 ignore_user_abort=false)

底層機制深度解析

FastCGI 協議與連線狀態

  • Nginx 作為 FastCGI Client,透過 Unix Socket/TCP 與 PHP-FPM Master/Worker 通訊。
  • 請求開始時,Nginx 將 HTTP 請求轉換為 FastCGI 記錄發送給 Worker。
  • Worker 處理過程中,**FastCGI 通道保持開啟**,除非 Nginx 主動關閉 (客戶端斷開且 proxy_ignore_client_abort off 預設)。
  • **關鍵點**:PHP 運行時**不會主動輪詢**客戶端連線狀態,只有在**嘗試寫入 stdout/stderr** (對應 FastCGI stdout/stderr stream) 時,作業系統才會回傳 EPIPE / 發送 SIGPIPE 信號。

ignore_user_abort 運作邏輯

// PHP 內核簡化邏輯 (main/php_main.c / sapi/fpm/fpm/fpm_main.c)
if (php_handle_aborted_connection()) { // 檢測到 SIGPIPE/EPIPE
    if (!PG(ignore_user_abort)) {      // ignore_user_abort = false
        zend_bailout();                // 直接跳出執行 (類似 exit)
    }
    // ignore_user_abort = true 時,僅標記 PG(connection_status) = PHP_CONNECTION_ABORTED
    // 腳本繼續執行,後續輸出會再次觸發 SIGPIPE 但被忽略
}

connection_status() / connection_aborted()

  • connection_status() 回傳位元遮罩:0=NORMAL, 1=ABORTED, 2=TIMEOUT, 4=ABORTED|TIMEOUT
  • connection_aborted() 回傳布林:是否為 ABORTED 狀態
  • 這兩個函數**只反映 PHP 內核已偵測到的狀態**,不會主動偵測。

實戰場景與對策

場景 1:長時間任務 (報表生成、視頻轉碼、大量推播)

風險:用戶關閉頁面,任務仍佔用 Worker 進程,導致併發容量下降。
解決:

  1. 異步任務佇列:將任務推入 Redis/RabbitMQ,Worker 進程消費,HTTP 請求立即返回任務 ID。
  2. 定期檢查連線狀態並主動退出:
    ignore_user_abort(true); // 允許繼續
    for ($i=0; $i<100; $i++) {
        do_heavy_work();
        if (connection_aborted()) {
            // 記錄進度、清理資源
            exit; // 主動結束
        }
    }
  3. Nginx 設定 proxy_ignore_client_abort on;:讓 Nginx 繼續等待 PHP 完成再回應 (需評估超時風險)。

場景 2:串流輸出 (大檔下載、SSE、進度條)

風險:用戶取消下載,PHP 繼續讀取檔案/推送資料浪費頻寬。
解決:依賴預設行為 (ignore_user_abort=false)。每次 echo + flush() 會觸發連線檢測,客戶端斷開即自動終止腳本。**無需額外代碼**。

場景 3:關鍵業務必須完成 (支付回調、訂單狀態同步)

風險:上游服務超時重試,導致重複處理。
解決:ignore_user_abort(true) 確保腳本執行完畢,**但必須實現冪等性** (唯一索引、狀態機、分佈式鎖)。

配置參數對照表

層級參數預設值影響
PHP 運行時ignore_user_abort()false腳本層級控制
php.iniignore_user_abortOff全域預設值
php.inimax_execution_time30s硬性終止上限 (cli 為 0 無限制)
Nginxfastcgi_read_timeout60sNginx 等待 FastCGI 回應上限
Nginxproxy_ignore_client_abortoff客戶端斷開時是否繼續代理請求
PHP-FPMrequest_terminate_timeout0 (關閉)強制終止超時請求 (優先於 max_execution_time)

strace 系統調用證據

# 腳本執行到第二次 flush 時
sendto(4, "chunk 1\n", 8, 0, NULL, 0) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=1234} ---
# PHP 內核捕捉 SIGPIPE,檢查 ignore_user_abort,決定 zend_bailout()
# 後續迴圈不再執行

總結速查表

場景預設行為建議設定關鍵點
純計算/睡眠無輸出完整執行改用異步佇列PHP 無法感知斷線
定期輸出/flush斷線後下次輸出終止保持預設 (ignore_user_abort=false)依賴 SIGPIPE 機制
必須完成關鍵邏輯可能被中斷ignore_user_abort(true) + 冪等設計配合 connection_aborted() 記錄日誌
大檔下載/串流用戶取消即停止預設即最佳flush() 觸發檢測

結語

**「瀏覽器關閉 = PHP 停止」是常見誤區。** 在 FastCGI 架構下,PHP 腳本生命週期由**輸出動作觸發的連線檢測**與 **ignore_user_abort 設定**共同決定。理解此機制,才能正確設計長任務、串流輸出、關鍵業務的可靠處理策略。**永遠優先考慮異步佇列將長任務移出 HTTP 請求週期**,這是架構層面的最優解。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved