mirror of
https://github.com/danog/process.git
synced 2024-11-26 20:24:43 +01:00
Refactor for full Windows compatibility
This commit is contained in:
parent
88f8865a6a
commit
7cf91efd08
Binary file not shown.
Binary file not shown.
@ -6,7 +6,11 @@ use Amp\ByteStream\Message;
|
|||||||
use Amp\Process\Process;
|
use Amp\Process\Process;
|
||||||
|
|
||||||
Amp\Loop::run(function () {
|
Amp\Loop::run(function () {
|
||||||
$process = yield Process::start("echo 'Hello, world!'");
|
// "echo" is a shell internal command on Windows and doesn't work.
|
||||||
|
$command = DIRECTORY_SEPARATOR === "\\" ? "cmd /c echo Hello World!" : "echo 'Hello, world!'";
|
||||||
|
|
||||||
|
$process = new Process($command);
|
||||||
|
$process->start();
|
||||||
|
|
||||||
echo yield new Message($process->getStdout());
|
echo yield new Message($process->getStdout());
|
||||||
|
|
||||||
|
@ -6,19 +6,18 @@ use Amp\Process\Process;
|
|||||||
use Amp\Promise;
|
use Amp\Promise;
|
||||||
use function Amp\Promise\all;
|
use function Amp\Promise\all;
|
||||||
|
|
||||||
function show_process_output(Promise $promise): \Generator
|
function show_process_output(Process $process): \Generator
|
||||||
{
|
{
|
||||||
/** @var Process $process */
|
|
||||||
$process = yield $promise;
|
|
||||||
|
|
||||||
$stream = $process->getStdout();
|
$stream = $process->getStdout();
|
||||||
while ($chunk = yield $stream->read()) {
|
|
||||||
|
while (null !== $chunk = yield $stream->read()) {
|
||||||
echo $chunk;
|
echo $chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
$code = yield $process->join();
|
$code = yield $process->join();
|
||||||
|
$pid = yield $process->getPid();
|
||||||
|
|
||||||
echo "Process {$process->getPid()} exited with {$code}\n";
|
echo "Process {$pid} exited with {$code}\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
Amp\Loop::run(function () {
|
Amp\Loop::run(function () {
|
||||||
@ -27,7 +26,9 @@ Amp\Loop::run(function () {
|
|||||||
$promises = [];
|
$promises = [];
|
||||||
|
|
||||||
foreach ($hosts as $host) {
|
foreach ($hosts as $host) {
|
||||||
$promises[] = new \Amp\Coroutine(show_process_output(Process::start("ping {$host}")));
|
$process = new Process("ping {$host}");
|
||||||
|
$process->start();
|
||||||
|
$promises[] = new \Amp\Coroutine(show_process_output($process));
|
||||||
}
|
}
|
||||||
|
|
||||||
yield all($promises);
|
yield all($promises);
|
||||||
|
@ -5,10 +5,12 @@ include dirname(__DIR__) . "/vendor/autoload.php";
|
|||||||
use Amp\Process\Process;
|
use Amp\Process\Process;
|
||||||
|
|
||||||
Amp\Loop::run(function () {
|
Amp\Loop::run(function () {
|
||||||
$process = yield Process::start("echo 1; sleep 1; echo 2; sleep 1; echo 3; exit 42");
|
$process = new Process("echo 1; sleep 1; echo 2; sleep 1; echo 3; exit 42");
|
||||||
|
$process->start();
|
||||||
|
|
||||||
$stream = $process->getStdout();
|
$stream = $process->getStdout();
|
||||||
while ($chunk = yield $stream->read()) {
|
|
||||||
|
while (null !== $chunk = yield $stream->read()) {
|
||||||
echo $chunk;
|
echo $chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,13 @@ use Amp\ByteStream\Message;
|
|||||||
use Amp\Process\Process;
|
use Amp\Process\Process;
|
||||||
|
|
||||||
Amp\Loop::run(function () {
|
Amp\Loop::run(function () {
|
||||||
$process = yield Process::start('read ; echo "$REPLY"');
|
if (DIRECTORY_SEPARATOR === "\\") {
|
||||||
|
echo "This example doesn't work on Windows." . PHP_EOL;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$process = new Process('read; echo "$REPLY"');
|
||||||
|
$process->start();
|
||||||
|
|
||||||
/* send to stdin */
|
/* send to stdin */
|
||||||
$process->getStdin()->write("abc\n");
|
$process->getStdin()->write("abc\n");
|
||||||
|
@ -24,6 +24,9 @@ final class Handle extends ProcessHandle {
|
|||||||
/** @var string */
|
/** @var string */
|
||||||
public $extraDataPipeWatcher;
|
public $extraDataPipeWatcher;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
public $extraDataPipeStartWatcher;
|
||||||
|
|
||||||
/** @var int */
|
/** @var int */
|
||||||
public $originalParentPid;
|
public $originalParentPid;
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ final class Runner implements ProcessRunner {
|
|||||||
|
|
||||||
public static function onProcessEndExtraDataPipeReadable($watcher, $stream, Handle $handle) {
|
public static function onProcessEndExtraDataPipeReadable($watcher, $stream, Handle $handle) {
|
||||||
Loop::cancel($watcher);
|
Loop::cancel($watcher);
|
||||||
|
$handle->extraDataPipeWatcher = null;
|
||||||
|
|
||||||
$handle->status = ProcessStatus::ENDED;
|
$handle->status = ProcessStatus::ENDED;
|
||||||
|
|
||||||
@ -55,14 +56,13 @@ final class Runner implements ProcessRunner {
|
|||||||
|
|
||||||
$handle->status = ProcessStatus::RUNNING;
|
$handle->status = ProcessStatus::RUNNING;
|
||||||
$handle->pidDeferred->resolve((int) $pid);
|
$handle->pidDeferred->resolve((int) $pid);
|
||||||
$deferreds[0]->resolve(new ResourceOutputStream($pipes[0]));
|
$deferreds[0]->resolve($pipes[0]);
|
||||||
$deferreds[1]->resolve(new ResourceInputStream($pipes[1]));
|
$deferreds[1]->resolve($pipes[1]);
|
||||||
$deferreds[2]->resolve(new ResourceInputStream($pipes[2]));
|
$deferreds[2]->resolve($pipes[2]);
|
||||||
|
|
||||||
$handle->extraDataPipeWatcher = Loop::onReadable($stream, [self::class, 'onProcessEndExtraDataPipeReadable'], $handle);
|
if ($handle->extraDataPipeWatcher !== null) {
|
||||||
Loop::unreference($handle->extraDataPipeWatcher);
|
Loop::enable($handle->extraDataPipeWatcher);
|
||||||
|
}
|
||||||
$handle->sockets->resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
@ -104,10 +104,18 @@ final class Runner implements ProcessRunner {
|
|||||||
|
|
||||||
\stream_set_blocking($pipes[3], false);
|
\stream_set_blocking($pipes[3], false);
|
||||||
|
|
||||||
Loop::onReadable($pipes[3], [self::class, 'onProcessStartExtraDataPipeReadable'], [$handle, $pipes, [
|
$handle->extraDataPipeStartWatcher = Loop::onReadable($pipes[3], [self::class, 'onProcessStartExtraDataPipeReadable'], [$handle, [
|
||||||
|
new ResourceOutputStream($pipes[0]),
|
||||||
|
new ResourceInputStream($pipes[1]),
|
||||||
|
new ResourceInputStream($pipes[2]),
|
||||||
|
], [
|
||||||
$stdinDeferred, $stdoutDeferred, $stderrDeferred
|
$stdinDeferred, $stdoutDeferred, $stderrDeferred
|
||||||
]]);
|
]]);
|
||||||
|
|
||||||
|
$handle->extraDataPipeWatcher = Loop::onReadable($pipes[3], [self::class, 'onProcessEndExtraDataPipeReadable'], $handle);
|
||||||
|
Loop::unreference($handle->extraDataPipeWatcher);
|
||||||
|
Loop::disable($handle->extraDataPipeWatcher);
|
||||||
|
|
||||||
return $handle;
|
return $handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,13 +132,21 @@ final class Runner implements ProcessRunner {
|
|||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function kill(ProcessHandle $handle) {
|
public function kill(ProcessHandle $handle) {
|
||||||
/** @var Handle $handle */
|
/** @var Handle $handle */
|
||||||
|
if ($handle->extraDataPipeWatcher !== null) {
|
||||||
|
Loop::cancel($handle->extraDataPipeWatcher);
|
||||||
|
$handle->extraDataPipeWatcher = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Handle $handle */
|
||||||
|
if ($handle->extraDataPipeStartWatcher !== null) {
|
||||||
|
Loop::cancel($handle->extraDataPipeStartWatcher);
|
||||||
|
$handle->extraDataPipeStartWatcher = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!\proc_terminate($handle->proc, 9)) { // Forcefully kill the process using SIGKILL.
|
if (!\proc_terminate($handle->proc, 9)) { // Forcefully kill the process using SIGKILL.
|
||||||
throw new ProcessException("Terminating process failed");
|
throw new ProcessException("Terminating process failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
Loop::cancel($handle->extraDataPipeWatcher);
|
|
||||||
$handle->extraDataPipeWatcher = null;
|
|
||||||
|
|
||||||
$handle->status = ProcessStatus::ENDED;
|
$handle->status = ProcessStatus::ENDED;
|
||||||
$handle->joinDeferred->fail(new ProcessException("The process was killed"));
|
$handle->joinDeferred->fail(new ProcessException("The process was killed"));
|
||||||
}
|
}
|
||||||
@ -147,11 +163,23 @@ final class Runner implements ProcessRunner {
|
|||||||
public function destroy(ProcessHandle $handle) {
|
public function destroy(ProcessHandle $handle) {
|
||||||
/** @var Handle $handle */
|
/** @var Handle $handle */
|
||||||
if ($handle->status < ProcessStatus::ENDED && \getmypid() === $handle->originalParentPid) {
|
if ($handle->status < ProcessStatus::ENDED && \getmypid() === $handle->originalParentPid) {
|
||||||
$this->kill($handle);
|
try {
|
||||||
|
$this->kill($handle);
|
||||||
|
} catch (ProcessException $e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @var Handle $handle */
|
||||||
if ($handle->extraDataPipeWatcher !== null) {
|
if ($handle->extraDataPipeWatcher !== null) {
|
||||||
Loop::cancel($handle->extraDataPipeWatcher);
|
Loop::cancel($handle->extraDataPipeWatcher);
|
||||||
|
$handle->extraDataPipeWatcher = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Handle $handle */
|
||||||
|
if ($handle->extraDataPipeStartWatcher !== null) {
|
||||||
|
Loop::cancel($handle->extraDataPipeStartWatcher);
|
||||||
|
$handle->extraDataPipeStartWatcher = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_resource($handle->extraDataPipe)) {
|
if (\is_resource($handle->extraDataPipe)) {
|
||||||
|
@ -8,6 +8,7 @@ use Amp\Process\Internal\ProcessHandle;
|
|||||||
final class Handle extends ProcessHandle {
|
final class Handle extends ProcessHandle {
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->joinDeferred = new Deferred;
|
$this->joinDeferred = new Deferred;
|
||||||
|
$this->pidDeferred = new Deferred;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Deferred */
|
/** @var Deferred */
|
||||||
@ -16,6 +17,9 @@ final class Handle extends ProcessHandle {
|
|||||||
/** @var string */
|
/** @var string */
|
||||||
public $exitCodeWatcher;
|
public $exitCodeWatcher;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
public $exitCodeRequested = false;
|
||||||
|
|
||||||
/** @var resource */
|
/** @var resource */
|
||||||
public $proc;
|
public $proc;
|
||||||
|
|
||||||
@ -26,7 +30,7 @@ final class Handle extends ProcessHandle {
|
|||||||
public $wrapperStderrPipe;
|
public $wrapperStderrPipe;
|
||||||
|
|
||||||
/** @var resource[] */
|
/** @var resource[] */
|
||||||
public $sockets;
|
public $sockets = [];
|
||||||
|
|
||||||
/** @var Deferred[] */
|
/** @var Deferred[] */
|
||||||
public $stdioDeferreds;
|
public $stdioDeferreds;
|
||||||
|
@ -27,7 +27,7 @@ final class Runner implements ProcessRunner {
|
|||||||
|
|
||||||
private $socketConnector;
|
private $socketConnector;
|
||||||
|
|
||||||
private function makeCommand(string $command, string $workingDirectory): string {
|
private function makeCommand(string $workingDirectory): string {
|
||||||
$result = sprintf(
|
$result = sprintf(
|
||||||
'%s --address=%s --port=%d --token-size=%d',
|
'%s --address=%s --port=%d --token-size=%d',
|
||||||
\escapeshellarg(self::WRAPPER_EXE_PATH),
|
\escapeshellarg(self::WRAPPER_EXE_PATH),
|
||||||
@ -40,8 +40,6 @@ final class Runner implements ProcessRunner {
|
|||||||
$result .= ' ' . \escapeshellarg('--cwd=' . \rtrim($workingDirectory, '\\'));
|
$result .= ' ' . \escapeshellarg('--cwd=' . \rtrim($workingDirectory, '\\'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$result .= ' ' . $command;
|
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,12 +49,14 @@ final class Runner implements ProcessRunner {
|
|||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function start(string $command, string $cwd = null, array $env = [], array $options = []): ProcessHandle {
|
public function start(string $command, string $cwd = null, array $env = [], array $options = []): ProcessHandle {
|
||||||
$command = $this->makeCommand($command, $cwd ?? '');
|
if (strpos($command, "\0") !== false) {
|
||||||
|
throw new ProcessException("Can't execute commands that contain null bytes.");
|
||||||
|
}
|
||||||
|
|
||||||
$options['bypass_shell'] = true;
|
$options['bypass_shell'] = true;
|
||||||
|
|
||||||
$handle = new Handle;
|
$handle = new Handle;
|
||||||
$handle->proc = @\proc_open($command, self::FD_SPEC, $pipes, $cwd ?: null, $env ?: null, $options);
|
$handle->proc = @\proc_open($this->makeCommand($cwd ?? ''), self::FD_SPEC, $pipes, $cwd ?: null, $env ?: null, $options);
|
||||||
|
|
||||||
if (!\is_resource($handle->proc)) {
|
if (!\is_resource($handle->proc)) {
|
||||||
$message = "Could not start process";
|
$message = "Could not start process";
|
||||||
@ -74,16 +74,16 @@ final class Runner implements ProcessRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$securityTokens = \random_bytes(SocketConnector::SECURITY_TOKEN_SIZE * 6);
|
$securityTokens = \random_bytes(SocketConnector::SECURITY_TOKEN_SIZE * 6);
|
||||||
$written = \fwrite($pipes[0], $securityTokens);
|
$written = \fwrite($pipes[0], $securityTokens . "\0" . $command . "\0");
|
||||||
|
|
||||||
\fclose($pipes[0]);
|
\fclose($pipes[0]);
|
||||||
\fclose($pipes[1]);
|
\fclose($pipes[1]);
|
||||||
|
|
||||||
if ($written !== SocketConnector::SECURITY_TOKEN_SIZE * 6) {
|
if ($written !== SocketConnector::SECURITY_TOKEN_SIZE * 6 + \strlen($command) + 2) {
|
||||||
\fclose($pipes[2]);
|
\fclose($pipes[2]);
|
||||||
\proc_close($handle->proc);
|
\proc_close($handle->proc);
|
||||||
|
|
||||||
throw new ProcessException("Could not send security tokens to process wrapper");
|
throw new ProcessException("Could not send security tokens / command to process wrapper");
|
||||||
}
|
}
|
||||||
|
|
||||||
$handle->securityTokens = \str_split($securityTokens, SocketConnector::SECURITY_TOKEN_SIZE);
|
$handle->securityTokens = \str_split($securityTokens, SocketConnector::SECURITY_TOKEN_SIZE);
|
||||||
@ -91,13 +91,16 @@ final class Runner implements ProcessRunner {
|
|||||||
$handle->wrapperStderrPipe = $pipes[2];
|
$handle->wrapperStderrPipe = $pipes[2];
|
||||||
|
|
||||||
$stdinDeferred = new Deferred;
|
$stdinDeferred = new Deferred;
|
||||||
$handle->stdioDeferreds[] = new ProcessOutputStream($stdinDeferred->promise());
|
$handle->stdioDeferreds[] = $stdinDeferred;
|
||||||
|
$handle->stdin = new ProcessOutputStream($stdinDeferred->promise());
|
||||||
|
|
||||||
$stdoutDeferred = new Deferred;
|
$stdoutDeferred = new Deferred;
|
||||||
$handle->stdioDeferreds[] = new ProcessInputStream($stdoutDeferred->promise());
|
$handle->stdioDeferreds[] = $stdoutDeferred;
|
||||||
|
$handle->stdout = new ProcessInputStream($stdoutDeferred->promise());
|
||||||
|
|
||||||
$stderrDeferred = new Deferred;
|
$stderrDeferred = new Deferred;
|
||||||
$handle->stdioDeferreds[] = new ProcessInputStream($stderrDeferred->promise());
|
$handle->stdioDeferreds[] = $stderrDeferred;
|
||||||
|
$handle->stderr = new ProcessInputStream($stderrDeferred->promise());
|
||||||
|
|
||||||
$this->socketConnector->registerPendingProcess($handle);
|
$this->socketConnector->registerPendingProcess($handle);
|
||||||
|
|
||||||
@ -107,6 +110,8 @@ final class Runner implements ProcessRunner {
|
|||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function join(ProcessHandle $handle): Promise {
|
public function join(ProcessHandle $handle): Promise {
|
||||||
/** @var Handle $handle */
|
/** @var Handle $handle */
|
||||||
|
$handle->exitCodeRequested = true;
|
||||||
|
|
||||||
if ($handle->exitCodeWatcher !== null) {
|
if ($handle->exitCodeWatcher !== null) {
|
||||||
Loop::reference($handle->exitCodeWatcher);
|
Loop::reference($handle->exitCodeWatcher);
|
||||||
}
|
}
|
||||||
@ -122,8 +127,10 @@ final class Runner implements ProcessRunner {
|
|||||||
throw new ProcessException("Terminating process failed");
|
throw new ProcessException("Terminating process failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
Loop::cancel($handle->exitCodeWatcher);
|
if ($handle->exitCodeWatcher !== null) {
|
||||||
$handle->exitCodeWatcher = null;
|
Loop::cancel($handle->exitCodeWatcher);
|
||||||
|
$handle->exitCodeWatcher = null;
|
||||||
|
}
|
||||||
|
|
||||||
$handle->status = ProcessStatus::ENDED;
|
$handle->status = ProcessStatus::ENDED;
|
||||||
$handle->joinDeferred->fail(new ProcessException("The process was killed"));
|
$handle->joinDeferred->fail(new ProcessException("The process was killed"));
|
||||||
@ -137,12 +144,17 @@ final class Runner implements ProcessRunner {
|
|||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function destroy(ProcessHandle $handle) {
|
public function destroy(ProcessHandle $handle) {
|
||||||
/** @var Handle $handle */
|
/** @var Handle $handle */
|
||||||
if ($handle->status < ProcessStatus::ENDED) {
|
if ($handle->status < ProcessStatus::ENDED && \is_resource($handle->proc)) {
|
||||||
$this->kill($handle);
|
try {
|
||||||
|
$this->kill($handle);
|
||||||
|
} catch (ProcessException $e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($handle->exitCodeWatcher !== null) {
|
if ($handle->exitCodeWatcher !== null) {
|
||||||
Loop::cancel($handle->exitCodeWatcher);
|
Loop::cancel($handle->exitCodeWatcher);
|
||||||
|
$handle->exitCodeWatcher = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($i = 0; $i < 4; $i++) {
|
for ($i = 0; $i < 4; $i++) {
|
||||||
@ -151,8 +163,8 @@ final class Runner implements ProcessRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
\stream_get_contents($handle->wrapperStderrPipe);
|
@\stream_get_contents($handle->wrapperStderrPipe);
|
||||||
\fclose($handle->wrapperStderrPipe);
|
@\fclose($handle->wrapperStderrPipe);
|
||||||
|
|
||||||
if (\is_resource($handle->proc)) {
|
if (\is_resource($handle->proc)) {
|
||||||
\proc_close($handle->proc);
|
\proc_close($handle->proc);
|
||||||
|
@ -46,7 +46,7 @@ final class SocketConnector {
|
|||||||
Loop::unreference(Loop::onReadable($this->server, [$this, 'onServerSocketReadable']));
|
Loop::unreference(Loop::onReadable($this->server, [$this, 'onServerSocketReadable']));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function failClientHandshake($socket, int $code): void {
|
private function failClientHandshake($socket, int $code) {
|
||||||
\fwrite($socket, \chr(SignalCode::HANDSHAKE_ACK) . \chr($code));
|
\fwrite($socket, \chr(SignalCode::HANDSHAKE_ACK) . \chr($code));
|
||||||
\fclose($socket);
|
\fclose($socket);
|
||||||
|
|
||||||
@ -84,10 +84,6 @@ final class SocketConnector {
|
|||||||
$data = \fread($socket, $length);
|
$data = \fread($socket, $length);
|
||||||
|
|
||||||
if ($data === false || $data === '') {
|
if ($data === false || $data === '') {
|
||||||
\fclose($socket);
|
|
||||||
Loop::cancel($state->readWatcher);
|
|
||||||
Loop::cancel($state->timeoutWatcher);
|
|
||||||
unset($this->pendingClients[(int) $socket]);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,16 +200,15 @@ final class SocketConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onReadableChildPid($watcher, $socket, Handle $handle) {
|
public function onReadableChildPid($watcher, $socket, Handle $handle) {
|
||||||
Loop::cancel($watcher);
|
|
||||||
Loop::cancel($handle->connectTimeoutWatcher);
|
|
||||||
|
|
||||||
$data = \fread($socket, 5);
|
$data = \fread($socket, 5);
|
||||||
|
|
||||||
if ($data === false || $data === '') {
|
if ($data === false || $data === '') {
|
||||||
$this->failHandleStart($handle, 'Failed to read PID from wrapper: No data received');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loop::cancel($watcher);
|
||||||
|
Loop::cancel($handle->connectTimeoutWatcher);
|
||||||
|
|
||||||
if (\strlen($data) !== 5) {
|
if (\strlen($data) !== 5) {
|
||||||
$this->failHandleStart(
|
$this->failHandleStart(
|
||||||
$handle, 'Failed to read PID from wrapper: Received %d of 5 expected bytes', \strlen($data)
|
$handle, 'Failed to read PID from wrapper: Received %d of 5 expected bytes', \strlen($data)
|
||||||
@ -237,27 +232,27 @@ final class SocketConnector {
|
|||||||
$handle->stdioDeferreds[2]->resolve(new ResourceInputStream($handle->sockets[2]));
|
$handle->stdioDeferreds[2]->resolve(new ResourceInputStream($handle->sockets[2]));
|
||||||
|
|
||||||
$handle->exitCodeWatcher = Loop::onReadable($handle->sockets[0], [$this, 'onReadableExitCode'], $handle);
|
$handle->exitCodeWatcher = Loop::onReadable($handle->sockets[0], [$this, 'onReadableExitCode'], $handle);
|
||||||
Loop::unreference($handle->exitCodeWatcher);
|
if (!$handle->exitCodeRequested) {
|
||||||
|
Loop::unreference($handle->exitCodeWatcher);
|
||||||
|
}
|
||||||
|
|
||||||
unset($this->pendingProcesses[$handle->wrapperPid]);
|
unset($this->pendingProcesses[$handle->wrapperPid]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onReadableExitCode($watcher, $socket, Handle $handle) {
|
public function onReadableExitCode($watcher, $socket, Handle $handle) {
|
||||||
$handle->exitCodeWatcher = null;
|
|
||||||
Loop::cancel($watcher);
|
|
||||||
|
|
||||||
$data = \fread($socket, 5);
|
$data = \fread($socket, 5);
|
||||||
|
|
||||||
if ($data === false || $data === '') {
|
if ($data === false || $data === '') {
|
||||||
$handle->status = ProcessStatus::ENDED;
|
|
||||||
$handle->joinDeferred->fail(new ProcessException('Failed to read exit code from wrapper: No data received'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$handle->exitCodeWatcher = null;
|
||||||
|
Loop::cancel($watcher);
|
||||||
|
|
||||||
if (\strlen($data) !== 5) {
|
if (\strlen($data) !== 5) {
|
||||||
$handle->status = ProcessStatus::ENDED;
|
$handle->status = ProcessStatus::ENDED;
|
||||||
$handle->joinDeferred->fail(new ProcessException(
|
$handle->joinDeferred->fail(new ProcessException(
|
||||||
\sprintf('Failed to read exit code from wrapper: Recieved %d of 5 expected bytes', \strlen($data))
|
\sprintf('Failed to read exit code from wrapper: Received %d of 5 expected bytes', \strlen($data))
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -317,6 +312,8 @@ final class SocketConnector {
|
|||||||
foreach ($handle->stdioDeferreds as $deferred) {
|
foreach ($handle->stdioDeferreds as $deferred) {
|
||||||
$deferred->fail($error);
|
$deferred->fail($error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$handle->joinDeferred->fail($error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerPendingProcess(Handle $handle) {
|
public function registerPendingProcess(Handle $handle) {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Amp\Process;
|
namespace Amp\Process;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use Amp\Process\Internal\Posix\Runner as PosixProcessRunner;
|
use Amp\Process\Internal\Posix\Runner as PosixProcessRunner;
|
||||||
use Amp\Process\Internal\ProcessHandle;
|
use Amp\Process\Internal\ProcessHandle;
|
||||||
use Amp\Process\Internal\ProcessRunner;
|
use Amp\Process\Internal\ProcessRunner;
|
||||||
@ -11,7 +12,7 @@ use Amp\Promise;
|
|||||||
|
|
||||||
class Process {
|
class Process {
|
||||||
/** @var ProcessRunner */
|
/** @var ProcessRunner */
|
||||||
private static $processRunner;
|
private $processRunner;
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
private $command;
|
private $command;
|
||||||
@ -57,6 +58,16 @@ class Process {
|
|||||||
$this->cwd = $cwd;
|
$this->cwd = $cwd;
|
||||||
$this->env = $envVars;
|
$this->env = $envVars;
|
||||||
$this->options = $options;
|
$this->options = $options;
|
||||||
|
|
||||||
|
$this->processRunner = Loop::getState(self::class);
|
||||||
|
|
||||||
|
if ($this->processRunner === null) {
|
||||||
|
$this->processRunner = \strncasecmp(\PHP_OS, "WIN", 3) === 0
|
||||||
|
? new WindowsProcessRunner
|
||||||
|
: new PosixProcessRunner;
|
||||||
|
|
||||||
|
Loop::setState(self::class, $this->processRunner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,7 +75,7 @@ class Process {
|
|||||||
*/
|
*/
|
||||||
public function __destruct() {
|
public function __destruct() {
|
||||||
if ($this->handle !== null) {
|
if ($this->handle !== null) {
|
||||||
self::$processRunner->destroy($this->handle);
|
$this->processRunner->destroy($this->handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +93,7 @@ class Process {
|
|||||||
throw new StatusError("Process has already been started.");
|
throw new StatusError("Process has already been started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->handle = self::$processRunner->start($this->command, $this->cwd, $this->env, $this->options);
|
$this->handle = $this->processRunner->start($this->command, $this->cwd, $this->env, $this->options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,7 +108,7 @@ class Process {
|
|||||||
throw new StatusError("Process has not been started.");
|
throw new StatusError("Process has not been started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::$processRunner->join($this->handle);
|
return $this->processRunner->join($this->handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,7 +122,7 @@ class Process {
|
|||||||
throw new StatusError("The process is not running");
|
throw new StatusError("The process is not running");
|
||||||
}
|
}
|
||||||
|
|
||||||
self::$processRunner->kill($this->handle);
|
$this->processRunner->kill($this->handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -127,7 +138,7 @@ class Process {
|
|||||||
throw new StatusError("The process is not running");
|
throw new StatusError("The process is not running");
|
||||||
}
|
}
|
||||||
|
|
||||||
self::$processRunner->signal($this->handle, $signo);
|
$this->processRunner->signal($this->handle, $signo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -200,7 +211,7 @@ class Process {
|
|||||||
* @return ProcessOutputStream
|
* @return ProcessOutputStream
|
||||||
*/
|
*/
|
||||||
public function getStdin(): ProcessOutputStream {
|
public function getStdin(): ProcessOutputStream {
|
||||||
return $this->stdin;
|
return $this->handle->stdin;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -213,7 +224,7 @@ class Process {
|
|||||||
throw new StatusError("The process is not running");
|
throw new StatusError("The process is not running");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->stdout;
|
return $this->handle->stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -226,13 +237,6 @@ class Process {
|
|||||||
throw new StatusError("The process is not running");
|
throw new StatusError("The process is not running");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->stderr;
|
return $this->handle->stderr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(function () {
|
|
||||||
/** @noinspection PhpUndefinedClassInspection */
|
|
||||||
self::$processRunner = \strncasecmp(\PHP_OS, "WIN", 3) === 0
|
|
||||||
? new WindowsProcessRunner
|
|
||||||
: new PosixProcessRunner;
|
|
||||||
})->bindTo(null, Process::class)();
|
|
||||||
|
@ -17,6 +17,9 @@ class ProcessInputStream implements InputStream {
|
|||||||
/** @var bool */
|
/** @var bool */
|
||||||
private $shouldClose = false;
|
private $shouldClose = false;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $referenced = true;
|
||||||
|
|
||||||
/** @var ResourceInputStream */
|
/** @var ResourceInputStream */
|
||||||
private $resourceStream;
|
private $resourceStream;
|
||||||
|
|
||||||
@ -27,12 +30,18 @@ class ProcessInputStream implements InputStream {
|
|||||||
$resourceStreamPromise->onResolve(function ($error, $resourceStream) {
|
$resourceStreamPromise->onResolve(function ($error, $resourceStream) {
|
||||||
if ($error) {
|
if ($error) {
|
||||||
$this->error = new StreamException("Failed to launch process", 0, $error);
|
$this->error = new StreamException("Failed to launch process", 0, $error);
|
||||||
$this->initialRead->fail($this->error);
|
if ($this->initialRead) {
|
||||||
|
$this->initialRead->fail($this->error);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->resourceStream = $resourceStream;
|
$this->resourceStream = $resourceStream;
|
||||||
|
|
||||||
|
if (!$this->referenced) {
|
||||||
|
$this->resourceStream->unreference();
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->shouldClose) {
|
if ($this->shouldClose) {
|
||||||
$this->resourceStream->close();
|
$this->resourceStream->close();
|
||||||
}
|
}
|
||||||
@ -70,6 +79,22 @@ class ProcessInputStream implements InputStream {
|
|||||||
return $this->initialRead->promise();
|
return $this->initialRead->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function reference() {
|
||||||
|
$this->referenced = true;
|
||||||
|
|
||||||
|
if ($this->resourceStream) {
|
||||||
|
$this->resourceStream->reference();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unreference() {
|
||||||
|
$this->referenced = false;
|
||||||
|
|
||||||
|
if ($this->resourceStream) {
|
||||||
|
$this->resourceStream->unreference();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function close() {
|
public function close() {
|
||||||
$this->shouldClose = true;
|
$this->shouldClose = true;
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ use Amp\Promise;
|
|||||||
|
|
||||||
class ProcessOutputStream implements OutputStream {
|
class ProcessOutputStream implements OutputStream {
|
||||||
/** @var array */
|
/** @var array */
|
||||||
private $queuedWrites;
|
private $queuedWrites = [];
|
||||||
|
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
private $shouldClose = false;
|
private $shouldClose = false;
|
||||||
|
@ -1,17 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<phpunit
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/6.0/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true">
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
|
|
||||||
backupGlobals="false"
|
|
||||||
backupStaticAttributes="false"
|
|
||||||
bootstrap="vendor/autoload.php"
|
|
||||||
colors="true"
|
|
||||||
convertErrorsToExceptions="true"
|
|
||||||
convertNoticesToExceptions="true"
|
|
||||||
convertWarningsToExceptions="true"
|
|
||||||
processIsolation="false"
|
|
||||||
stopOnFailure="false"
|
|
||||||
>
|
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="Amp Process">
|
<testsuite name="Amp Process">
|
||||||
<directory>test</directory>
|
<directory>test</directory>
|
||||||
|
@ -7,7 +7,7 @@ use Amp\Process\Process;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class ProcessTest extends TestCase {
|
class ProcessTest extends TestCase {
|
||||||
const CMD_PROCESS = 'echo foo';
|
const CMD_PROCESS = \DIRECTORY_SEPARATOR === "\\" ? "cmd /c echo foo" : "echo foo";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Amp\Process\StatusError
|
* @expectedException \Amp\Process\StatusError
|
||||||
@ -22,7 +22,7 @@ class ProcessTest extends TestCase {
|
|||||||
|
|
||||||
public function testIsRunning() {
|
public function testIsRunning() {
|
||||||
Loop::run(function () {
|
Loop::run(function () {
|
||||||
$process = new Process("exit 42");
|
$process = new Process(\DIRECTORY_SEPARATOR === "\\" ? "cmd /c exit 42" : "exit 42");
|
||||||
$process->start();
|
$process->start();
|
||||||
$promise = $process->join();
|
$promise = $process->join();
|
||||||
|
|
||||||
@ -36,8 +36,9 @@ class ProcessTest extends TestCase {
|
|||||||
|
|
||||||
public function testExecuteResolvesToExitCode() {
|
public function testExecuteResolvesToExitCode() {
|
||||||
Loop::run(function () {
|
Loop::run(function () {
|
||||||
$process = new Process("exit 42");
|
$process = new Process(\DIRECTORY_SEPARATOR === "\\" ? "cmd /c exit 42" : "exit 42");
|
||||||
$process->start();
|
$process->start();
|
||||||
|
|
||||||
$code = yield $process->join();
|
$code = yield $process->join();
|
||||||
|
|
||||||
$this->assertSame(42, $code);
|
$this->assertSame(42, $code);
|
||||||
@ -54,7 +55,7 @@ class ProcessTest extends TestCase {
|
|||||||
$completed = false;
|
$completed = false;
|
||||||
$promise->onResolve(function () use (&$completed) { $completed = true; });
|
$promise->onResolve(function () use (&$completed) { $completed = true; });
|
||||||
$this->assertFalse($completed);
|
$this->assertFalse($completed);
|
||||||
$this->assertInternalType('int', $process->getPid());
|
$this->assertInternalType('int', yield $process->getPid());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ class ProcessTest extends TestCase {
|
|||||||
|
|
||||||
$process->kill();
|
$process->kill();
|
||||||
|
|
||||||
$code = yield $promise;
|
yield $promise;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user