You are here

CurlMultiHandler.php in Lockr 7.3

Namespace

GuzzleHttp\Handler

File

vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
View source
<?php

namespace GuzzleHttp\Handler;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;

/**
 * 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.
 */
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, $nextTime - $currentTime) * 1000000;
  }

}

Classes

Namesort descending Description
CurlMultiHandler Returns an asynchronous response using curl_multi_* functions.