退出时自动重启 PHP 脚本

2022-01-03 00:00:00 cron bash restart sh php php-pcntl

有没有一种方法可以在 PHP 脚本退出时自动重新启动它,无论它是正确退出,还是由于错误或内存使用最大化等而终止?

Is there a way I can automatically restart a PHP script whenever it exits, regardless of whether it has been exited properly, or has terminated due to an error, or maxed memory usage, etc.?

推荐答案

PHP 脚本也可以使用 PCNTL 重新启动.

免责声明:本练习的目的只是为了证明PHP完全有能力重新启动并回答问题:

A PHP Script can also restart itself with PCNTL.

Disclaimer: the point of this exercise is only to prove that PHP is perfectly capable of restarting itself and to answer the question:

有没有一种方法可以在 PHP 脚本退出时自动重新启动它,无论它是正确退出还是由于错误而终止?

Is there a way I can automatically restart a PHP script whenever it exits, regardless of whether it has been exited properly, or has terminated due to an error?

因此,关于 unix 进程的任何细节都超出了我们的范围,所以我建议您从 PCNTL 书籍 或参考 php-pcntl 了解更多详情.

It is therefor beyond our scope to go into any detail about unix processes so I would suggest you start with the PCNTL book or refer to php-pcntl for more details.

在示例中,我们将假设它是在 *nix 环境中从终端启动的 PHP CLI 脚本,使用命令:

In the examples we will assume it is a PHP CLI script launched in a *nix environment from a terminal with a semi-decent shell using the command:

$ php i-can-restart-myself.php 0

我们传递一个重启计数属性作为进程重启的指示符.

We pass a restart count attribute as indicator that the process was restarted.

<?php
    echo ++$argv[1];     // count every restart
    $_ = $_SERVER['_'];  // or full path to php binary

    echo "
======== start =========
";
    // do a lot of stuff
    $cnt = 0;
    while( $cnt++ < 10000000 ){}
    echo "
========== end =========
";

    // restart myself
    pcntl_exec($_, $argv);

无论是否正常终止?

是的,如果终止,我可以重新启动!

<?php
    echo ++$argv[1];    
    $_ = $_SERVER['_']; 

    register_shutdown_function(function () {
        global $_, $argv; // note we need to reference globals inside a function
        // restart myself
        pcntl_exec($_, $argv);
    });

    echo "
======== start =========
";
    // do a lot of stuff
    $cnt = 0;
    while( $cnt++ < 10000000 ){}
    echo "
========== end =========
";

    die; // exited properly
    // we can't reach here 
    pcntl_exec($_, $argv);

还是因为错误而终止?

同上!

<?php
    echo ++$argv[1];    
    $_ = $_SERVER['_']; 

    register_shutdown_function(function () {
        global $_, $argv; 
        // restart myself
        pcntl_exec($_, $argv);
    });

    echo "
======== start =========
";
    // do a lot of stuff
    $cnt = 0;
    while( $cnt++ < 10000000 ){}
    echo "
===== what if? =========
";
    require 'OOPS! I dont exist.'; // FATAL Error:

    // we can't reach here
    echo "
========== end =========
";        
    die; // exited properly
    // we can't reach here 
    pcntl_exec($_, $argv);

但你知道我想要的不止这些吗?

当然可以!我可以在杀死,集线器甚至 Ctrl-C 时重新启动!

<?php
    echo ++$argv[1];     
    $_ = $_SERVER['_'];  
    $restartMyself = function () {
        global $_, $argv; 
        pcntl_exec($_, $argv);
    };
    register_shutdown_function($restartMyself);
    pcntl_signal(SIGTERM, $restartMyself); // kill
    pcntl_signal(SIGHUP,  $restartMyself); // kill -s HUP or kill -1
    pcntl_signal(SIGINT,  $restartMyself); // Ctrl-C

    echo "
======== start =========
";
    // do a lot of stuff
    $cnt = 0;
    while( $cnt++ < 10000000 ){}
    echo "
===== what if? =========
";
    require 'OOPS! I dont exist.'; // FATAL Error:

    // we can't reach here
    echo "
========== end =========
";        
    die; // exited properly
    // we can't reach here 
    pcntl_exec($_, $argv);

我现在如何终止它?

如果您通过按住 Ctrl-C 来淹没进程,您可能会在关机的某个地方捕获它.

If you flood the process by holding down Ctrl-C you might just catch it somewhere in the shutdown.

我不想看到所有这些错误,我也可以重新启动它们吗?

没问题我也能处理错误!

<?php
    echo ++$argv[1];     
    $_ = $_SERVER['_'];  
    $restartMyself = function () {
        global $_, $argv; 
        pcntl_exec($_, $argv);
    };
    register_shutdown_function($restartMyself);
    pcntl_signal(SIGTERM, $restartMyself);   
    pcntl_signal(SIGHUP,  $restartMyself);   
    pcntl_signal(SIGINT,  $restartMyself);   
    set_error_handler($restartMyself , E_ALL); // Catch all errors

    echo "
======== start =========
";
    // do a lot of stuff
    $cnt = 0;
    while( $cnt++ < 10000000 ){}

    echo $CAREFUL_NOW; // NOTICE: will also be caught

    // we would normally still go here
    echo "
===== what if? =========
";
    require 'OOPS! I dont exist.'; // FATAL Error:

    // we can't reach here
    echo "
========== end =========
";        
    die; // exited properly
    // we can't reach here 
    pcntl_exec($_, $argv);

尽管乍一看这似乎工作正常,但由于 pcntl_exec 在同一进程中运行,我们并未注意到事情非常错误.如果您想生成一个新进程并让旧进程死掉,这是一个非常可行的替代方案,请参阅下一篇文章,您会注意到每次迭代都会启动 2 个进程,因为我们由于共同的疏忽而触发了一个 PHP 通知和一个错误.这当然可以通过确保在错误处理程序中调用 pcntl_exec 之后调用 die() 或 exit() 来轻松纠正,否则 PHP 假定容忍被接受并继续.

Although this appears to be working fine at first glance, because pcntl_exec runs in the same process we do not notice that things are terribly wrong. If you wanted to spawn a new process and letting the old one die instead, which is a perfectly viable alternative see next post, you will notice 2 processes are started each iteration, because we trigger a PHP NOTICE and an ERROR due to a common oversight. Which of course can easily be rectified by ensuring we die() or exit() after the call to pcntl_exec in the error handler, otherwise PHP assumes tolerance were accepted and continues.

我想说明的一点是,即使您能够重新启动失败的脚本,这也不是允许损坏代码的借口.尽管这种做法可能存在可行的用例,我强烈反对应该采用失败重启作为脚本因错误而失败的解决方案!正如我们所看到的这些示例无法确切知道它在哪里失败,因此我们无法确定它将从哪里重新开始.寻求看似运行良好的快速修复"解决方案现在可能会比以前遇到更多问题.

The point I am trying to make is that even if you are able to relaunch a script that failed this is not an excuse to allow broken code. Although there may exist viable use cases for this practice, I strongly disagree that restart on failure should be employed as a solution for scripts failing due to errors! As we can see from these examples there is no way of knowing exactly where it failed so we cant be sure where it will start from again. Going for the "quick fix" solution which could appear to be working fine may have more problems now then before.

相反,我更愿意通过一些适当的单元测试来解决缺陷,这将有助于找出罪魁祸首,以便我们可以纠正问题.如果 PHP 内存不足,可以通过在使用后取消设置变量来保守使用资源来避免.(顺便说一句.对于相同的结果,您会发现分配 null 比使用 unset 快得多)不过,恐怕如果不解决,重新启动肯定会成为您已经拥有的蠕虫罐头的合适开启者.

I would instead prefer to see the defect addressed with some proper unit tests which will help flush out the culprit so we may rectify the problem. If PHP runs out of memory it can be avoided through conservative use of resources by unsetting the variables after use. (btw. you will find assigning null is much quicker than using unset for the same result) I am afraid though, if left unresolved, restarting would most definitely serve as suitable opener to the can of worms you already have.

相关文章