Skip to content

Support reading from blocking STDIN via child process/thread on Windows #83

Open
@clue

Description

@clue

Originally, #18 aimed to look into ways to bring native non-blocking I/O to Windows - which still doesn't look like it's going to be supported any time soon unfortunately.

As an alternative, we may use a child process or thread to start blocking read operations on the STDIN stream without blocking the main process.

Here's the gist of this concept:

<?php

use React\Stream\ReadableResourceStream;
use React\EventLoop\Factory;

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

$fds = array(
    0 => STDIN,
    1 => array('pipe', 'w'),
    2 => STDERR,
);

$p = proc_open('php ' . escapeshellarg(__DIR__ . '/stdin-child.php'), $fds, $pipes);

$loop = Factory::create();
$stdin = new ReadableResourceStream($pipes[1], $loop);

$stdin->on('data', function ($data) {
    echo "\r";
    var_dump($data);
});

$loop->run();

A simple child process could look something like this:

<?php

while (true) {
    $data = fread(STDIN, 8192);
    if ($data === '' || $data === false) {
        return;
    }

    echo $data;
}

On top of this, we can't really access the STDOUT stream without blocking either, so we may have to use socket I/O instead (see e.g. clue/reactphp-sqlite#13).

On other platforms, we should also avoid inheriting active FDs to the child process (see e.g. clue/reactphp-sqlite#7).

We should be able to use pthreads, libeio or libuv to avoid spawning a child process and use a worker thread instead. This does however require a custom PHP extension to be present.

On top of this, the console will echo each keypress to the output immediately. We may have to disable console echo, but to the best of my knowledge this isn't possible from within PHP either. We may spawn a special binary though, e.g. https://github.com/Seldaek/hidden-input as the C/C++ implementation is relatively straight forward. As an ugly workaround may be able to overwrite the console output using a periodic timer like this:

$overwrite = $loop->addPeriodicTimer(0.005, function () {
    echo "\r" . str_repeat(' ', 40) . "\r";
});
$stdin->on('close', function () use ($overwrite, $loop) {
    $loop->cancelTimer($overwrite);
});

Sounds like fun? 🎉

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions