Symfony Process component returns exit code -1
Asked Answered
Y

1

8

I am getting exit code -1 from the Symfony process command on some machines, while exactly the same code returns exit code 0 as excepted on others. In both cases the given command is executed successfully regarding its output.

Running the same command line from shell gives the correct exit code (0). I produced a small test case to reproduce the issue:

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

Any ideas on how to diagnose this?

Yingling answered 5/8, 2015 at 9:36 Comment(0)
L
0

tl;dr

  • This shouldn't happen on PHP 8.3.
  • This can happen if you call some methods, such as isRunning or getExitCode inside the output callback of the process.

Long explanation

If we look under the hood into what Symfony Process is doing, we can see it's using proc_open.

In most cases, an exit status code of -1 is more likely to come from a process manager than the process itself, so we can be almost certain this isn't the real exit status code.

Before PHP 8.3, if we invoke proc_get_status() against the process resource, it will return the actual exit code only on the first time. Then, the process is discarded and any subsequent calls will return -1.

We can see this behavior by adding a breakpoint after proc_open and running proc_get_status($this->process) manually.

Long story short, Symfony Process is good at avoiding this issue, as it bails immediately after the process is no longer running, which prevents it from invoking that method more than once on the process and therefore discarding the actual exit status code.

I don't know all the ways that it's possible to trigger this but, but I was able to consistently reproduce it in at least one way, and it's a combination of factors and racing conditions.

The way that I was able to reproduce it is: If the output callback of the process invokes on itself a method such as isRunning or getExitCode, you incur in a racing condition that can trigger the bug. The racing condition depends on the time between the output being generated and the process exiting.

Here's an isolated 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;

This shouldn't happen on PHP 8.3, as this issue with get_proc_status has been fixed, as per changelog notes:

Executing proc_get_status() multiple times

Executing proc_get_status() multiple times will now always return the right value on POSIX systems. Previously, only the first call of the function returned the right value. Executing proc_close() after proc_get_status() will now also return the right exit code. Previously this would return -1.

PS: I don't have time to contribute a PR to Symfony Process right now, but if anyone does, I just make a pledge to keep backwards PHP compatibility.

Libel answered 7/2 at 2:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.