当前位置: 首页 > 知识库问答 >
问题:

MySQL代码导致PHP脚本在popen/exec处崩溃

鲁淇
2023-03-14

我在< code>Ubuntu 14.04服务器上有以下< code>PHP 5.6.19代码。这段代码简单地连接到一个< code>MySQL 5.6.28数据库,等待一分钟,启动自己的另一个进程,然后退出。

注意:这是完整的脚本,其目的是演示问题-它没有做任何有用的事情。

class DatabaseConnector {
    const DB_HOST = 'localhost';
    const DB_NAME = 'database1';
    const DB_USERNAME = 'root';
    const DB_PASSWORD = 'password';

    public static $db;

    public static function Init() {
        if (DatabaseConnector::$db === null) {
            DatabaseConnector::$db = new PDO('mysql:host=' . DatabaseConnector::DB_HOST . ';dbname=' . DatabaseConnector::DB_NAME . ';charset=utf8', DatabaseConnector::DB_USERNAME, DatabaseConnector::DB_PASSWORD);
        }
    }
}

$startTime = time();

// ***** Script works fine if this line is removed.
DatabaseConnector::Init();

while (true) {
    // Sleep for 100 ms.
    usleep(100000);

    if (time() - $startTime > 60) {
        $filePath = __FILE__;
        $cmd = "nohup php $filePath > /tmp/1.log 2>&1 &";

        // ***** Script sometimes exits here without opening the process and without errors.
        $p = popen($cmd, 'r');

        pclose($p);

        exit;
    }
}

我使用nohup php myscript开始了脚本的第一个进程.php

这个过程循环应该永远持续下去,但是......基于多次测试,在一天之内(但不是立即),服务器上的过程无缘无故地“消失”。我发现MySQL代码导致popen代码失败(脚本退出时没有任何错误或输出)。

这里发生了什么事?

注意事项

  • 服务器全天候运行。
  • 记忆不是问题
  • 数据库连接正确
  • 文件路径不包含空格
  • 当使用shell_execexec而不是popen(和pclose)时,也存在同样的问题

我还知道< code>popen是失败的那一行,因为我通过在脚本中的某些点记录到一个文件中做了进一步的调试(上面没有显示)。

共有3个答案

有德业
2023-03-14

脚本将在没有任何错误或输出的情况下退出

当代码中没有错误检查时并不奇怪。但是,如果它真的是“崩溃”,那么:

>

  • 如果原因被PHP运行时捕获,那么它将尝试记录错误。您是否尝试过故意创建错误场景以改变重新排序/日志记录是否如您预期的那样工作?

    如果错误没有被PHP运行时捕获,则操作系统应该转储核心文件 - 您是否检查过操作系统配置?查找核心文件?分析过吗?

    $cmd = "nohup php $filePath

    这可能并不像你想象的那样。当您在后台使用大多数版本的nohup运行一个进程时,它仍然保留与父进程的关系;在子进程退出之前不能收获父进程——并且一个子进程总是在它之前产卵另一个子进程。

    这不是保持代码在后台运行/作为守护程序的有效方法。正确的方法取决于你想要实现的目标。是否有特定原因尝试每 60 秒续订一次进程?

    (永远不要显式关闭数据库连接——这不是什么大问题,因为PHP应该在调用< code>exit时这样做)。

    你可能想看看这个和这个

  • 童铭晨
    2023-03-14

    我在使用<code>pcntl_fork()</code>和MySQL连接时也遇到了类似的情况。这里的原因可能是相同的。

    < code>popen()创建一个子进程。对< code>pclose()的调用关闭通信通道,子进程继续运行,直到退出。这是事情开始失控的时候。

    当子进程完成时,父进程将收到 SIGCHLD 信号。此处的父进程是运行您发布的代码的 PHP 解释器。子进程是使用 popen() 启动的进程(它运行什么命令并不重要)。

    这里有一件小事你可能不知道,或者你在文档中发现并忽略了它,因为当一个人用PHP编程时,它没有多大意义。在< code>sleep()的文档中提到:

    如果调用被信号中断,sleep()返回一个非零值。

    PHP函数只是Linux系统调用的一个包装器(而uspt()PHP函数是Linux系统调用的一个包装器。)

    PHP文档中未说明的内容在系统调用文档中有明确说明:

    < code>sleep()使调用线程Hibernate,直到秒秒过去或一个不被忽略的信号到达。

    在代码中有两个 PHP 解释器调用 usleep() Linux 系统函数的地方。其中之一是清晰可见的:您的PHP代码调用它。另一个是隐藏的(见下文)。

    从第二次迭代开始,如果当父程序在< code>usleep(100000)调用中时,子进程(在前一次迭代中使用< code>popen()创建)碰巧退出,PHP解释器进程接收到< code>SIGCHLD信号,并在超时前继续执行。< code>usleep()比预期早返回。因为超时时间很短,这种影响是肉眼无法观察到的。把0.1秒换成10秒,你会注意到的。

    但是,除了中断的超时之外,这不会影响代码的执行。

    传入信号损害程序执行的第二个地方隐藏在PHP解释器代码的深处。由于某些协议原因,MySQL客户端库在多个位置使用sleep()和/或usleep()。如果当SIGCHLD到达时,解释器碰巧在其中一个调用中,MySQL客户端库代码会意外恢复,并且很多时候,它以错误状态“MySQL服务器已消失(错误2006)”结束。

    您的代码可能会忽略(或吞下)MySQL错误状态(因为它不希望它发生在那个地方)。我的没有,我花了几天的调查来找出上面总结的事实。

    这个问题的解决方案很简单(在你知道了上面暴露的所有内部细节之后)。它在上面的留档引用中得到了暗示:“一个信号到达了,它没有被忽视”。

    当不需要信号到达时,可以屏蔽(忽略)信号。PHP PCNTL 扩展提供了函数 pcntl_sigprocmask()。它包装了 sigprocmask() Linux 系统调用,该调用设置从现在开始程序可以接收哪些信号(实际上,要阻止哪些信号)。

    根据您的需要,您可以实施两种策略。

    如果您的程序需要与数据库通信并在子进程处理完成时得到通知,那么您必须将所有数据库调用封装在对< code>pcntl_sigprocmask()的一对调用中,以阻塞然后解除阻塞< code>SIGCHLD信号。

    如果您不关心子进程何时完成,那么您只需调用:

    pcntl_sigprocmask(SIG_BLOCK, array(SIGCHLD));
    

    在您开始创建任何子进程之前(在while()之前)。它使您的进程忽略子进程的终止,并让它在没有意外中断的情况下运行其数据库查询。

    SIGCHLD信号的默认处理是调用wait(),以便在完成子进程后进行系统清理。如果信号未被处理(因为其传递被阻止),会发生什么情况,请参见<code>wait()</code>的文档:

    终止但尚未等待的子进程成为“僵尸”。内核维护有关僵尸进程的最小信息集(PID、终止状态、资源使用信息),以便允许父进程稍后执行等待以获取有关子进程的信息。只要僵尸没有通过等待从系统中删除,它就会消耗内核进程表中的一个槽,如果该表填满,就无法创建进一步的进程。如果父进程终止,则其“僵尸”子进程(如果有)被init(1)采用,它会自动执行等待以删除僵尸。

    简单地说,如果您阻止了<code>SIGCHLD</code>信号的接收,那么您必须调用<code>pcntl_wait()</code>,以清理僵尸子进程。

    您可以添加:

    pcntl_wait($status, WNOHANG);
    

    while 循环中的某个位置(例如,就在它结束之前)。

    皇甫鸿远
    2023-03-14

    分叉后父进程是否确实退出?我原以为pclose会等到孩子退出后再回来。

    如果它没有退出,我会推测,由于mySQL连接从未关闭,当您生成子进程树时,您最终将达到其连接限制(或其他限制)。

    编辑1

    我只是试图复制这一点。我修改了你的脚本,每半秒分叉一次,而不是每分钟一次,并且能够在大约10分钟内将其杀死。

    看起来子进程的重复创建正在生成越来越多的FD,直到最终它不能再有了:

    $ lsof | grep type=STREAM | wc -l
    240
    $ lsof | grep type=STREAM | wc -l
    242
    ...
    $ lsof | grep type=STREAM | wc -l
    425
    $ lsof | grep type=STREAM | wc -l
    428
    ...
    

    这是因为孩子在分叉时继承了父级的FD(在本例中为mySQL连接)。

    如果您在popen之前关闭mySQL连接(在您的情况下):

    DatabaseConnector::$db = null;
    

    这个问题有望解决。

     类似资料:
    • 我有一个脚本,它使用php-l检查php文件中的语法错误。它在windows中工作正常,但在Linux中输出不正确: 文件exec_ip的内容。正在检查语法错误的php是(它有要检查的语法错误): 剧本是: 窗口(WAMP){更正}中的结果: LINUX(Centos/cPanel){未知输出}中的结果: 请有人帮助我,并告诉我为什么它在linux生产服务器中给出了不正确的输出。我也尝试过用she

    • 我是PHP和phpseclib实现的新SSH。 我有以下代码: 我在这里试图完成的是将用户在远程服务器上选择的文件复制到同一服务器上的新目录。在执行脚本时,它成功地找到第一个文件并将其复制到新目录,但之后脚本就停止了。即使用户只选择了一个项目,脚本也会挂起并且不会继续。它甚至不增加 对可能发生的事情有什么想法吗? 更新: 实时NET_SSH2日志 我还直接在服务器上运行了这个命令,它工作得非常好。

    • 对不起,英语不好。。 我有这样的php文件: 这是剧本: 现在当加载文件时。对于浏览器,脚本可以工作,但只执行wget和sed命令,cp不工作。。不复制文件!如果我手动运行脚本到终端(Debian 8),所有cmd都会执行。。。问题在哪里?谢谢约勒

    • 我需要检查为什么exec函数停止我的php脚本。 我在HTTP查询中调用了“exec”函数中的bat文件(但我遇到了passthru、system和proc_open的问题),2分钟后我得到了一个错误代码500(没有详细信息)。但是我的bat文件总是在后台运行(我可以看到创建的文件…)我是这样使用它的: 在$输出中我什么都得不到,在phperror_log什么都没有...我添加了以下参数: 同样的

    • 本文向大家介绍Opcache导致php-fpm崩溃nginx返回502,包括了Opcache导致php-fpm崩溃nginx返回502的使用技巧和注意事项,需要的朋友参考一下 我这个博客为了提高运行效率在vps上装了opcache扩展,结果发现有个页面返回502,其他页面正常。 检查了php-fpm日志,发现是php-fpm子进程不知道为什么会崩溃,然后把opcache关了就正常。中间折腾的过程就

    • 问题内容: 我有一个简单的PHP函数,该函数应在调用时执行Pyton脚本。我已经在我的php程序中多次尝试了这种功能,但是这次以某种方式该功能根本不执行python脚本。当我从命令提示符下访问脚本并运行时,它将成功执行。我要提到的一件事是,该脚本具有python的NLTK库的一些认真实现,并且执行和执行其操作(即数据处理并存储到db)需要20秒钟以上。执行延迟是否会导致此问题,或者这次我还缺少其他