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);
                 }
             }
         }
     }
 }
Example #4
0
 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;
 }