You are here

class CurlMultiHandler in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php \GuzzleHttp\Handler\CurlMultiHandler

Returns an asynchronous response using curl_multi_* functions.

When using the CurlMultiHandler, custom curl options can be specified as an associative array of curl option constants mapping to values in the **curl** key of the provided request options.

@property resource $_mh Internal use only. Lazy loaded multi-handle.

Hierarchy

Expanded class hierarchy of CurlMultiHandler

1 file declares its use of CurlMultiHandler
functions.php in vendor/guzzlehttp/guzzle/src/functions.php

File

vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php, line 18

Namespace

GuzzleHttp\Handler
View source
class CurlMultiHandler {

  /** @var CurlFactoryInterface */
  private $factory;
  private $selectTimeout;
  private $active;
  private $handles = [];
  private $delays = [];

  /**
   * This handler accepts the following options:
   *
   * - handle_factory: An optional factory  used to create curl handles
   * - select_timeout: Optional timeout (in seconds) to block before timing
   *   out while selecting curl handles. Defaults to 1 second.
   *
   * @param array $options
   */
  public function __construct(array $options = []) {
    $this->factory = isset($options['handle_factory']) ? $options['handle_factory'] : new CurlFactory(50);
    $this->selectTimeout = isset($options['select_timeout']) ? $options['select_timeout'] : 1;
  }
  public function __get($name) {
    if ($name === '_mh') {
      return $this->_mh = curl_multi_init();
    }
    throw new \BadMethodCallException();
  }
  public function __destruct() {
    if (isset($this->_mh)) {
      curl_multi_close($this->_mh);
      unset($this->_mh);
    }
  }
  public function __invoke(RequestInterface $request, array $options) {
    $easy = $this->factory
      ->create($request, $options);
    $id = (int) $easy->handle;
    $promise = new Promise([
      $this,
      'execute',
    ], function () use ($id) {
      return $this
        ->cancel($id);
    });
    $this
      ->addRequest([
      'easy' => $easy,
      'deferred' => $promise,
    ]);
    return $promise;
  }

  /**
   * Ticks the curl event loop.
   */
  public function tick() {

    // Add any delayed handles if needed.
    if ($this->delays) {
      $currentTime = microtime(true);
      foreach ($this->delays as $id => $delay) {
        if ($currentTime >= $delay) {
          unset($this->delays[$id]);
          curl_multi_add_handle($this->_mh, $this->handles[$id]['easy']->handle);
        }
      }
    }

    // Step through the task queue which may add additional requests.
    P\queue()
      ->run();
    if ($this->active && curl_multi_select($this->_mh, $this->selectTimeout) === -1) {

      // Perform a usleep if a select returns -1.
      // See: https://bugs.php.net/bug.php?id=61141
      usleep(250);
    }
    while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM) {
    }
    $this
      ->processMessages();
  }

  /**
   * Runs until all outstanding connections have completed.
   */
  public function execute() {
    $queue = P\queue();
    while ($this->handles || !$queue
      ->isEmpty()) {

      // If there are no transfers, then sleep for the next delay
      if (!$this->active && $this->delays) {
        usleep($this
          ->timeToNext());
      }
      $this
        ->tick();
    }
  }
  private function addRequest(array $entry) {
    $easy = $entry['easy'];
    $id = (int) $easy->handle;
    $this->handles[$id] = $entry;
    if (empty($easy->options['delay'])) {
      curl_multi_add_handle($this->_mh, $easy->handle);
    }
    else {
      $this->delays[$id] = microtime(true) + $easy->options['delay'] / 1000;
    }
  }

  /**
   * Cancels a handle from sending and removes references to it.
   *
   * @param int $id Handle ID to cancel and remove.
   *
   * @return bool True on success, false on failure.
   */
  private function cancel($id) {

    // Cannot cancel if it has been processed.
    if (!isset($this->handles[$id])) {
      return false;
    }
    $handle = $this->handles[$id]['easy']->handle;
    unset($this->delays[$id], $this->handles[$id]);
    curl_multi_remove_handle($this->_mh, $handle);
    curl_close($handle);
    return true;
  }
  private function processMessages() {
    while ($done = curl_multi_info_read($this->_mh)) {
      $id = (int) $done['handle'];
      curl_multi_remove_handle($this->_mh, $done['handle']);
      if (!isset($this->handles[$id])) {

        // Probably was cancelled.
        continue;
      }
      $entry = $this->handles[$id];
      unset($this->handles[$id], $this->delays[$id]);
      $entry['easy']->errno = $done['result'];
      $entry['deferred']
        ->resolve(CurlFactory::finish($this, $entry['easy'], $this->factory));
    }
  }
  private function timeToNext() {
    $currentTime = microtime(true);
    $nextTime = PHP_INT_MAX;
    foreach ($this->delays as $time) {
      if ($time < $nextTime) {
        $nextTime = $time;
      }
    }
    return max(0, $currentTime - $nextTime);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
CurlMultiHandler::$active private property
CurlMultiHandler::$delays private property
CurlMultiHandler::$factory private property @var CurlFactoryInterface
CurlMultiHandler::$handles private property
CurlMultiHandler::$selectTimeout private property
CurlMultiHandler::addRequest private function
CurlMultiHandler::cancel private function Cancels a handle from sending and removes references to it.
CurlMultiHandler::execute public function Runs until all outstanding connections have completed.
CurlMultiHandler::processMessages private function
CurlMultiHandler::tick public function Ticks the curl event loop.
CurlMultiHandler::timeToNext private function
CurlMultiHandler::__construct public function This handler accepts the following options:
CurlMultiHandler::__destruct public function
CurlMultiHandler::__get public function
CurlMultiHandler::__invoke public function