I've been experimenting with Amp\Process to better understand how it works. I launched several processes, connecting them stdout -> stdin, simulated various situations in order to understand where errors could occur and how to handle them correctly, including so that there were no zombie processes left. I have encountered one problem, the nature of which is not clear to me.
Important: I am using the Windows 10 operating system and what I am working on will run on the same operating system.
I will give some synthetic examples. Most of them work correctly, but one works unexpectedly. In all examples, I will use the ImageMagick command to convert a PNG image to a JPEG.
Example 1 (works well):
\Amp\Process\Process::start([
'magick',
'image.png', // input file
'-quality', '100',
'JPEG:image.jpg' // output file
]);
\Revolt\EventLoop::run();
Now I modify this example to pass the input image to stdin.
Example 2 (works well):
$content = \file_get_contents('image.png');
$process = \Amp\Process\Process::start([
'magick',
'-', // input stdin
'-quality', '100',
'JPEG:image.jpg' // output file
]);
$process->getStdin()->write($content);
$process->getStdin()->end();
\Revolt\EventLoop::run();
Next, I change the example so that the output image is sent to stdout.
Example 3 (works well):
$content = \file_get_contents('image.png');
$process = \Amp\Process\Process::start([
'magick',
'-', // input stdin
'-quality', '100',
'JPEG:-' // output stdout
]);
$process->getStdin()->write($content);
$process->getStdin()->end();
$content = \Amp\ByteStream\buffer($process->getStdout());
\file_put_contents('image.jpg', $content);
\Revolt\EventLoop::run();
Now I remove the code that reads from stdout. I expect the script to hang forever on the \Revolt\EventLoop::run();
call. I realize this is stupid code, but it demonstrates the problem.
Example 4 (not working correctly):
$content = \file_get_contents('image.png');
$process = \Amp\Process\Process::start([
'magick',
'-', // input stdin
'-quality', '100',
'JPEG:-' // output stdout
]);
$process->getStdin()->write($content);
$process->getStdin()->end();
\Revolt\EventLoop::run();
The script does hang at the line \Revolt\EventLoop::run();
, but every time after exactly 120 seconds, I get an exception:
Uncaught Amp\Process\ProcessException: Received 0 of 5 expected bytes in C:\test\vendor\amphp\process\src\Internal\Windows\SocketConnector.php:245
The exception text is always identical, it reports the expected 5 bytes.
Then I additionally remove the code that sends the input image to stdin.
Example 5 (works well):
\Amp\Process\Process::start([
'magick',
'-', // input stdin
'-quality', '100',
'JPEG:-' // output stdout
]);
\Revolt\EventLoop::run();
In this case, the script hangs indefinitely, as expected (perhaps not indefinitely, but certainly much longer than 120 seconds).
Then I try again to specify the input file as the command argument.
Example 6 (works well):
\Amp\Process\Process::start([
'magick',
'image.png', // input file
'-quality', '100',
'JPEG:-' // output stdout
]);
\Revolt\EventLoop::run();
In this example, the script still hangs forever, as expected.
In each of the examples I tried replacing \Revolt\EventLoop::run();
with $process->join();
, this did not affect the results in any way.
Below I provide the full stack trace of the exception from example 4, but with $process->join();
:
PHP Fatal error: Uncaught Amp\Process\ProcessException: Received 0 of 5 expected bytes in C:\test\vendor\amphp\process\src\Internal\Windows\SocketConnector.php:245
Stack trace:
#0 C:\test\vendor\amphp\process\src\Internal\Windows\SocketConnector.php(219): Amp\Process\Internal\Windows\SocketConnector->read(Object(Amp\ByteStream\ReadableResourceStream), 5)
#1 C:\test\vendor\amphp\process\src\Internal\Windows\SocketConnector.php(102): Amp\Process\Internal\Windows\SocketConnector->readExitCode(Object(Amp\ByteStream\ReadableResourceStream))
#2 C:\test\vendor\amphp\amp\src\functions.php(34): Amp\Process\Internal\Windows\SocketConnector->Amp\Process\Internal\Windows\{closure}()
#3 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(425): Amp\{closure}(NULL, NULL, Array)
#4 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(616): Revolt\EventLoop\Internal\AbstractDriver->invokeMicrotasks()
#5 [internal function]: Revolt\EventLoop\Internal\AbstractDriver->Revolt\EventLoop\Internal\{closure}()
#6 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\DriverSuspension.php(64): Fiber->resume(NULL)
#7 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(425): Revolt\EventLoop\Internal\DriverSuspension::Revolt\EventLoop\Internal\{closure}()
#8 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(616): Revolt\EventLoop\Internal\AbstractDriver->invokeMicrotasks()
#9 [internal function]: Revolt\EventLoop\Internal\AbstractDriver->Revolt\EventLoop\Internal\{closure}()
#10 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(498): Fiber->start()
#11 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(553): Revolt\EventLoop\Internal\AbstractDriver->invokeCallbacks()
#12 [internal function]: Revolt\EventLoop\Internal\AbstractDriver->Revolt\EventLoop\Internal\{closure}()
#13 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(94): Fiber->resume()
#14 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\DriverSuspension.php(117): Revolt\EventLoop\Internal\AbstractDriver->Revolt\EventLoop\Internal\{closure}()
#15 C:\test\vendor\amphp\amp\src\Future.php(251): Revolt\EventLoop\Internal\DriverSuspension->suspend()
#16 C:\test\vendor\amphp\process\src\Internal\Windows\WindowsRunner.php(128): Amp\Future->await(NULL)
#17 C:\test\vendor\amphp\process\src\Process.php(123): Amp\Process\Internal\Windows\WindowsRunner->join(Object(Amp\Process\Internal\Windows\WindowsHandle), NULL)
#18 C:\test\test.php(19): Amp\Process\Process->join()
#19 {main}
Next Amp\Future\UnhandledFutureError: Unhandled future: Amp\Process\ProcessException: "Received 0 of 5 expected bytes"; Await the Future with Future::await() before the future is destroyed or use Future::ignore() to suppress this exception. The future has been created at #0 C:\test\vendor\amphp\amp\src\functions.php:40 Amp\Internal\FutureState->__construct()
#1 C:\test\vendor\amphp\process\src\Internal\Windows\SocketConnector.php:100 Amp\async()
#2 C:\test\vendor\amphp\process\src\Internal\Windows\WindowsRunner.php:101 Amp\Process\Internal\Windows\SocketConnector->connectPipes()
#3 C:\test\vendor\amphp\process\src\Process.php:80 Amp\Process\Internal\Windows\WindowsRunner->start()
#4 C:\test\test.php:9 Amp\Process\Process::start() in C:\test\vendor\amphp\amp\src\Internal\FutureState.php:53
Stack trace:
#0 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(425): Amp\Internal\FutureState->__destruct()
#1 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(616): Revolt\EventLoop\Internal\AbstractDriver->invokeMicrotasks()
#2 [internal function]: Revolt\EventLoop\Internal\AbstractDriver->Revolt\EventLoop\Internal\{closure}()
#3 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\DriverSuspension.php(64): Fiber->resume(NULL)
#4 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(425): Revolt\EventLoop\Internal\DriverSuspension::Revolt\EventLoop\Internal\{closure}()
#5 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(616): Revolt\EventLoop\Internal\AbstractDriver->invokeMicrotasks()
#6 [internal function]: Revolt\EventLoop\Internal\AbstractDriver->Revolt\EventLoop\Internal\{closure}()
#7 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(498): Fiber->start()
#8 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(553): Revolt\EventLoop\Internal\AbstractDriver->invokeCallbacks()
#9 [internal function]: Revolt\EventLoop\Internal\AbstractDriver->Revolt\EventLoop\Internal\{closure}()
#10 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(94): Fiber->resume()
#11 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\DriverSuspension.php(117): Revolt\EventLoop\Internal\AbstractDriver->Revolt\EventLoop\Internal\{closure}()
#12 C:\test\vendor\amphp\amp\src\Future.php(251): Revolt\EventLoop\Internal\DriverSuspension->suspend()
#13 C:\test\vendor\amphp\process\src\Internal\Windows\WindowsRunner.php(128): Amp\Future->await(NULL)
#14 C:\test\vendor\amphp\process\src\Process.php(123): Amp\Process\Internal\Windows\WindowsRunner->join(Object(Amp\Process\Internal\Windows\WindowsHandle), NULL)
#15 C:\test\test.php(19): Amp\Process\Process->join()
#16 {main}
Next Revolt\EventLoop\UncaughtThrowable: Uncaught Amp\Future\UnhandledFutureError thrown in event loop callback Amp\Internal\FutureState::Amp\Internal\{closure} defined in C:\test\vendor\amphp\amp\src\Internal\FutureState.php:54; use Revolt\EventLoop::setErrorHandler() to gracefully handle such exceptions: Unhandled future: Amp\Process\ProcessException: "Received 0 of 5 expected bytes"; Await the Future with Future::await() before the future is destroyed or use Future::ignore() to suppress this exception. The future has been created at #0 C:\test\vendor\amphp\amp\src\functions.php:40 Amp\Internal\FutureState->__construct()
#1 C:\test\vendor\amphp\process\src\Internal\Windows\SocketConnector.php:100 Amp\async()
#2 C:\test\vendor\amphp\process\src\Internal\Windows\WindowsRunner.php:101 Amp\Process\Internal\Windows\SocketConnector->connectPipes()
#3 C:\test\vendor\amphp\process\src\Process.php:80 Amp\Process\Internal\Windows\WindowsRunner->start()
#4 C:\test\test.php:9 Amp\Process\Process::start() in C:\test\vendor\revolt\event-loop\src\EventLoop\UncaughtThrowable.php:13
Stack trace:
#0 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\AbstractDriver.php(400): Revolt\EventLoop\UncaughtThrowable::throwingCallback(Object(Closure), Object(Amp\Future\UnhandledFutureError))
#1 C:\test\vendor\revolt\event-loop\src\EventLoop\Internal\DriverSuspension.php(127): Revolt\EventLoop\Internal\AbstractDriver::Revolt\EventLoop\Internal\{closure}()
#2 C:\test\vendor\amphp\amp\src\Future.php(251): Revolt\EventLoop\Internal\DriverSuspension->suspend()
#3 C:\test\vendor\amphp\process\src\Internal\Windows\WindowsRunner.php(128): Amp\Future->await(NULL)
#4 C:\test\vendor\amphp\process\src\Process.php(123): Amp\Process\Internal\Windows\WindowsRunner->join(Object(Amp\Process\Internal\Windows\WindowsHandle), NULL)
#5 C:\test\test.php(19): Amp\Process\Process->join()
#6 {main}
thrown in C:\test\vendor\revolt\event-loop\src\EventLoop\UncaughtThrowable.php on line 13