<?php
-/**
- * @file
- * Contains \Drupal\memcache\MemcacheBackend.
- */
-
namespace Drupal\memcache;
-use Drupal\Core\Cache\Cache;
+use Drupal\Component\Assertion\Inspector;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheTagsChecksumInterface;
-use Drupal\Core\Lock\LockBackendInterface;
+use Drupal\memcache\Invalidator\TimestampInvalidatorInterface;
/**
* Defines a Memcache cache backend.
protected $bin;
/**
- * The lock count.
+ * The (micro)time the bin was last deleted.
*
- * @var int
+ * @var float
*/
- protected $lockCount = 0;
+ protected $lastBinDeletionTime;
/**
* The memcache wrapper object.
protected $memcache;
/**
- * The lock backend that should be used.
- *
- * @var \Drupal\Core\Lock\LockBackendInterface
- */
- protected $lock;
-
- /**
- * The Settings instance.
+ * The cache tags checksum provider.
*
- * @var \Drupal\memcache\DrupalMemcacheConfig
+ * @var \Drupal\Core\Cache\CacheTagsChecksumInterface|\Drupal\Core\Cache\CacheTagsInvalidatorInterface
*/
- protected $settings;
+ protected $checksumProvider;
/**
- * The cache tags checksum provider.
+ * The timestamp invalidation provider.
*
- * @var \Drupal\Core\Cache\CacheTagsChecksumInterface
+ * @var \Drupal\memcache\Invalidator\TimestampInvalidatorInterface
*/
- protected $checksumProvider;
+ protected $timestampInvalidator;
/**
* 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\memcache\DrupalMemcacheConfig $settings
- * The settings instance.
* @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
* The cache tags checksum service.
+ * @param \Drupal\memcache\Invalidator\TimestampInvalidatorInterface $timestamp_invalidator
+ * The timestamp invalidation provider.
*/
- public function __construct($bin, DrupalMemcacheInterface $memcache, LockBackendInterface $lock, DrupalMemcacheConfig $settings, CacheTagsChecksumInterface $checksum_provider) {
+ public function __construct($bin, DrupalMemcacheInterface $memcache, CacheTagsChecksumInterface $checksum_provider, TimestampInvalidatorInterface $timestamp_invalidator) {
$this->bin = $bin;
$this->memcache = $memcache;
- $this->lock = $lock;
- $this->settings = $settings;
$this->checksumProvider = $checksum_provider;
+ $this->timestampInvalidator = $timestamp_invalidator;
+
+ $this->ensureBinDeletionTimeIsSet();
}
/**
* {@inheritdoc}
*/
public function get($cid, $allow_invalid = FALSE) {
- $cids = array($cid);
+ $cids = [$cid];
$cache = $this->getMultiple($cids, $allow_invalid);
return reset($cache);
}
* {@inheritdoc}
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
- $keys = array_map(function($cid) {
- return $this->key($cid);
- }, $cids);
-
- $cache = $this->memcache->getMulti($keys);
+ $cache = $this->memcache->getMulti($cids);
$fetched = [];
- foreach ($cache as $key => $result) {
+ foreach ($cache as $result) {
+ if (!$this->timeIsGreaterThanBinDeletionTime($result->created)) {
+ continue;
+ }
+
if ($this->valid($result->cid, $result) || $allow_invalid) {
// Add it to the fetched items to diff later.
$fetched[$result->cid] = $result;
* The cache item.
*
* @return bool
+ * TRUE if valid, FALSE otherwise.
*/
protected function valid($cid, \stdClass $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('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('stampede_semaphore', 15))) {
- $cache->valid = TRUE;
- }
- }
- }
- else {
- $cache->valid = TRUE;
- }
- }
- // On cache misses, attempt to avoid stampedes when the
- // memcache_stampede_protection variable is enabled.
- else {
- if ($this->settings->get('stampede_protection', FALSE) && !$this->lock->acquire($lock_key, $this->settings->get('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('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('stampede_wait_time', 5));
- $cache = $this->get($cid);
- }
- }
+ $cache->valid = TRUE;
+
+ // Items that have expired are invalid.
+ if ($cache->expire != CacheBackendInterface::CACHE_PERMANENT && $cache->expire <= REQUEST_TIME) {
+ $cache->valid = FALSE;
}
// Check if invalidateTags() has been called with any of the items's tags.
$cache->valid = FALSE;
}
- return (bool) $cache->valid;
+ return $cache->valid;
}
/**
* {@inheritdoc}
*/
- public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = array()) {
- assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)');
+ public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = []) {
+ assert(Inspector::assertAllStrings($tags));
+
+ $tags[] = "memcache:$this->bin";
$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->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($this->key($cid), $cache);
+ return $this->memcache->set($cid, $cache);
}
/**
*/
public function setMultiple(array $items) {
foreach ($items as $cid => $item) {
- $item += array(
+ $item += [
'expire' => CacheBackendInterface::CACHE_PERMANENT,
- 'tags' => array(),
- );
+ 'tags' => [],
+ ];
$this->set($cid, $item['data'], $item['expire'], $item['tags']);
}
* {@inheritdoc}
*/
public function delete($cid) {
- $this->memcache->delete($this->key($cid));
+ $this->memcache->delete($cid);
}
/**
*/
public function deleteMultiple(array $cids) {
foreach ($cids as $cid) {
- $this->memcache->delete($this->key($cid));
+ $this->memcache->delete($cid);
}
}
* {@inheritdoc}
*/
public function deleteAll() {
- // Invalidate all keys, as we can't actually delete all?
- $this->invalidateAll();
+ $this->lastBinDeletionTime = $this->timestampInvalidator->invalidateTimestamp($this->bin);
}
/**
* {@inheritdoc}
*/
public function invalidate($cid) {
- $this->invalidateMultiple((array) $cid);
+ $this->invalidateMultiple([$cid]);
}
/**
foreach ($cids as $cid) {
if ($item = $this->get($cid)) {
$item->expire = REQUEST_TIME - 1;
- $this->memcache->set($this->key($cid), $item);
+ $this->memcache->set($cid, $item);
}
}
}
* {@inheritdoc}
*/
public function invalidateAll() {
- $this->memcache->flush();
+ $this->invalidateTags(["memcache:$this->bin"]);
}
/**
* {@inheritdoc}
*/
public function removeBin() {
- // Do nothing here too?
+ $this->lastBinDeletionTime = $this->timestampInvalidator->invalidateTimestamp($this->bin);
}
/**
}
/**
- * (@inheritdoc)
+ * {@inheritdoc}
*/
public function isEmpty() {
// We do not know so err on the safe side? Not sure if we can know this?
}
/**
- * Returns a cache key prefixed with the current bin.
+ * Determines if a (micro)time is greater than the last bin deletion time.
*
- * @param string $cid
+ * @param float $item_microtime
+ * A given (micro)time.
+ *
+ * @internal
+ *
+ * @return bool
+ * TRUE if the (micro)time is greater than the last bin deletion time, FALSE
+ * otherwise.
+ */
+ protected function timeIsGreaterThanBinDeletionTime($item_microtime) {
+ $last_bin_deletion = $this->getBinLastDeletionTime();
+
+ // If there is time, assume FALSE as there is no previous deletion time
+ // to compare with.
+ if (!$last_bin_deletion) {
+ return FALSE;
+ }
+
+ return $item_microtime > $last_bin_deletion;
+ }
+
+ /**
+ * Gets the last invalidation time for the bin.
+ *
+ * @internal
*
- * @return string
+ * @return float
+ * The last invalidation timestamp of the tag.
*/
- protected function key($cid) {
- return $this->bin . '-' . $cid;
+ protected function getBinLastDeletionTime() {
+ if (!isset($this->lastBinDeletionTime)) {
+ $this->lastBinDeletionTime = $this->timestampInvalidator->getLastInvalidationTimestamp($this->bin);
+ }
+
+ return $this->lastBinDeletionTime;
+ }
+
+ /**
+ * Ensures a last bin deletion time has been set.
+ *
+ * @internal
+ */
+ protected function ensureBinDeletionTimeIsSet() {
+ if (!$this->getBinLastDeletionTime()) {
+ $this->lastBinDeletionTime = $this->timestampInvalidator->invalidateTimestamp($this->bin);
+ }
}
}