public function readAllInput() { $channel = $this->getIOChannel(); while ($channel->update()) { PhutilChannel::waitForAny(array($channel)); if (!$channel->isOpenForReading()) { break; } } return $channel->read(); }
protected function waitForGitClient() { $io_channel = $this->getIOChannel(); // If we don't wait for the client to close the connection, `git` will // consider it an early abort and fail. Sit around until Git is comfortable // that it really received all the data. while ($io_channel->isOpenForReading()) { $io_channel->update(); $this->getErrorChannel()->flush(); PhutilChannel::waitForAny(array($io_channel)); } }
public function run() { while ($this->clients) { PhutilChannel::waitForAny($this->clients); foreach ($this->clients as $key => $client) { if (!$client->update()) { // If the client has exited, remove it from the list of clients. // We still need to process any remaining buffered I/O. unset($this->clients[$key]); } while ($message = $client->read()) { $response = $this->handleMessage($message); if ($response) { $client->write($response); } } } } }
public function execute() { while (true) { if ($this->exec) { $iterator = new FutureIterator($this->exec); $iterator->setUpdateInterval(0.05); foreach ($iterator as $key => $future) { if ($future === null) { break; } $this->resolveFuture($key, $future); break; } } else { PhutilChannel::waitForAny(array($this->getMaster())); } $this->processInput(); } }
/** * Construct a socket channel from a socket or a socket pair. * * NOTE: This must be a //stream// socket from `stream_socket_client()` or * `stream_socket_server()` or similar, not a //plain// socket from * `socket_create()` or similar. * * @param socket Socket (stream socket, not plain socket). If only one * socket is provided, it is used for reading and writing. * @param socket? Optional write socket. * * @task construct */ public function __construct($read_socket, $write_socket = null) { parent::__construct(); foreach (array($read_socket, $write_socket) as $socket) { if (!$socket) { continue; } $ok = stream_set_blocking($socket, false); if (!$ok) { throw new Exception("Failed to set socket nonblocking!"); } } $this->readSocket = $read_socket; if ($write_socket) { $this->writeSocket = $write_socket; } else { $this->writeSocket = $read_socket; $this->isSingleSocket = true; } }
/** * Update one client, processing any commands it has sent us. We fully * process all commands we've received here before returning to the main * server loop. * * @param ArcanistHgClientChannel The client to update. * @param ArcanistHgServerChannel The Mercurial server. * * @task server */ private function updateClient(ArcanistHgClientChannel $client, ArcanistHgServerChannel $hg) { if (!$client->update()) { // Client has disconnected, don't bother proceeding. return false; } // Read a command from the client if one is available. Note that we stop // updating other clients or accepting new connections while processing a // command, since there isn't much we can do with them until the server // finishes executing this command. $message = $client->read(); if (!$message) { return true; } $this->log($client, '$ ' . $message[0] . ' ' . $message[1]); $t_start = microtime(true); // Forward the command to the server. $hg->write($message); while (true) { PhutilChannel::waitForAny(array($client, $hg)); if (!$client->update() || !$hg->update()) { // If either the client or server has exited, bail. return false; } $response = $hg->read(); if (!$response) { continue; } // Forward the response back to the client. $client->write($response); // If the response was on the 'r'esult channel, it indicates the end // of the command output. We can process the next command (if any // remain) or go back to accepting new connections and servicing // other clients. if ($response[0] == 'r') { // Update the client immediately to try to get the bytes on the wire // as quickly as possible. This gives us slightly more throughput. $client->update(); break; } } // Log the elapsed time. $t_end = microtime(true); $t = 1000000 * ($t_end - $t_start); $this->log($client, pht('< %sus', number_format($t, 0))); $this->idleSince = time(); return true; }
public function update() { $this->future->isReady(); return parent::update(); }
private function agentExpect(PhutilChannel $agent, $expect, $what) { $message = $agent->waitForMessage(); $this->assertEqual($expect, $message, $what); }
public function __construct(PhutilChannel $channel) { parent::__construct(); $this->channel = $channel; $this->didConstruct(); }
public function setReadBufferSize($size) { // NOTE: We may end up using 2x the buffer size here, one inside // ExecFuture and one inside the Channel. We could tune this eventually, but // it should be fine for now. parent::setReadBufferSize($size); $this->future->setReadBufferSize($size); return $this; }
protected function identifyRepository() { // NOTE: In SVN, we need to read the first few protocol frames before we // can determine which repository the user is trying to access. We're // going to peek at the data on the wire to identify the repository. $io_channel = $this->getIOChannel(); // Before the client will send us the first protocol frame, we need to send // it a connection frame with server capabilities. To figure out the // correct frame we're going to start `svnserve`, read the frame from it, // send it to the client, then kill the subprocess. // TODO: This is pretty inelegant and the protocol frame will change very // rarely. We could cache it if we can find a reasonable way to dirty the // cache. $command = csprintf('svnserve -t'); $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); $future = new ExecFuture('%C', $command); $exec_channel = new PhutilExecChannel($future); $exec_protocol = new DiffusionSubversionWireProtocol(); while (true) { PhutilChannel::waitForAny(array($exec_channel)); $exec_channel->update(); $exec_message = $exec_channel->read(); if ($exec_message !== null) { $messages = $exec_protocol->writeData($exec_message); if ($messages) { $message = head($messages); $raw = $message['raw']; // Write the greeting frame to the client. $io_channel->write($raw); // Kill the subprocess. $future->resolveKill(); break; } } if (!$exec_channel->isOpenForReading()) { throw new Exception(pht('%s subprocess exited before emitting a protocol frame.', 'svnserve')); } } $io_protocol = new DiffusionSubversionWireProtocol(); while (true) { PhutilChannel::waitForAny(array($io_channel)); $io_channel->update(); $in_message = $io_channel->read(); if ($in_message !== null) { $this->peekBuffer .= $in_message; if (strlen($this->peekBuffer) > 1024 * 1024) { throw new Exception(pht('Client transmitted more than 1MB of data without transmitting ' . 'a recognizable protocol frame.')); } $messages = $io_protocol->writeData($in_message); if ($messages) { $message = head($messages); $struct = $message['structure']; // This is the: // // ( version ( cap1 ... ) url ... ) // // The `url` allows us to identify the repository. $uri = $struct[2]['value']; $path = $this->getPathFromSubversionURI($uri); return $this->loadRepositoryWithPath($path, PhabricatorRepositoryType::REPOSITORY_TYPE_SVN); } } if (!$io_channel->isOpenForReading()) { throw new Exception(pht('Client closed connection before sending a complete protocol ' . 'frame.')); } // If the client has disconnected, kill the subprocess and bail. if (!$io_channel->isOpenForWriting()) { throw new Exception(pht('Client closed connection before receiving response.')); } } }
public function execute() { $command_channel = $this->commandChannel; $io_channel = $this->ioChannel; $error_channel = $this->errorChannel; if (!$command_channel) { throw new Exception('Set a command channel before calling execute()!'); } if (!$io_channel) { throw new Exception('Set an IO channel before calling execute()!'); } if (!$error_channel) { throw new Exception('Set an error channel before calling execute()!'); } $channels = array($command_channel, $io_channel, $error_channel); // We want to limit the amount of data we'll hold in memory for this // process. See T4241 for a discussion of this issue in general. $buffer_size = 1024 * 1024; // 1MB $io_channel->setReadBufferSize($buffer_size); $command_channel->setReadBufferSize($buffer_size); // TODO: This just makes us throw away stderr after the first 1MB, but we // don't currently have the support infrastructure to buffer it correctly. // It's difficult to imagine this causing problems in practice, though. $this->execFuture->getStderrSizeLimit($buffer_size); while (true) { PhutilChannel::waitForAny($channels); $io_channel->update(); $command_channel->update(); $error_channel->update(); // If any channel is blocked on the other end, wait for it to flush before // we continue reading. For example, if a user is running `git clone` on // a 1GB repository, the underlying `git-upload-pack` may // be able to produce data much more quickly than we can send it over // the network. If we don't throttle the reads, we may only send a few // MB over the I/O channel in the time it takes to read the entire 1GB off // the command channel. That leaves us with 1GB of data in memory. while ($command_channel->isOpen() && $io_channel->isOpenForWriting() && ($command_channel->getWriteBufferSize() >= $buffer_size || $io_channel->getWriteBufferSize() >= $buffer_size || $error_channel->getWriteBufferSize() >= $buffer_size)) { PhutilChannel::waitForActivity(array(), $channels); $io_channel->update(); $command_channel->update(); $error_channel->update(); } // If the subprocess has exited and we've read everything from it, // we're all done. $done = !$command_channel->isOpenForReading() && $command_channel->isReadBufferEmpty(); $in_message = $io_channel->read(); if ($in_message !== null) { $in_message = $this->willWriteData($in_message); if ($in_message !== null) { $command_channel->write($in_message); } } $out_message = $command_channel->read(); if ($out_message !== null) { $out_message = $this->willReadData($out_message); if ($out_message !== null) { $io_channel->write($out_message); } } // If we have nothing left on stdin, close stdin on the subprocess. if (!$io_channel->isOpenForReading()) { $command_channel->closeWriteChannel(); } if ($done) { break; } // If the client has disconnected, kill the subprocess and bail. if (!$io_channel->isOpenForWriting()) { $this->execFuture->setStdoutSizeLimit(0)->setStderrSizeLimit(0)->setReadBufferSize(null)->resolveKill(); break; } } list($err) = $this->execFuture->setStdoutSizeLimit(0)->setStderrSizeLimit(0)->setReadBufferSize(null)->resolve(); return $err; }