Why stream_select on STDIN becomes blocking when c

2019-07-05 12:54发布

问题:

Goal: run a PHP file in cmd, script loops x times and on every iteration checks to see if user has entered any input (stream_select() with STDIN) and if so - pauses the loop until the user hits enter, then prints out the input and continues with iteration.

Problem: Script runs perfectly as long as cmd.exe window is in focus - when I click on another window the script pauses at stream_select and doesn't continue until I but the cmd window back in focus and send it some input (a simple enter key press would do the trick). No errors.

Question: why does losing focus on cmd affect stream_select and block the loop? ...and is there a workaround? (e.g. is it possible to check if the current cmd window is in focus?)

Code example, used cmd php script.php in working directory.

<?php
$loopCount = 20;

while ($loopCount) {

    $start = microtime(true);

    echo 'check on "' . $loopCount . '"' . PHP_EOL;

    $stream = fopen('php://stdin', 'r');

    $stream_array = array($stream);
    $write = array();
    $except = array();

    if (stream_select($stream_array, $write, $except, 1, 0)) {
        $input = trim(fgets($stream));
        if ($input) {
            echo 'input was "' . $input . '"' . PHP_EOL;
        }
    }    

    fclose($stream);

    echo $loopCount . ' in ' . (microtime(true) - $start) . PHP_EOL;

    $loopCount--;    

}

Things I have tried with no luck:

  • moving fopen and fclose outside the loop
  • ignore_user_abort(1);
  • stream_set_blocking($stream, 0);
  • null, 0 and higher values for both tv_sec and tv_usec params of stream_select()
  • checking for connection_aborted() and connection_status()

Environment: Windows 7, XAMPP for windows, PHP 5.4.19 (cli), Zend Engine v2.4.0

回答1:

I think the problem is because stream_select() will also return 1 for STDIN when the stream receives EOL, which is what I think happens when cmd loses focus.

From http://www.php.net/manual/en/function.stream-select.php

in particular, a stream resource is also ready on end-of-file, in which case an fread() will return a zero length string

When focus is not lost and no input is given, stream_select returns 0 and the $read array passed in is emptied by reference. When focus is lost OR when input is given, the $read array remains intact and you are expected to read from each index.

But that leaves us window's users in a bad situation because we don't have a way to non-blockingly read that zero length string with stream_get_(contents|line) or fread or fgets. And since we cannot differentiate between losing focus and supplying actual data I believe we have to wait for one of these bugs to get fixed:

https://bugs.php.net/bug.php?id=36030

https://bugs.php.net/bug.php?id=34972

I'm currently exploring an option to prevent the window from losing focus via the win32 API, but that looks impractical for even my needs.

Here is the workaround I'm using for now...

https://gist.github.com/anonymous/80080060869f875e7214