/** * We have to keep a static reference of eio event streams * because if we don't garbage collection can unload eio's * underlying pipe via a system close() call before it's * finished and generate a SIGPIPE. */ public function __construct() { if (empty(self::$stream)) { \eio_init(); self::$stream = \eio_get_event_stream(); } $this->callableDecrementor = function () { \call_user_func($this->incrementor, -1); }; $this->incrementor = function ($increment) { switch ($increment) { case 1: case -1: $this->pending += $increment; break; default: throw new FilesystemException("Invalid pending event increment; 1 or -1 required"); } if ($this->pending === 0) { \Amp\disable($this->watcher); } elseif ($this->pending === 1) { \Amp\enable($this->watcher); } }; $this->watcher = \Amp\onReadable(self::$stream, function () { while (\eio_npending()) { \eio_poll(); } }, $options = ["enable" => false]); }
public function __construct($db, $socket, string $connectionString) { $this->db = $db; $this->socket = $socket; $this->connectionString = $connectionString; $this->readWatcher = \Amp\onReadable($socket, [$this, "onReadable"]); $this->writeWatcher = \Amp\onReadable($socket, [$this, "onWritable"], ["enable" => FALSE]); }
protected function doStart(Console $console) : \Generator { $server = $this->bootstrapper->boot($this->logger, $console); (yield $server->start()); $this->server = $server; \Amp\onReadable($this->ipcSock, function ($watcherId) { \Amp\cancel($watcherId); yield from $this->stop(); }); }
protected function doStart(Console $console) : \Generator { // Shutdown the whole server in case we needed to stop during startup register_shutdown_function(function () use($console) { if (!$this->server) { // ensure a clean reactor for clean shutdown $reactor = \Amp\reactor(); \Amp\reactor(\Amp\driver()); \Amp\wait((new CommandClient((string) $console->getArg("config")))->stop()); \Amp\reactor($reactor); } }); $server = (yield from $this->bootstrapper->boot($this->logger, $console)); (yield $server->start()); $this->server = $server; \Amp\onReadable($this->ipcSock, function ($watcherId) { \Amp\cancel($watcherId); yield from $this->stop(); }); }
public function __construct() { if (empty(self::$isEioInitialized)) { \eio_init(); self::$isEioInitialized = true; } $this->stream = \eio_get_event_stream(); $this->callableDelReq = function () { $this->decrementPending(); }; $this->internalIncrement = function () { $this->incrementPending(); }; $this->internalDecrement = function () { $this->decrementPending(); }; $this->watcher = \Amp\onReadable($this->stream, function () { while (\eio_npending()) { \eio_poll(); } }, $options = ["enable" => false]); }
private function doWrite(HttpTunnelStruct $struct) { $socket = $struct->socket; $bytesToWrite = strlen($struct->writeBuffer); $bytesWritten = @fwrite($socket, $struct->writeBuffer); if ($bytesToWrite === $bytesWritten) { \Amp\cancel($struct->writeWatcher); $struct->parser = new Parser(Parser::MODE_RESPONSE); $struct->parser->enqueueResponseMethodMatch('CONNECT'); $struct->readWatcher = \Amp\onReadable($socket, function () use($struct) { $this->doRead($struct); }); } elseif ($bytesWritten > 0) { $struct->writeBuffer = substr($struct->writeBuffer, 0, $bytesWritten); $this->enableWriteWatcher($struct); } elseif ($this->isSocketDead($socket)) { \Amp\cancel($struct->writeWatcher); $struct->promisor->fail(new SocketException('Proxy CONNECT failed: socket went away while writing tunneling request')); } else { $this->enableWriteWatcher($struct); } }
private function accept($watcherId, $ipcServer) { if (!($ipcClient = @stream_socket_accept($ipcServer, $timeout = 0))) { return; } if ($this->stopPromisor) { @\fwrite($ipcClient, "\n"); } stream_set_blocking($ipcClient, false); $parser = $this->parser($ipcClient); $readWatcherId = \Amp\onReadable($ipcClient, function () use($parser) { $parser->next(); }); $this->ipcClients[$readWatcherId] = $ipcClient; $parser->send($readWatcherId); assert(!empty($this->spawnPromisors)); array_shift($this->spawnPromisors)->succeed(); }
public function reapClient($watcherId, InternalRequest $ireq) { $client = new Rfc6455Client(); $client->connectedAt = $this->now; $socket = $ireq->client->socket; $client->id = (int) $socket; $client->socket = $socket; $client->writeBuffer = $ireq->client->writeBuffer; $client->serverRefClearer = ($ireq->client->exporter)($ireq->client); $client->parser = $this->parser([$this, "onParse"], $options = ["cb_data" => $client]); $client->readWatcher = \Amp\onReadable($socket, [$this, "onReadable"], $options = ["enable" => true, "cb_data" => $client]); $client->writeWatcher = \Amp\onWritable($socket, [$this, "onWritable"], $options = ["enable" => $client->writeBuffer != "", "cb_data" => $client]); $this->clients[$client->id] = $client; $this->heartbeatTimeouts[$client->id] = $this->now + $this->heartbeatPeriod; resolve($this->tryAppOnOpen($client->id, $ireq->locals["aerys.websocket"])); return $client; }
public function do(InternalRequest $ireq) { $headers = yield; if ($headers[":status"] == 101) { $yield = (yield $headers); } else { return $headers; // detach if we don't want to establish websocket connection } while ($yield !== null) { $yield = (yield $yield); } \Amp\immediately(function () use($ireq) { $client = new Rfc6455Client(); $client->connectedAt = $this->now; $socket = $ireq->client->socket; $client->id = (int) $socket; $client->socket = $socket; $client->writeBuffer = $ireq->client->writeBuffer; $client->serverRefClearer = ($ireq->client->exporter)($ireq->client); $client->parser = $this->parser([$this, "onParse"], $options = ["cb_data" => $client]); $client->readWatcher = \Amp\onReadable($socket, [$this, "onReadable"], $options = ["enable" => true, "cb_data" => $client]); $client->writeWatcher = \Amp\onWritable($socket, [$this, "onWritable"], $options = ["enable" => $client->writeBuffer != "", "cb_data" => $client]); $this->clients[$client->id] = $client; $this->heartbeatTimeouts[$client->id] = $this->now + $this->heartbeatPeriod; resolve($this->tryAppOnOpen($client->id, $ireq->locals["aerys.websocket"])); }); }
private function accept($watcherId, $ipcServer) { if (!($ipcClient = @stream_socket_accept($ipcServer, $timeout = 0))) { return; } $clientId = (int) $ipcClient; $this->ipcClients[$clientId] = $ipcClient; stream_set_blocking($ipcClient, false); $parser = $this->parser($ipcClient); $onReadable = function () use($parser) { $parser->next(); }; $readWatcherId = \Amp\onReadable($ipcClient, $onReadable); $parser->send($readWatcherId); }
function __watchCrypto($method, $socket) { $result = \stream_socket_enable_crypto($socket, $enable = true, $method); if ($result === true) { return new Success($socket); } elseif ($result === false) { return new Failure(new CryptoException("Crypto negotiation failed: " . \error_get_last()["message"])); } else { $promisor = new \Amp\Deferred(); $cbData = [$promisor, $method]; \Amp\onReadable($socket, '\\Amp\\Socket\\__onCryptoWatchReadability', $options = ["cb_data" => $cbData]); return $promisor->promise(); } }
private function importClient($socket, string $peerName, HttpDriver $http) { $client = new Client(); $client->id = (int) $socket; $client->socket = $socket; $client->httpDriver = $http; $client->exporter = $this->exporter; $client->remainingKeepAlives = $this->options->maxKeepAliveRequests ?: null; $portStartPos = strrpos($peerName, ":"); $client->clientAddr = substr($peerName, 0, $portStartPos); $client->clientPort = substr($peerName, $portStartPos + 1); $serverName = stream_socket_get_name($socket, false); $portStartPos = strrpos($serverName, ":"); $client->serverAddr = substr($serverName, 0, $portStartPos); $client->serverPort = substr($serverName, $portStartPos + 1); $meta = stream_get_meta_data($socket); $client->cryptoInfo = $meta["crypto"] ?? []; $client->isEncrypted = (bool) $client->cryptoInfo; $client->requestParser = $http->parser($client); $client->requestParser->send(""); $client->readWatcher = \Amp\onReadable($socket, $this->onReadable, $options = ["enable" => true, "cb_data" => $client]); $client->writeWatcher = \Amp\onWritable($socket, $this->onWritable, $options = ["enable" => false, "cb_data" => $client]); $this->renewKeepAliveTimeout($client); $this->clients[$client->id] = $client; }
private function onCryptoCompletion(RequestCycle $cycle) { $parser = new Parser(Parser::MODE_RESPONSE); $parser->enqueueResponseMethodMatch($cycle->request->getMethod()); $futureResponse = $cycle->futureResponse; $parser->setAllOptions([Parser::OP_DISCARD_BODY => $cycle->options[self::OP_DISCARD_BODY], Parser::OP_BODY_SWAP_SIZE => $cycle->options[self::OP_BODY_SWAP_SIZE], Parser::OP_MAX_HEADER_BYTES => $cycle->options[self::OP_MAX_HEADER_BYTES], Parser::OP_MAX_BODY_BYTES => $cycle->options[self::OP_MAX_BODY_BYTES], Parser::OP_RETURN_BEFORE_ENTITY => true, Parser::OP_BODY_DATA_CALLBACK => function ($data) use($futureResponse) { $futureResponse->update([Notify::RESPONSE_BODY_DATA, $data]); }]); $cycle->parser = $parser; $cycle->readWatcher = \Amp\onReadable($cycle->socket, function () use($cycle) { $this->onReadableSocket($cycle); }); $timeout = $cycle->options[self::OP_MS_TRANSFER_TIMEOUT]; if ($timeout > 0) { $cycle->transferTimeoutWatcher = \Amp\once(function () use($cycle, $timeout) { $this->fail($cycle, new TimeoutException(sprintf('Allowed transfer timeout exceeded: %d ms', $timeout))); }, $timeout); } $streamMeta = stream_get_meta_data($cycle->socket); $cycle->futureResponse->update([Notify::HANDSHAKE_COMPLETE, $streamMeta]); $this->writeRequest($cycle); }
/** * @param int $buffer one of the self::BUFFER_* constants. Determines whether it will buffer the stdout and/or stderr data internally * @return Promise is updated with ["out", $data] or ["err", $data] for data received on stdout or stderr * That Promise will be resolved to a stdClass object with stdout, stderr (when $buffer is true), exit (holding exit code) and signal (only present when terminated via signal) properties */ public function exec($buffer = self::BUFFER_NONE) { if ($this->proc) { throw new \RuntimeException("Process was already launched"); } $fds = [["pipe", "r"], ["pipe", "w"], ["pipe", "w"]]; $cwd = isset($this->options["cwd"]) ? $this->options["cwd"] : NULL; $env = isset($this->options["env"]) ? $this->options["env"] : NULL; if (!($this->proc = @proc_open($this->cmd, $fds, $pipes, $cwd, $env, $this->options))) { return new Failure(new \RuntimeException("Failed executing command: {$this->cmd}")); } $this->writeBuf = ""; $this->writeTotal = 0; $this->writeCur = 0; stream_set_blocking($pipes[0], false); stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); $this->deferred = new Deferred(); $result = new \stdClass(); if ($buffer & self::BUFFER_STDOUT) { $result->stdout = ""; } if ($buffer & self::BUFFER_STDERR) { $result->stderr = ""; } $this->stdout = \Amp\onReadable($pipes[1], function ($watcher, $sock) use($result) { if ("" == ($data = @fread($sock, 8192))) { \Amp\cancel($watcher); \Amp\cancel($this->stdin); \Amp\immediately(function () use($result) { $status = proc_get_status($this->proc); assert($status["running"] === false); if ($status["signaled"]) { $result->signal = $status["termsig"]; } $result->exit = $status["exitcode"]; $this->proc = NULL; $this->deferred->succeed($result); foreach ($this->writeDeferreds as $deferred) { $deferred->fail(new \Exception("Write could not be completed, process finished")); } $this->writeDeferreds = []; }); } else { isset($result->stdout) && ($result->stdout .= $data); $this->deferred->update(["out", $data]); } }); $this->stderr = \Amp\onReadable($pipes[2], function ($watcher, $sock) use($result) { if ("" == ($data = @fread($sock, 8192))) { \Amp\cancel($watcher); } else { isset($result->stderr) && ($result->stderr .= $data); $this->deferred->update(["err", $data]); } }); $this->stdin = \Amp\onWritable($pipes[0], function ($watcher, $sock) { $this->writeCur += @fwrite($sock, $this->writeBuf); if ($this->writeCur == $this->writeTotal) { \Amp\disable($watcher); } while (($next = key($this->writeDeferreds)) !== null && $next <= $this->writeCur) { $this->writeDeferreds[$next]->succeed($this->writeCur); unset($this->writeDeferreds[$next]); } }, ["enable" => false]); return $this->deferred->promise(); }
public function reapClient($watcherId, InternalRequest $ireq) { $client = $ireq->client->socket; list($reverse, $externBuf) = $ireq->locals["aerys.reverse.socket"]; $serverRefClearer = ($ireq->client->exporter)($ireq->client)(); $internBuf = ""; $clientWrite = \Amp\onWritable($client, [self::class, "writer"], ["cb_data" => [&$externBuf, &$reverseRead, &$extern], "enable" => false, "keep_alive" => false]); $reverseWrite = \Amp\onWritable($reverse, [self::class, "writer"], ["cb_data" => [&$internBuf, &$clientRead, &$intern], "enable" => false, "keep_alive" => false]); $clientRead = \Amp\onReadable($client, [self::class, "reader"], ["cb_data" => [&$internBuf, $reverseWrite, &$intern], "keep_alive" => false]); $reverseRead = \Amp\onReadable($reverse, [self::class, "reader"], ["cb_data" => [&$externBuf, $clientWrite, &$intern], "keep_alive" => false]); }
public function reapClient($watcherId, InternalRequest $ireq) { $client = new Rfc6455Client(); $client->capacity = $this->maxBytesPerMinute; $client->connectedAt = $this->now; $socket = $ireq->client->socket; $client->id = (int) $socket; $client->socket = $socket; $client->writeBuffer = $ireq->client->writeBuffer; $client->serverRefClearer = ($ireq->client->exporter)($ireq->client); $client->parser = $this->parser([$this, "onParse"], $options = ["cb_data" => $client, "max_msg_size" => $this->maxMsgSize, "max_frame_size" => $this->maxFrameSize, "validate_utf8" => $this->validateUtf8, "text_only" => $this->textOnly, "threshold" => $this->autoFrameSize]); $client->readWatcher = \Amp\onReadable($socket, [$this, "onReadable"], $options = ["enable" => true, "cb_data" => $client]); $client->writeWatcher = \Amp\onWritable($socket, [$this, "onWritable"], $options = ["enable" => $client->writeBuffer != "", "cb_data" => $client]); $this->clients[$client->id] = $client; $this->heartbeatTimeouts[$client->id] = $this->now + $this->heartbeatPeriod; resolve($this->tryAppOnOpen($client->id, $ireq->locals["aerys.websocket"])); return $client; }
public function send($socket) { $deferred = new Deferred(); stream_set_blocking($socket, false); $data = $this->getRequest(); \Amp\onWritable($socket, function ($writer, $socket) use($deferred, &$data) { if ($bytes = fwrite($socket, $data)) { if ($bytes < \strlen($data)) { $data = substr($data, $bytes); return; } $size = 8192; \Amp\onReadable($socket, function ($reader, $socket) use($deferred, &$size) { /* make attention to not read too much data */ $data = stream_socket_recvfrom($socket, $size, STREAM_PEEK); if (false === ($pos = strpos($data, "\r\n\r\n"))) { if (\strlen($data) == $size) { $size *= 2; // unbounded?? } return; } \Amp\cancel($reader); $deferred->succeed($this->parseResponse(fread($socket, $pos + 4))); }); } else { $deferred->succeed(null); } \Amp\cancel($writer); }); return $deferred->promise(); }
private function loadNewServer($uri) { if (!($socket = @\stream_socket_client($uri, $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT))) { throw new ResolutionException(sprintf("Connection to %s failed: [Error #%d] %s", $uri, $errno, $errstr)); } \stream_set_blocking($socket, false); $id = (int) $socket; $server = new \StdClass(); $server->id = $id; $server->uri = $uri; $server->socket = $socket; $server->buffer = ""; $server->length = INF; $server->pendingRequests = []; $server->watcherId = \Amp\onReadable($socket, $this->makePrivateCallable("onReadable"), ["enable" => true, "keep_alive" => true]); $this->serverIdMap[$id] = $server; $this->serverUriMap[$uri] = $server; if (substr($uri, 0, 6) == "tcp://") { $promisor = new Deferred(); $server->connect = $promisor->promise(); $watcher = \Amp\onWritable($server->socket, static function ($watcher) use($server, $promisor, &$timer) { \Amp\cancel($watcher); \Amp\cancel($timer); unset($server->connect); $promisor->succeed(); }); $timer = \Amp\once(function () use($id, $promisor, $watcher, $uri) { \Amp\cancel($watcher); $this->unloadServer($id); $promisor->fail(new TimeoutException("Name resolution timed out, could not connect to server at {$uri}")); }, 5000); } return $server; }
/** * @param int $buffer one of the self::BUFFER_* constants. Determines whether it will buffer the stdout and/or stderr data internally * @return Promise is updated with ["out", $data] or ["err", $data] for data received on stdout or stderr * That Promise will be resolved to a stdClass object with stdout, stderr (when $buffer is true), exit (holding exit code) and signal (only present when terminated via signal) properties */ public function exec($buffer = self::BUFFER_NONE) { if ($this->proc) { throw new \RuntimeException("Process was already launched"); } $cwd = isset($this->options["cwd"]) ? $this->options["cwd"] : NULL; $env = isset($this->options["env"]) ? $this->options["env"] : NULL; if (stripos(PHP_OS, "WIN") !== 0) { $fds = [["pipe", "r"], ["pipe", "w"], ["pipe", "w"], ["pipe", "w"]]; $this->proc = @proc_open("{$this->cmd}; echo \$? >&3", $fds, $pipes, $cwd, $env, $this->options); } else { $options = $this->options; $options["bypass_shell"] = true; $fds = [["pipe", "r"], ["pipe", "w"], ["pipe", "w"]]; $this->proc = @proc_open($this->cmd, $fds, $pipes, $cwd, $env, $options); } if (!$this->proc) { return new Failure(new \RuntimeException("Failed executing command: {$this->cmd}")); } $this->writeBuf = ""; $this->writeTotal = 0; $this->writeCur = 0; stream_set_blocking($pipes[0], false); stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); if (isset($pipes[3])) { stream_set_blocking($pipes[3], false); $this->openPipes = 3; } else { $this->openPipes = 2; } $this->deferred = new Deferred(); $result = new \stdClass(); if ($buffer & self::BUFFER_STDOUT) { $result->stdout = ""; } if ($buffer & self::BUFFER_STDERR) { $result->stderr = ""; } $cleanup = function () use($result) { \Amp\cancel($this->stdin); $deferreds = $this->writeDeferreds; $this->writeDeferreds = []; $status = \proc_get_status($this->proc); if ($status["running"] === false && $status["signaled"]) { $result->signal = $status["termsig"]; $result->exit = $status["exitcode"]; } if (!isset($this->exit)) { $result->exit = proc_close($this->proc); } unset($this->proc); $this->deferred->succeed($result); foreach ($deferreds as $deferred) { $deferred->fail(new \Exception("Write could not be completed, process finished")); } }; $this->stdout = \Amp\onReadable($pipes[1], function ($watcher, $sock) use($result, $cleanup) { if ("" == ($data = @\fread($sock, 8192))) { \Amp\cancel($watcher); if (--$this->openPipes == 0) { \Amp\immediately($cleanup); } } else { if (isset($result->stdout)) { $result->stdout .= $data; } $this->deferred->update(["out", $data]); } }); $this->stderr = \Amp\onReadable($pipes[2], function ($watcher, $sock) use($result, $cleanup) { if ("" == ($data = @\fread($sock, 8192))) { \Amp\cancel($watcher); if (--$this->openPipes == 0) { \Amp\immediately($cleanup); } } else { if (isset($result->stderr)) { $result->stderr .= $data; } $this->deferred->update(["err", $data]); } }); $this->stdin = \Amp\onWritable($pipes[0], function ($watcher, $sock) { $this->writeCur += @\fwrite($sock, $this->writeBuf); if ($this->writeCur == $this->writeTotal) { \Amp\disable($watcher); } while (($next = key($this->writeDeferreds)) !== null && $next <= $this->writeCur) { $this->writeDeferreds[$next]->succeed($this->writeCur); unset($this->writeDeferreds[$next]); } }, ["enable" => false]); if (isset($pipes[3])) { $this->exit = \Amp\onReadable($pipes[3], function ($watcher, $sock) use($result, $cleanup) { stream_set_blocking($sock, true); // it should never matter, but just to be really 100% sure. $result->exit = (int) stream_get_contents($sock); \Amp\cancel($watcher); if (--$this->openPipes == 0) { \Amp\immediately($cleanup); } }); } return $this->deferred->promise(); }
function __loadNewServer($state, $uri) { if (!($socket = @\stream_socket_client($uri, $errno, $errstr))) { throw new ResolutionException(sprintf("Connection to %s failed: [Error #%d] %s", $uri, $errno, $errstr)); } \stream_set_blocking($socket, false); $id = (int) $socket; $server = new \StdClass(); $server->id = $id; $server->uri = $uri; $server->socket = $socket; $server->buffer = ""; $server->length = INF; $server->pendingRequests = []; $server->watcherId = \Amp\onReadable($socket, 'Amp\\Dns\\__onReadable', ["enable" => true, "keep_alive" => true, "cb_data" => $state]); $state->serverIdMap[$id] = $server; $state->serverUriMap[$uri] = $server; return $server; }
/** * @return Promise */ private function connect() { if ($this->promisor) { return $this->promisor->promise(); } // Already connected if (is_resource($this->socket)) { return new Success(); } $this->promisor = new \Amp\Deferred(); /** @var $socketPromise Promise */ $socketPromise = \Amp\Socket\connect($this->uri, ['timeout' => 1000]); $socketPromise->when(function ($error, $socket) { $promisor = $this->promisor; $this->promisor = null; $this->socket = $socket; $this->reader = \Amp\onReadable($this->socket, [$this, "onRead"]); $this->writer = \Amp\onWritable($this->socket, [$this, "onWrite"]); $promisor->succeed(); }); return $this->promisor->promise(); }
public function send($socket) { $deferred = new Deferred(); stream_set_blocking($socket, false); $data = $this->getRequest(); \Amp\onWritable($socket, function ($writer, $socket) use($deferred, &$data) { if ($bytes = fwrite($socket, $data)) { if ($bytes < \strlen($data)) { $data = substr($data, $bytes); return; } $data = ''; \Amp\onReadable($socket, function ($reader, $socket) use($deferred, &$data) { $data .= $bytes = fgets($socket); if ($bytes == '' || \strlen($bytes) > 32768) { \Amp\cancel($reader); $deferred->succeed(null); } elseif (substr($data, -4) == "\r\n\r\n") { \Amp\cancel($reader); $deferred->succeed($this->parseResponse($data)); } }); } else { $deferred->succeed(null); } \Amp\cancel($writer); }); return $deferred->promise(); }