From 7cf91efd085dbac670d2bae282a588963fdcbbdf Mon Sep 17 00:00:00 2001 From: Niklas Keller Date: Sun, 17 Sep 2017 19:07:13 +0200 Subject: [PATCH] Refactor for full Windows compatibility --- bin/windows/ProcessWrapper.exe | Bin 75776 -> 75776 bytes bin/windows/ProcessWrapper64.exe | Bin 79872 -> 79872 bytes examples/basic-command.php | 6 ++- examples/ping-many.php | 15 ++++--- examples/watch-live.php | 6 ++- examples/write-command.php | 8 +++- lib/Internal/Posix/Handle.php | 3 ++ lib/Internal/Posix/Runner.php | 52 +++++++++++++++++------ lib/Internal/Windows/Handle.php | 6 ++- lib/Internal/Windows/Runner.php | 46 ++++++++++++-------- lib/Internal/Windows/SocketConnector.php | 29 ++++++------- lib/Process.php | 36 +++++++++------- lib/ProcessInputStream.php | 27 +++++++++++- lib/ProcessOutputStream.php | 2 +- phpunit.xml.dist | 14 +----- test/ProcessTest.php | 11 ++--- 16 files changed, 168 insertions(+), 93 deletions(-) diff --git a/bin/windows/ProcessWrapper.exe b/bin/windows/ProcessWrapper.exe index df431cc4cf85e929005791a0e4308baa8a379d34..e7a7b71fe95d752a2b963dd8731779c8752f9e65 100644 GIT binary patch delta 4785 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 NA3Xj{PO)%N_+K58dc6Pu delta 8000 zcmeHLi+5B-ny-7)O*#op=h2-9iAjfan!E|=BnCpD8#;P}9X3FCX+Sg&VPqs~;&M1( zN1He_*XCh~;>_wGqk?ban3|zwaKwD?xNwRtUE4z)$gX zrgkNl+}5pqn&U6WP)D$lzZpm81=ny-(*41O+&+3Wcpd*qEOqN%_ztr~+4J`uz-l4$~_;PSaFxk9JYVrF|F zh&@i$`!PZ6mrM?*Hv1c6CDXsNM$FFpGGS*NtifWJiItYz;?oULtcg!Q+s*1G>*cx} zQM*Mzf8K&!0N+TvQ2mh*bl9tZMP{|z0rFmX^)E99^C@EUNiwiPm8C)u8!Yn$&msH6 z9o=_-nB{!T@@)q+m01cl|Jq86oxU4YVffPzlEiuwSZ^&=AK{?c#vDE_nJzTW z9QPh(>KTw4jgqMYqCBm<447U!l8t??Zey7^@|@tSw3sE+Yi9bTF**BLC}Ng|P(@ayg+m00%p1b6SKf>5kQ&eM{cMwRPk{9n|C=QBe z8^`Pmhb$hIOijt)yxJ#GC&0Br1hGpprC_|hJP*oeT8T9Tn8$$EdAcMa{_B5nvOYXSWGhC>ZCa_iL)wxX>1*8 zthJ(TR|wkvCtCrr97iDnm5{bdreH;CmrOt1BXih;tB{;x>3Nq=e`zdzBPN}1zD1A3 zSolBQL%)lO<1GU;G&VDS9@IKZn_vcxRe6)(ZQde_+OKZXirDn(D5b+5wg%qjt*Dbs z=Mt24h?YzjAj<2|#|(S{JK6UPY{D+jsh3PwP>WT7-u}Dv7BRgUu&y4~akMvf-2H#S zI4Ji;$+4*#5hE}o7}*i1x?G7--ri}(X1`l; z{Tr!lPEc>xFw5l@cZUj>*I}lIM$aBuJCQjv{IlT0i8LjCHh-U;u86lLjY1b?UWC<; zaeuhPc$cr>*n}a~2|p|*tiBhnEp1HE)pLUeR06a6?^2!ckJ?|u$zz)hhC z62`i9f_IrET<|tnV&^$39djIW9rM^6T-D#Y#9QUnJ+>o)=a^I`UM?4pPxEOqT-E*M z#aC8m8Is7qi5kS^jyglq8q5bXlKhUQNrW`s5Ii68WfB5tS9$S?Rj12>jtYjUI7EMo z!>JXoyTtR@7uJn?mZSLkvm*`%n+2)!aPS!9WuoyrLsFB{VMo)X(WLRFOYFe%2Oh+o zBm~`-4hz!9LT3jemZY{RzOri1;b2lG9_kA^%=Su-$jcCs5tbLTrFJ~*Smapfs2U!g z1+Tk`O@5grR`9wky18r#)CI8Ja8(c5{N9505<>n9j>jV1Zph-fPHiiYjqWXY4^la% z<1N_1>aIZ84xz00-0I0JFdKVaKBuL^DIO2Fm9S>>1h|GWmQD2p+;B*EWn+kVrB8c+ zjUsglQU{u^I)qKyH3l0O0L|ic6`RenVY8`vcu|Mc*(^1D?kJX4_c${p@pP$(1?&!3 zewHLpiJqAWvqu3NN4tF5M)ZfUJR_Nb{@f%|NdhYeC22xIfbCm2$lIt3JcA*{!+kGK zDA><<_F@QPrYw^Ai%(+e2O&b|VlCuhPAvGe&(Vg&Qtl6QU*f}jN-T{}O683)bZSz% z=`7Ayv)^G54zc@elpHL-q)kZ=@GFYx@uYaZCYpYmR6Xh@ykGFn4j05vCDS(gkU5LH zPS=_XV(&Gg0=M`Iq4{K^)g?M{ZTj>FlIZ8=LVi;c)h8D&;aKD5BMo(u>0lHS{SD&< zaXvc3k<^$N^N)rkIX+0HUX+=0`Aj*u5TFtm#4h!<3!;86&K2t?Q8LYC{fPSQU=aP8 z4V~Io=%16*xOeGi$))@!IvPJF-~AJ!L?9P|3qy{CU0#H*!2U2~S6R&!mNkN7qcC@8bg(C*FdTHNaOTQ?W3q*8;J#Yn$63nuLJ}*ScXZ2 zwo9nHIH)M(DGKpS;S1bkomvZ8V+7Ktv+v>L5kuA;3p|YI7Ko(olyTg*^!=1leoH^S zk@7psImB9d%fEsknY!d+*S-)9QKm0QsXOVL7R%!|bJ0Ny8x$K{1pobNJTr~S9zOz> z_6b%NwVe#S+9IekO*`aCpgOgYU|6@E+L4gh{5rMKs??4ZBf{f;HrG+PG&eOmrEnbU z$E7h3)p6MD^dS;@wUKmbYC`-5`K{MBfDUX@DfR+SuEm0QLNa|$x26_yuh7$}B{@y` zI5Ai9*qqWjTDiWP*g)l8FznI(0hP^1K4c~toE>YmBAse|FpYo>{8MopO`gARCC1b+ z?(Z^Zc-i0g7#<3H=^?A_H*eeEvR>Tn-qL^}T*m$%3=#U*!T(?gFYh-*XkU}%2>qKt zB^Kggy0Bv}?j90Yf-Pew7G4zV5qGB<{MIK3wrAA{u?p`re<);*|~iUQ}pOGfo~AK zAn@{Lb=&+Cv>Nn^1xHBG9+35Y+FdewX-j&1)|YT@oD7Y4?0C8-)q)y0QhC4aq@%ru z`F%DrMo6agF#3IZEWbRAhGk6Qtzp!akqZ}DosrC~qkqgWPk9q(NWiZdyJUI;BHl`& zH~tatPu7}chyD*s54MZFYXA$`j|!W=Lk)>}vPa28iv54X0yfnsR;(+nnM@o!ZH1v@)y4 zV$WeA%DeEIcg~LuvjmU+zlym{V0ZZ4MY>0dAYhvg( zJa>y27L!$BOcP3nsVzG{ZWDAIhJ9ZfpMIiKn}Cj3yufK3OPjKDvjbK*PM5Azdl~}X zC*($H!0grj9i@gSPnWq)6n z7KdC)?Ui%(-jq`Pa{hZce^JiscNIsZz|Tjl(DIj@xS$t=%HZITOv@-TdFz!3>JBr>B>jn7-!{L>Br-WX=}xK_l|j(D8Ah) zcB|N{Vn9WHzM>bdqE*F-Do$5%zKTm!d{)JcD!!xQK?U8UML`5GnUt@1HYu=ZWX&#%vBAjQ_B<8@=y%^s@`I`jPduY3eT$w zTh#IkD$Y{*d=)3DIAquJ= zB{;=O8CP(_DLBgK^H=661w?iLPcc2{#k62Z_v5 zjJ;E)d!~&WmoSnf;PI1C8AcK?ZbDvcfAo1{PozJ*o{JA9@s%SJBSp|m{F=8qt`c+f0Dx+Lpj=YPe;rSQb~*YI3sK4w9< z5bS5ZhG?eJ_0H(%7bhq$=rwp1?7*A&B%9?py3c7f4OMl*+2+8x`cXB9UU$xOGbbDR z5?N5R!~5qBF@!t_PQV%HnsbLc1x^QU?J&I}#Ir7ND~56N!L@^{pLeJ2N^q@H2|1(a z$&OBb4>A^H*RaO>!Rf%Y594~l4LCL>kN(#M{L6~|>jGAm;{WXhT&bB=`$(I!NVdA=d-f;eL1v^6;2H{61j!!uDuF&O?Xc`$*RyGdzK$nTgl~ zWH*4DT>(t^QyeZ`$P7D?1jr1@XhP;gW~fDCd^qqsqx`rtYJKG{yl_jg}fN( zNJQj-%>JYQ1gQsd4=~saay^yi&WDv;-8B0yDtfUe$ z!>3evBk=XHSTXQhfv=>&I3@%CnNG-ytS#byDbibz@5Fz+`Pjt}j)32V3Wga-y^x1r z1nfe17U>#zhL@3avoU+%`&on}KyC$2$;Q0~xdQkkQW0c4-iQ^q0W&%cScG&5Jj0o8 zwNME>i|b+%DtduekeE_G(2}b(W_TTG8TbL<((&jV@-pDtNKAhR@OvaS*frpRJaFLs zz$zPFb&zF*%yisM5dN$db^>p!@{hnMymnZpMxY;QH{?se-~xrW0GAe`4tzcE5E9e> z3^?*$MP@h`iEZC};5Jp>3H>_11W({P9 zx?=3_Y?>T&_^)$Azu>qJ!g9GIJU9M>&+|A(kg>I;y~W?s)zZ^)rsaIgz|QbpvAaIV JDCACY{|5zZIAj0- diff --git a/examples/basic-command.php b/examples/basic-command.php index 09c2b72..86761e9 100644 --- a/examples/basic-command.php +++ b/examples/basic-command.php @@ -6,7 +6,11 @@ use Amp\ByteStream\Message; use Amp\Process\Process; 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()); diff --git a/examples/ping-many.php b/examples/ping-many.php index b618d4e..1b61775 100644 --- a/examples/ping-many.php +++ b/examples/ping-many.php @@ -6,19 +6,18 @@ use Amp\Process\Process; use Amp\Promise; 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(); - while ($chunk = yield $stream->read()) { + + while (null !== $chunk = yield $stream->read()) { echo $chunk; } $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 () { @@ -27,7 +26,9 @@ Amp\Loop::run(function () { $promises = []; 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); diff --git a/examples/watch-live.php b/examples/watch-live.php index 0d1b7b9..1214eab 100644 --- a/examples/watch-live.php +++ b/examples/watch-live.php @@ -5,10 +5,12 @@ include dirname(__DIR__) . "/vendor/autoload.php"; use Amp\Process\Process; 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(); - while ($chunk = yield $stream->read()) { + + while (null !== $chunk = yield $stream->read()) { echo $chunk; } diff --git a/examples/write-command.php b/examples/write-command.php index 373830b..96601b7 100644 --- a/examples/write-command.php +++ b/examples/write-command.php @@ -6,7 +6,13 @@ use Amp\ByteStream\Message; use Amp\Process\Process; 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 */ $process->getStdin()->write("abc\n"); diff --git a/lib/Internal/Posix/Handle.php b/lib/Internal/Posix/Handle.php index 1747c85..c1da8a4 100644 --- a/lib/Internal/Posix/Handle.php +++ b/lib/Internal/Posix/Handle.php @@ -24,6 +24,9 @@ final class Handle extends ProcessHandle { /** @var string */ public $extraDataPipeWatcher; + /** @var string */ + public $extraDataPipeStartWatcher; + /** @var int */ public $originalParentPid; } diff --git a/lib/Internal/Posix/Runner.php b/lib/Internal/Posix/Runner.php index ba6a61b..e2585e5 100644 --- a/lib/Internal/Posix/Runner.php +++ b/lib/Internal/Posix/Runner.php @@ -24,6 +24,7 @@ final class Runner implements ProcessRunner { public static function onProcessEndExtraDataPipeReadable($watcher, $stream, Handle $handle) { Loop::cancel($watcher); + $handle->extraDataPipeWatcher = null; $handle->status = ProcessStatus::ENDED; @@ -55,14 +56,13 @@ final class Runner implements ProcessRunner { $handle->status = ProcessStatus::RUNNING; $handle->pidDeferred->resolve((int) $pid); - $deferreds[0]->resolve(new ResourceOutputStream($pipes[0])); - $deferreds[1]->resolve(new ResourceInputStream($pipes[1])); - $deferreds[2]->resolve(new ResourceInputStream($pipes[2])); + $deferreds[0]->resolve($pipes[0]); + $deferreds[1]->resolve($pipes[1]); + $deferreds[2]->resolve($pipes[2]); - $handle->extraDataPipeWatcher = Loop::onReadable($stream, [self::class, 'onProcessEndExtraDataPipeReadable'], $handle); - Loop::unreference($handle->extraDataPipeWatcher); - - $handle->sockets->resolve(); + if ($handle->extraDataPipeWatcher !== null) { + Loop::enable($handle->extraDataPipeWatcher); + } } /** @inheritdoc */ @@ -104,10 +104,18 @@ final class Runner implements ProcessRunner { \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 ]]); + $handle->extraDataPipeWatcher = Loop::onReadable($pipes[3], [self::class, 'onProcessEndExtraDataPipeReadable'], $handle); + Loop::unreference($handle->extraDataPipeWatcher); + Loop::disable($handle->extraDataPipeWatcher); + return $handle; } @@ -124,13 +132,21 @@ final class Runner implements ProcessRunner { /** @inheritdoc */ public function kill(ProcessHandle $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. throw new ProcessException("Terminating process failed"); } - Loop::cancel($handle->extraDataPipeWatcher); - $handle->extraDataPipeWatcher = null; - $handle->status = ProcessStatus::ENDED; $handle->joinDeferred->fail(new ProcessException("The process was killed")); } @@ -147,11 +163,23 @@ final class Runner implements ProcessRunner { public function destroy(ProcessHandle $handle) { /** @var Handle $handle */ 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) { 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)) { diff --git a/lib/Internal/Windows/Handle.php b/lib/Internal/Windows/Handle.php index 196180f..58d5507 100644 --- a/lib/Internal/Windows/Handle.php +++ b/lib/Internal/Windows/Handle.php @@ -8,6 +8,7 @@ use Amp\Process\Internal\ProcessHandle; final class Handle extends ProcessHandle { public function __construct() { $this->joinDeferred = new Deferred; + $this->pidDeferred = new Deferred; } /** @var Deferred */ @@ -16,6 +17,9 @@ final class Handle extends ProcessHandle { /** @var string */ public $exitCodeWatcher; + /** @var bool */ + public $exitCodeRequested = false; + /** @var resource */ public $proc; @@ -26,7 +30,7 @@ final class Handle extends ProcessHandle { public $wrapperStderrPipe; /** @var resource[] */ - public $sockets; + public $sockets = []; /** @var Deferred[] */ public $stdioDeferreds; diff --git a/lib/Internal/Windows/Runner.php b/lib/Internal/Windows/Runner.php index 03e4681..ed00764 100644 --- a/lib/Internal/Windows/Runner.php +++ b/lib/Internal/Windows/Runner.php @@ -27,7 +27,7 @@ final class Runner implements ProcessRunner { private $socketConnector; - private function makeCommand(string $command, string $workingDirectory): string { + private function makeCommand(string $workingDirectory): string { $result = sprintf( '%s --address=%s --port=%d --token-size=%d', \escapeshellarg(self::WRAPPER_EXE_PATH), @@ -40,8 +40,6 @@ final class Runner implements ProcessRunner { $result .= ' ' . \escapeshellarg('--cwd=' . \rtrim($workingDirectory, '\\')); } - $result .= ' ' . $command; - return $result; } @@ -51,12 +49,14 @@ final class Runner implements ProcessRunner { /** @inheritdoc */ 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; $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)) { $message = "Could not start process"; @@ -74,16 +74,16 @@ final class Runner implements ProcessRunner { } $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[1]); - if ($written !== SocketConnector::SECURITY_TOKEN_SIZE * 6) { + if ($written !== SocketConnector::SECURITY_TOKEN_SIZE * 6 + \strlen($command) + 2) { \fclose($pipes[2]); \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); @@ -91,13 +91,16 @@ final class Runner implements ProcessRunner { $handle->wrapperStderrPipe = $pipes[2]; $stdinDeferred = new Deferred; - $handle->stdioDeferreds[] = new ProcessOutputStream($stdinDeferred->promise()); + $handle->stdioDeferreds[] = $stdinDeferred; + $handle->stdin = new ProcessOutputStream($stdinDeferred->promise()); $stdoutDeferred = new Deferred; - $handle->stdioDeferreds[] = new ProcessInputStream($stdoutDeferred->promise()); + $handle->stdioDeferreds[] = $stdoutDeferred; + $handle->stdout = new ProcessInputStream($stdoutDeferred->promise()); $stderrDeferred = new Deferred; - $handle->stdioDeferreds[] = new ProcessInputStream($stderrDeferred->promise()); + $handle->stdioDeferreds[] = $stderrDeferred; + $handle->stderr = new ProcessInputStream($stderrDeferred->promise()); $this->socketConnector->registerPendingProcess($handle); @@ -107,6 +110,8 @@ final class Runner implements ProcessRunner { /** @inheritdoc */ public function join(ProcessHandle $handle): Promise { /** @var Handle $handle */ + $handle->exitCodeRequested = true; + if ($handle->exitCodeWatcher !== null) { Loop::reference($handle->exitCodeWatcher); } @@ -122,8 +127,10 @@ final class Runner implements ProcessRunner { throw new ProcessException("Terminating process failed"); } - Loop::cancel($handle->exitCodeWatcher); - $handle->exitCodeWatcher = null; + if ($handle->exitCodeWatcher !== null) { + Loop::cancel($handle->exitCodeWatcher); + $handle->exitCodeWatcher = null; + } $handle->status = ProcessStatus::ENDED; $handle->joinDeferred->fail(new ProcessException("The process was killed")); @@ -137,12 +144,17 @@ final class Runner implements ProcessRunner { /** @inheritdoc */ public function destroy(ProcessHandle $handle) { /** @var Handle $handle */ - if ($handle->status < ProcessStatus::ENDED) { - $this->kill($handle); + if ($handle->status < ProcessStatus::ENDED && \is_resource($handle->proc)) { + try { + $this->kill($handle); + } catch (ProcessException $e) { + // ignore + } } if ($handle->exitCodeWatcher !== null) { Loop::cancel($handle->exitCodeWatcher); + $handle->exitCodeWatcher = null; } for ($i = 0; $i < 4; $i++) { @@ -151,8 +163,8 @@ final class Runner implements ProcessRunner { } } - \stream_get_contents($handle->wrapperStderrPipe); - \fclose($handle->wrapperStderrPipe); + @\stream_get_contents($handle->wrapperStderrPipe); + @\fclose($handle->wrapperStderrPipe); if (\is_resource($handle->proc)) { \proc_close($handle->proc); diff --git a/lib/Internal/Windows/SocketConnector.php b/lib/Internal/Windows/SocketConnector.php index 853e3bc..5f516eb 100644 --- a/lib/Internal/Windows/SocketConnector.php +++ b/lib/Internal/Windows/SocketConnector.php @@ -46,7 +46,7 @@ final class SocketConnector { 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)); \fclose($socket); @@ -84,10 +84,6 @@ final class SocketConnector { $data = \fread($socket, $length); if ($data === false || $data === '') { - \fclose($socket); - Loop::cancel($state->readWatcher); - Loop::cancel($state->timeoutWatcher); - unset($this->pendingClients[(int) $socket]); return null; } @@ -204,16 +200,15 @@ final class SocketConnector { } public function onReadableChildPid($watcher, $socket, Handle $handle) { - Loop::cancel($watcher); - Loop::cancel($handle->connectTimeoutWatcher); - $data = \fread($socket, 5); if ($data === false || $data === '') { - $this->failHandleStart($handle, 'Failed to read PID from wrapper: No data received'); return; } + Loop::cancel($watcher); + Loop::cancel($handle->connectTimeoutWatcher); + if (\strlen($data) !== 5) { $this->failHandleStart( $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->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]); } public function onReadableExitCode($watcher, $socket, Handle $handle) { - $handle->exitCodeWatcher = null; - Loop::cancel($watcher); - $data = \fread($socket, 5); if ($data === false || $data === '') { - $handle->status = ProcessStatus::ENDED; - $handle->joinDeferred->fail(new ProcessException('Failed to read exit code from wrapper: No data received')); return; } + $handle->exitCodeWatcher = null; + Loop::cancel($watcher); + if (\strlen($data) !== 5) { $handle->status = ProcessStatus::ENDED; $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; } @@ -317,6 +312,8 @@ final class SocketConnector { foreach ($handle->stdioDeferreds as $deferred) { $deferred->fail($error); } + + $handle->joinDeferred->fail($error); } public function registerPendingProcess(Handle $handle) { diff --git a/lib/Process.php b/lib/Process.php index fcfe91c..9e7cf4d 100644 --- a/lib/Process.php +++ b/lib/Process.php @@ -2,6 +2,7 @@ namespace Amp\Process; +use Amp\Loop; use Amp\Process\Internal\Posix\Runner as PosixProcessRunner; use Amp\Process\Internal\ProcessHandle; use Amp\Process\Internal\ProcessRunner; @@ -11,7 +12,7 @@ use Amp\Promise; class Process { /** @var ProcessRunner */ - private static $processRunner; + private $processRunner; /** @var string */ private $command; @@ -57,6 +58,16 @@ class Process { $this->cwd = $cwd; $this->env = $envVars; $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() { 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."); } - $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."); } - 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"); } - self::$processRunner->kill($this->handle); + $this->processRunner->kill($this->handle); } /** @@ -127,7 +138,7 @@ class Process { 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 */ 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"); } - return $this->stdout; + return $this->handle->stdout; } /** @@ -226,13 +237,6 @@ class Process { 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)(); diff --git a/lib/ProcessInputStream.php b/lib/ProcessInputStream.php index ec43048..92992b1 100644 --- a/lib/ProcessInputStream.php +++ b/lib/ProcessInputStream.php @@ -17,6 +17,9 @@ class ProcessInputStream implements InputStream { /** @var bool */ private $shouldClose = false; + /** @var bool */ + private $referenced = true; + /** @var ResourceInputStream */ private $resourceStream; @@ -27,12 +30,18 @@ class ProcessInputStream implements InputStream { $resourceStreamPromise->onResolve(function ($error, $resourceStream) { if ($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; } $this->resourceStream = $resourceStream; + if (!$this->referenced) { + $this->resourceStream->unreference(); + } + if ($this->shouldClose) { $this->resourceStream->close(); } @@ -70,6 +79,22 @@ class ProcessInputStream implements InputStream { 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() { $this->shouldClose = true; diff --git a/lib/ProcessOutputStream.php b/lib/ProcessOutputStream.php index de84be1..29d8f11 100644 --- a/lib/ProcessOutputStream.php +++ b/lib/ProcessOutputStream.php @@ -12,7 +12,7 @@ use Amp\Promise; class ProcessOutputStream implements OutputStream { /** @var array */ - private $queuedWrites; + private $queuedWrites = []; /** @var bool */ private $shouldClose = false; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4856517..e535180 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,17 +1,5 @@ - + test diff --git a/test/ProcessTest.php b/test/ProcessTest.php index ac914fc..2e1ff71 100644 --- a/test/ProcessTest.php +++ b/test/ProcessTest.php @@ -7,7 +7,7 @@ use Amp\Process\Process; use PHPUnit\Framework\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 @@ -22,7 +22,7 @@ class ProcessTest extends TestCase { public function testIsRunning() { Loop::run(function () { - $process = new Process("exit 42"); + $process = new Process(\DIRECTORY_SEPARATOR === "\\" ? "cmd /c exit 42" : "exit 42"); $process->start(); $promise = $process->join(); @@ -36,8 +36,9 @@ class ProcessTest extends TestCase { public function testExecuteResolvesToExitCode() { Loop::run(function () { - $process = new Process("exit 42"); + $process = new Process(\DIRECTORY_SEPARATOR === "\\" ? "cmd /c exit 42" : "exit 42"); $process->start(); + $code = yield $process->join(); $this->assertSame(42, $code); @@ -54,7 +55,7 @@ class ProcessTest extends TestCase { $completed = false; $promise->onResolve(function () use (&$completed) { $completed = true; }); $this->assertFalse($completed); - $this->assertInternalType('int', $process->getPid()); + $this->assertInternalType('int', yield $process->getPid()); }); } @@ -70,7 +71,7 @@ class ProcessTest extends TestCase { $process->kill(); - $code = yield $promise; + yield $promise; }); }