Symfony Process 组件返回退出代码 -1

Bre*_*aun 5 symfony symfony-process

我在某些机器上从 Symfony 进程命令获得退出代码 -1,而完全相同的代码返回退出代码 0,而在其他机器上除外。在这两种情况下,给定命令的输出都成功执行。

从 shell 运行相同的命令行会给出正确的退出代码 (0)。我制作了一个小测试用例来重现这个问题:

use Symfony\Component\Process\Process;
$process = new Process('./console');
$process->run();
echo $process->getExitCode();
Run Code Online (Sandbox Code Playgroud)

关于如何诊断这个的任何想法?

Luc*_*nte 0

太长了;博士

  • PHP 8.3 上不应该发生这种情况。
  • 如果您调用某些方法(例如在流程的输出回调中调用isRunning或),则可能会发生这种情况。getExitCode

长解释

如果我们深入了解 Symfony Process 正在做什么,我们可以看到它正在使用proc_open。

在大多数情况下,退出状态代码-1更有可能来自进程管理器而不是进程本身,因此我们几乎可以肯定这不是真正的退出状态代码。

在 PHP 8.3 之前,如果我们调用proc_get_status()进程资源,它只会在第一次返回实际的退出代码。然后,该进程将被丢弃,任何后续调用都将返回-1

我们可以通过添加断点proc_open并手动运行来看到这种行为proc_get_status($this->process)

长话短说,Symfony Process 擅长避免这个问题,因为它在进程不再运行后立即退出,这可以防止它在进程上多次调用该方法,从而丢弃实际的退出状态代码。

我不知道可能触发此问题的所有方式,但我能够以至少一种方式一致地重现它,这是因素和赛车条件的结合。

我能够重现它的方式是:如果进程的输出回调isRunning本身调用诸如or之类的方法getExitCode,则会导致可能触发错误的竞争条件。竞争条件取决于生成输出和进程退出之间的时间。

这是一个孤立的 PoC:

<?php

if ( getenv( 'OUTPUT' ) ) {
    #echo 'This should fail when "wait" gets large';
    sleep( 2 );
    echo 'This should fail even when "wait" is small';
    die( 0 );
}

use Symfony\Component\Process\Process;

require_once __DIR__ . '/vendor/autoload.php';

do {
    // Increasingly bigger waits.
    static $wait = 0;
    $wait += 100000;
    echo sprintf( 'Wait: %s', $wait ) . PHP_EOL;

    $p = new Process( [ 'php', __FILE__ ], __DIR__, [
        'OUTPUT' => 1,
    ] );

    $p->start( function ( string $type, string $out ) use ( $p ) {
        echo $out . PHP_EOL;

        /**
         * Calling most methods in Symfony Process that triggers
         * updateStatus() can potentially trigger the -1 bug.
         *
         * @see Process::updateStatus()
         */
        echo sprintf( 'Is Running: %s', $p->isRunning() ? 'Yes' : 'No' ) . PHP_EOL;
        echo sprintf( 'Exit Code: %s', $p->getExitCode() ) . PHP_EOL;
    } );

    while ( $p->isRunning() ) {
        usleep( $wait );
    }

    if ( ! $p->isSuccessful() ) {
        break;
    }
} while ( true );

$is_started = $p->isStarted();
$is_running = $p->isRunning();
$exit_code  = $p->getExitCode();

echo sprintf( 'Started: %s, Running: %s, Exit code: %s', $is_started, $is_running, $exit_code ) . PHP_EOL;
Run Code Online (Sandbox Code Playgroud)

这在 PHP 8.3 上不应该发生,因为这个问题get_proc_status已经被修复,根据更新日志注释:

多次执行 proc_get_status()

现在,在 POSIX 系统上执行proc_get_status()多次将始终返回正确的值。以前,只有函数的第一次调用才会返回正确的值。proc_close()之后 执行proc_get_status()现在还将返回正确的退出代码。以前这将返回 -1。

PS:我现在没有时间向 Symfony Process 贡献 PR,但如果有人这样做,我只是承诺保持 PHP 的向后兼容性。