如何監測PHP進程異常退出(進程被殺)的原因?

背景

通常,在cli下運行的常駐後臺PHP進程,可能異常退出,比如php執行過程中出現的致命錯誤,或被 kill 命令手動殺死等。如下面的php代碼:

1

2

3

4

5

6

7

8

9

<?php

while(1){

    $content fgets(STDIN);

    if(empty($content)){

        sleep(1);

    }

    //邏輯處理部分代碼省略

}

?>

 

查錯

我們使用register_shutdown_function可以跟蹤到底是什麼錯誤導致的進程退出。(想更多瞭解register_shutdown_function,請查看博文 妙用php中的register_shutdown_function和fastcgi_finish_request)加入了錯誤捕捉代碼。如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

<?php

$is_end = false;

function catch_error(){

        global $is_end;

        $time date('Y-m-d H:i:s');

        $error = error_get_last();

        $msg "$time [error]";

        if($is_end){

                $msg .= "is_end[yes]";

        }else{

                $msg .= "is_end[no]";

        }

        if($error){

                $msg .= var_export($error,1);

        }

        echo $msg."\r\n";

}

register_shutdown_function("catch_error");

?>

 

可是,php當進程再次退出時,在日誌中並沒有記錄任何信息。說明register_shutdown_function方法根本沒有執行。是什麼導致register_shutdown_function方法沒有運行呢?在php的官方文檔中又這樣一個註釋:

Shutdown functions will not be executed if the process is killed with a SIGTERM or SIGKILL signal. While you cannot intercept a SIGKILL, you can use pcntl_signal() to install a handler for a SIGTERM which uses exit() to end cleanly.

註釋的意思是當php進程獲得SIGTERM和SIGKILL信號而退出時,是不執行register_shutdown_function方法的。可以使用pcntl_signal()方法來捕獲信息,並調用相應的處理方法。

好,如何檢測是信號導致我們的php進程退出的呢?我們加入如下代碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

<?php

declare(ticks = 1);

function sig_handler($signo){

        $time date('Y-m-d H:i:s');

        echo $time." exit  signo[{$signo}]\r\n";

        exit("");

}

pcntl_signal(SIGTERM, "sig_handler");

pcntl_signal(SIGHUP, "sig_handler");

pcntl_signal(SIGINT, "sig_handler");

pcntl_signal(SIGQUIT, "sig_handler");

pcntl_signal(SIGILL, "sig_handler");

pcntl_signal(SIGPIPE, "sig_handler");

pcntl_signal(SIGALRM, "sig_handler");

?>

 

過一段時間,發現php進程退出了,日誌中出現瞭如下日誌信息:

2014-11-23 18:30:06 exit  signo[14]

2014-11-23 18:30:06 [error]is_end[no]

看來是sigalarm信號導致php進程退出了。這個信號是可以捕獲和處理的。kill 命令會發出sigalarm信號。最終的代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

<?php

declare(ticks = 1);

$is_end = false;

function catch_error(){

        global $is_end;

        $time date('Y-m-d H:i:s');

        $error = error_get_last();

        $msg "$time [error]";

        if($is_end){

                $msg .= "is_end[yes]";

        }else{

                $msg .= "is_end[no]";

        }

        if($error){

                $msg .= var_export($error,1);

        }

        echo $msg."\r\n";

}

register_shutdown_function("catch_error");

  

function sig_handler($signo){

        $time date('Y-m-d H:i:s');

        if($signo == 14){

                //忽略alarm信號

                echo $time." ignore alarm signo[{$signo}]\r\n";

        }else{

                echo $time." exit  signo[{$signo}]\r\n";

                exit("");

        }

}

pcntl_signal(SIGTERM, "sig_handler");

pcntl_signal(SIGHUP, "sig_handler");

pcntl_signal(SIGINT, "sig_handler");

pcntl_signal(SIGQUIT, "sig_handler");

pcntl_signal(SIGILL, "sig_handler");

pcntl_signal(SIGPIPE, "sig_handler");

pcntl_signal(SIGALRM, "sig_handler");

while(1){

    $content fgets(STDIN);

    if(empty($content)){

        sleep(1);

    }

    //邏輯處理部分代碼省略

}

$is_end = true;

?>

 

這時再kill 進程,在日誌中就發現了alarm相關的日誌,但是php進程依然在。

PS:以上代碼如果用kill -9命令殺死(SIGKILL信號),還是會退出的。也就是說SIGKILL信號是無法捕獲的。

 

參考

http://blogread.cn/it/article/7256?f=wb_blogread

 

PS:

error_get_last() 函數獲得最後發生的錯誤。

該函數以數組的形式返回最後發生的錯誤。如果沒有錯誤發生則返回 NULL。

返回的錯誤數組包含 4 個鍵名和鍵值:

  • [type] - 錯誤類型
  • [message] - 錯誤消息
  • [file] - 發生錯誤所在的文件
  • [line] - 發生錯誤所在的行
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章