You are here

MemcacheBackend.php in Zircon Profile 8

Same filename and directory in other branches
  1. 8.0 modules/memcache/src/MemcacheBackend.php

Namespace

Drupal\memcache

File

modules/memcache/src/MemcacheBackend.php
View source
<?php

/**
 * @file
 * Contains \Drupal\memcache\MemcacheBackend.
 */
namespace Drupal\memcache;

use Drupal\Core\Site\Settings;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheTagsChecksumInterface;
use Drupal\Core\Lock\LockBackendInterface;

/**
 * Defines a Memcache cache backend.
 */
class MemcacheBackend implements CacheBackendInterface {

  /**
   * The cache bin to use.
   *
   * @var string
   */
  protected $bin;

  /**
   * The lock count.
   *
   * @var int
   */
  protected $lockCount = 0;

  /**
   * The memcache wrapper object.
   *
   * @var \Drupal\memcache\DrupalMemcacheInterface
   */
  protected $memcache;

  /**
   * The lock backend that should be used.
   *
   * @var \Drupal\Core\Lock\LockBackendInterface
   */
  protected $lock;

  /**
   * The Settings instance.
   *
   * @var \Drupal\Core\Site\Settings
   */
  protected $settings;

  /**
   * The cache tags checksum provider.
   *
   * @var \Drupal\Core\Cache\CacheTagsChecksumInterface
   */
  protected $checksumProvider;

  /**
   * Constructs a MemcacheBackend object.
   *\Drupal\Core\Site\Settings
   * @param string $bin
   *   The bin name.
   * @param \Drupal\memcache\DrupalMemcacheInterface $memcache
   *   The memcache object.
   * @param \Drupal\Core\Lock\LockBackendInterface $lock
   *   The lock backend.
   * @param \Drupal\Core\Site\Settings $settings
   *   The settings instance.
   * @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
   *   The cache tags checksum service.
   */
  public function __construct($bin, DrupalMemcacheInterface $memcache, LockBackendInterface $lock, Settings $settings, CacheTagsChecksumInterface $checksum_provider) {
    $this->bin = $bin;
    $this->memcache = $memcache;
    $this->lock = $lock;
    $this->settings = $settings;
    $this->checksumProvider = $checksum_provider;
  }

  /**
   * {@inheritdoc}
   */
  public function get($cid, $allow_invalid = FALSE) {
    $cids = array(
      $cid,
    );
    $cache = $this
      ->getMultiple($cids, $allow_invalid);
    return reset($cache);
  }

  /**
   * {@inheritdoc}
   */
  public function getMultiple(&$cids, $allow_invalid = FALSE) {
    $cache = $this->memcache
      ->getMulti($cids);
    foreach ($cache as $cid => $result) {
      if (!$this
        ->valid($cid, $result) && !$allow_invalid) {

        // This object has expired, so don't return it.
        unset($cache[$cid]);
      }
    }

    // Remove items from the referenced $cids array that we are returning,
    // per comment in Drupal\Core\Cache\CacheBackendInterface::getMultiple().
    $cids = array_diff($cids, array_keys($cache));
    return $cache;
  }

  /**
   * {@inheritdoc}
   */
  protected function valid($cid, $cache) {
    $lock_key = "memcache_{$cid}:{$this->bin}";
    $cache->valid = FALSE;
    if ($cache) {

      // Items that have expired are invalid.
      if (isset($cache->expire) && $cache->expire != CacheBackendInterface::CACHE_PERMANENT && $cache->expire <= REQUEST_TIME) {

        // If the memcache_stampede_protection variable is set, allow one
        // process to rebuild the cache entry while serving expired content to
        // the rest.
        if ($this->settings
          ->get('memcache_stampede_protection', FALSE)) {

          // The process that acquires the lock will get a cache miss, all
          // others will get a cache hit.
          if (!$this->lock
            ->acquire($lock_key, $this->settings
            ->get('memcache_stampede_semaphore', 15))) {
            $cache->valid = TRUE;
          }
        }
      }
      else {
        $cache->valid = TRUE;
      }
    }
    else {
      if ($this->settings
        ->get('memcache_stampede_protection', FALSE) && !$this->lock
        ->acquire($lock_key, $this->settings
        ->get('memcache_stampede_semaphore', 15))) {

        // Prevent any single request from waiting more than three times due to
        // stampede protection. By default this is a maximum total wait of 15
        // seconds. This accounts for two possibilities - a cache and lock miss
        // more than once for the same item. Or a cache and lock miss for
        // different items during the same request.
        // @todo: it would be better to base this on time waited rather than
        // number of waits, but the lock API does not currently provide this
        // information. Currently the limit will kick in for three waits of 25ms
        // or three waits of 5000ms.
        $this->lockCount++;
        if ($this->lockCount <= $this->settings
          ->get('memcache_stampede_wait_limit', 3)) {

          // The memcache_stampede_semaphore variable was used in previous
          // releases of memcache, but the max_wait variable was not, so by
          // default divide the semaphore value by 3 (5 seconds).
          $this->lock
            ->wait($lock_key, $this->settings
            ->get('memcache_stampede_wait_time', 5));
          $cache = $this
            ->get($cid);
        }
      }
    }

    // Check if invalidateTags() has been called with any of the items's tags.
    if (!$this->checksumProvider
      ->isValid($cache->checksum, $cache->tags)) {
      $cache->valid = FALSE;
    }
    return (bool) $cache->valid;
  }

  /**
   * {@inheritdoc}
   */
  public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = array()) {
    Cache::validateTags($tags);
    $tags = array_unique($tags);

    // Sort the cache tags so that they are stored consistently.
    sort($tags);

    // Create new cache object.
    $cache = new \stdClass();
    $cache->cid = $cid;
    $cache->data = is_object($data) ? clone $data : $data;
    $cache->created = round(microtime(TRUE), 3);
    $cache->expire = $expire;
    $cache->tags = $tags;
    $cache->checksum = $this->checksumProvider
      ->getCurrentChecksum($tags);

    // Cache all items permanently. We handle expiration in our own logic.
    return $this->memcache
      ->set($cid, $cache);
  }

  /**
   * {@inheritdoc}
   */
  public function setMultiple(array $items) {
    foreach ($items as $cid => $item) {
      $item += array(
        'expire' => CacheBackendInterface::CACHE_PERMANENT,
        'tags' => array(),
      );
      $this
        ->set($cid, $item['data'], $item['expire'], $item['tags']);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function delete($cid) {
    $this->memcache
      ->delete($cid);
  }

  /**
   * {@inheritdoc}
   */
  public function deleteMultiple(array $cids) {
    foreach ($cids as $cid) {
      $this->memcache
        ->delete($cid);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function deleteAll() {

    // Invalidate all keys, as we can't actually delete all?
    $this
      ->invalidateAll();
  }

  /**
   * {@inheritdoc}
   */
  public function invalidate($cid) {
    $this
      ->invalidateMultiple((array) $cid);
  }

  /**
   * Marks cache items as invalid.
   *
   * Invalid items may be returned in later calls to get(), if the
   * $allow_invalid argument is TRUE.
   *
   * @param string $cids
   *   An array of cache IDs to invalidate.
   *
   * @see Drupal\Core\Cache\CacheBackendInterface::deleteMultiple()
   * @see Drupal\Core\Cache\CacheBackendInterface::invalidate()
   * @see Drupal\Core\Cache\CacheBackendInterface::invalidateTags()
   * @see Drupal\Core\Cache\CacheBackendInterface::invalidateAll()
   */
  public function invalidateMultiple(array $cids) {
    foreach ($cids as $cid) {
      if ($item = $this
        ->get($cid)) {
        $item->expire = REQUEST_TIME - 1;
        $this->memcache
          ->set($cid, $item);
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function invalidateAll() {
    $this->memcache
      ->flush();
  }

  /**
   * {@inheritdoc}
   */
  public function invalidateTags(array $tags) {
    $this->checksumProvider
      ->invalidateTags($tags);
  }

  /**
   * {@inheritdoc}
   */
  public function removeBin() {

    // Do nothing here too?
  }

  /**
   * {@inheritdoc}
   */
  public function garbageCollection() {

    // Memcache will invalidate items; That items memory allocation is then
    // freed up and reused. So nothing needs to be deleted/cleaned up here.
  }

  /**
   * (@inheritdoc)
   */
  public function isEmpty() {

    // We do not know so err on the safe side? Not sure if we can know this?
    return TRUE;
  }

}

Classes

Namesort descending Description
MemcacheBackend Defines a Memcache cache backend.