php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #65650 Using proc_open with file handles fails on large buffers
Submitted: 2013-09-10 15:04 UTC Modified: 2015-04-29 14:42 UTC
Votes:9
Avg. Score:5.0 ± 0.0
Reproduced:8 of 8 (100.0%)
Same Version:6 (75.0%)
Same OS:7 (87.5%)
From: imprec at gmail dot com Assigned: cmb (profile)
Status: Not a bug Package: Program Execution
PHP Version: 5.4.19 OS: Irrelevant
Private report: No CVE-ID: None
 [2013-09-10 15:04 UTC] imprec at gmail dot com
Description:
------------
When using proc_open with file handles instead of pipes (required on Windows, 
because of bug https://bugs.php.net/bug.php?id=51800), when reading large outputs,  
the result might not retrieve what's expected.

The problem has been mentioned when compiling large sass files to stdout on 
windows. The output may sometimes be different than the actual output you can get 
running the command in console.

The provided script fails 3 times on 5 on windows environment. Nearly everytime on 
OSX.

Test script:
---------------
<?php

error_reporting(E_ALL);

$cmd = sprintf('php -r %s', escapeshellarg('fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);'));    
$stdout = tmpfile();
$stderr = tmpfile();
$descriptors = array(
    array('pipe', 'r'),
    $stdout,
    $stderr,
);
$handles = array(
    $stdout, $stderr
);

$datastdin = str_repeat('*!', 8192*128);

$options = array_merge(array('suppress_errors' => true, 'binary_pipes' => true, 'bypass_shell' => false));
$process = proc_open($cmd, $descriptors, $pipes, getcwd(), array(), $options);

fwrite($pipes[0], $datastdin);
fclose($pipes[0]);
unset($pipes[0]);
$read = array();
while ($handles) {
    $status = proc_get_status($process);
    if (!$status['running']) { 
        proc_close($process);
    }
    $h = $handles;
    foreach ($h as $offset => $handle) {
        fseek($handle, isset($read[$offset]) ? strlen($read[$offset]) : 0);
        
        if (!isset($read[$offset])) {
            $read[$offset] = '';
        }
        $read[$offset] .= fread($handle, 8192);
        if (false === $status['running'] && feof($handle)) {
            fclose($handle);
            unset($handles[$offset]);
        }
    }
}

foreach ($read as $r) {
    assert($r === $datastdin);
}

Expected result:
----------------
No errors, assertion is good.

Actual result:
--------------
PHP Warning:  assert(): Assertion failed in C:\symfony\src\Symfony\Component\Pro
cess\bug.php on line 47
PHP Warning:  assert(): Assertion failed in C:\symfony\src\Symfony\Component\Pro
cess\bug.php on line 47

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2013-09-10 15:10 UTC] imprec at gmail dot com
-: romain at neutron dot io +: imprec at gmail dot com
 [2013-09-10 15:10 UTC] imprec at gmail dot com
Update email
 [2014-07-24 15:04 UTC] bill at zeroedin dot com
Your example code doesn't work for me.

In both PHP 5.4 and PHP 5.5, it spams "
Warning: proc_get_status(): 7 is not a valid process resource in C:\wamp\www\surveyor\65650_e.php on line 27

Warning: proc_close(): 7 is not a valid process resource in C:\wamp\www\surveyor\65650_e.php on line 29" to the console indefinitely.

I rewrote the example code to behave properly and I am unable to verify this bug.

New test script:
----------------
<?php

error_reporting(E_ALL);

$cmd = sprintf('SET XDEBUG_CONFIG=&"C:\wamp\bin\php\php5.5.12\php.exe" -r %s', escapeshellarg('fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);'));

$stdout = tmpfile();
$stderr = tmpfile();
$descriptors = array(
    array('pipe', 'r'),
    $stdout,
    $stderr,
);
$handles = array(
    $stdout, $stderr
);

$datastdin = str_repeat('*!', 1024*1024*20);

$options = array_merge(array('suppress_errors' => true, 'binary_pipes' => true, 'bypass_shell' => false));
$process = proc_open($cmd, $descriptors, $pipes, getcwd(), array(), $options);

$status = proc_get_status($process);
fwrite($pipes[0], $datastdin);
fclose($pipes[0]);
unset($pipes[0]);
$read = array();
$start = microtime(true);
$timeout = 10;
while ($status['running']) {
    if((microtime(true) - $start) > $timeout) {
        echo "timed out.";
        break;
    }
    usleep(150000);
    $status = proc_get_status($process);
}
proc_close($process);
rewind($stdout);
rewind($stderr);
$read['stdout'] = stream_get_contents($stdout);
$read['stderr'] = stream_get_contents($stderr);
fclose($stdout);
fclose($stderr);

$failed = false;
foreach ($read as $r) {
    if(!assert($r === $datastdin)) {
        $failed = true;
    }
}
if($failed) {
    echo "It's a bug!";
} else {
    echo "It's not a bug!";
}

Expected result:
-----------------
It's not a bug!

Actual result:
--------------
It's not a bug!

Tested on Windows 8.1 using php versions 5.5.12-nts and 5.4.31-nts.
 [2015-04-29 14:42 UTC] cmb@php.net
-Status: Open +Status: Not a bug -Assigned To: +Assigned To: cmb
 [2015-04-29 14:42 UTC] cmb@php.net
This is not a bug in PHP, but rather a timing issue in the supplied
test script. You have to wait until the other process has finished,
before you can read all its stdout and stderr. See Bill's code (the
while ($status['running']) loop) for a possible solution.

Furthermore it is safer to call proc_close only after all pipes
haven been closed.
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Sun Nov 19 01:31:42 2017 UTC