5 * Contains \Drupal\memcache\MemcacheBackend.
8 namespace Drupal\memcache;
10 use Drupal\Core\Cache\Cache;
11 use Drupal\Core\Cache\CacheBackendInterface;
12 use Drupal\Core\Cache\CacheTagsChecksumInterface;
13 use Drupal\Core\Lock\LockBackendInterface;
16 * Defines a Memcache cache backend.
18 class MemcacheBackend implements CacheBackendInterface {
21 * The cache bin to use.
32 protected $lockCount = 0;
35 * The memcache wrapper object.
37 * @var \Drupal\memcache\DrupalMemcacheInterface
42 * The lock backend that should be used.
44 * @var \Drupal\Core\Lock\LockBackendInterface
49 * The Settings instance.
51 * @var \Drupal\memcache\DrupalMemcacheConfig
56 * The cache tags checksum provider.
58 * @var \Drupal\Core\Cache\CacheTagsChecksumInterface
60 protected $checksumProvider;
63 * Constructs a MemcacheBackend object.
64 *\Drupal\Core\Site\Settings
67 * @param \Drupal\memcache\DrupalMemcacheInterface $memcache
68 * The memcache object.
69 * @param \Drupal\Core\Lock\LockBackendInterface $lock
71 * @param \Drupal\memcache\DrupalMemcacheConfig $settings
72 * The settings instance.
73 * @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
74 * The cache tags checksum service.
76 public function __construct($bin, DrupalMemcacheInterface $memcache, LockBackendInterface $lock, DrupalMemcacheConfig $settings, CacheTagsChecksumInterface $checksum_provider) {
78 $this->memcache = $memcache;
80 $this->settings = $settings;
81 $this->checksumProvider = $checksum_provider;
87 public function get($cid, $allow_invalid = FALSE) {
89 $cache = $this->getMultiple($cids, $allow_invalid);
96 public function getMultiple(&$cids, $allow_invalid = FALSE) {
97 $keys = array_map(function($cid) {
98 return $this->key($cid);
101 $cache = $this->memcache->getMulti($keys);
104 foreach ($cache as $key => $result) {
105 if ($this->valid($result->cid, $result) || $allow_invalid) {
106 // Add it to the fetched items to diff later.
107 $fetched[$result->cid] = $result;
111 // Remove items from the referenced $cids array that we are returning,
112 // per comment in Drupal\Core\Cache\CacheBackendInterface::getMultiple().
113 $cids = array_diff($cids, array_keys($fetched));
119 * Determines if the cache item is valid.
121 * This also alters the valid property of the cache item itself.
125 * @param \stdClass $cache
130 protected function valid($cid, \stdClass $cache) {
131 $lock_key = "memcache_$cid:$this->bin";
132 $cache->valid = FALSE;
135 // Items that have expired are invalid.
136 if (isset($cache->expire) && ($cache->expire != CacheBackendInterface::CACHE_PERMANENT) && ($cache->expire <= REQUEST_TIME)) {
137 // If the memcache_stampede_protection variable is set, allow one
138 // process to rebuild the cache entry while serving expired content to
140 if ($this->settings->get('stampede_protection', FALSE)) {
141 // The process that acquires the lock will get a cache miss, all
142 // others will get a cache hit.
143 if (!$this->lock->acquire($lock_key, $this->settings->get('stampede_semaphore', 15))) {
144 $cache->valid = TRUE;
149 $cache->valid = TRUE;
152 // On cache misses, attempt to avoid stampedes when the
153 // memcache_stampede_protection variable is enabled.
155 if ($this->settings->get('stampede_protection', FALSE) && !$this->lock->acquire($lock_key, $this->settings->get('stampede_semaphore', 15))) {
156 // Prevent any single request from waiting more than three times due to
157 // stampede protection. By default this is a maximum total wait of 15
158 // seconds. This accounts for two possibilities - a cache and lock miss
159 // more than once for the same item. Or a cache and lock miss for
160 // different items during the same request.
161 // @todo: it would be better to base this on time waited rather than
162 // number of waits, but the lock API does not currently provide this
163 // information. Currently the limit will kick in for three waits of 25ms
164 // or three waits of 5000ms.
166 if ($this->lockCount <= $this->settings->get('stampede_wait_limit', 3)) {
167 // The memcache_stampede_semaphore variable was used in previous
168 // releases of memcache, but the max_wait variable was not, so by
169 // default divide the semaphore value by 3 (5 seconds).
170 $this->lock->wait($lock_key, $this->settings->get('stampede_wait_time', 5));
171 $cache = $this->get($cid);
176 // Check if invalidateTags() has been called with any of the items's tags.
177 if (!$this->checksumProvider->isValid($cache->checksum, $cache->tags)) {
178 $cache->valid = FALSE;
181 return (bool) $cache->valid;
187 public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = array()) {
188 assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)');
189 $tags = array_unique($tags);
190 // Sort the cache tags so that they are stored consistently.
193 // Create new cache object.
194 $cache = new \stdClass();
196 $cache->data = is_object($data) ? clone $data : $data;
197 $cache->created = round(microtime(TRUE), 3);
198 $cache->expire = $expire;
199 $cache->tags = $tags;
200 $cache->checksum = $this->checksumProvider->getCurrentChecksum($tags);
202 // Cache all items permanently. We handle expiration in our own logic.
203 return $this->memcache->set($this->key($cid), $cache);
209 public function setMultiple(array $items) {
210 foreach ($items as $cid => $item) {
212 'expire' => CacheBackendInterface::CACHE_PERMANENT,
216 $this->set($cid, $item['data'], $item['expire'], $item['tags']);
223 public function delete($cid) {
224 $this->memcache->delete($this->key($cid));
230 public function deleteMultiple(array $cids) {
231 foreach ($cids as $cid) {
232 $this->memcache->delete($this->key($cid));
239 public function deleteAll() {
240 // Invalidate all keys, as we can't actually delete all?
241 $this->invalidateAll();
247 public function invalidate($cid) {
248 $this->invalidateMultiple((array) $cid);
252 * Marks cache items as invalid.
254 * Invalid items may be returned in later calls to get(), if the
255 * $allow_invalid argument is TRUE.
258 * An array of cache IDs to invalidate.
260 * @see Drupal\Core\Cache\CacheBackendInterface::deleteMultiple()
261 * @see Drupal\Core\Cache\CacheBackendInterface::invalidate()
262 * @see Drupal\Core\Cache\CacheBackendInterface::invalidateTags()
263 * @see Drupal\Core\Cache\CacheBackendInterface::invalidateAll()
265 public function invalidateMultiple(array $cids) {
266 foreach ($cids as $cid) {
267 if ($item = $this->get($cid)) {
268 $item->expire = REQUEST_TIME - 1;
269 $this->memcache->set($this->key($cid), $item);
277 public function invalidateAll() {
278 $this->memcache->flush();
284 public function invalidateTags(array $tags) {
285 $this->checksumProvider->invalidateTags($tags);
291 public function removeBin() {
292 // Do nothing here too?
298 public function garbageCollection() {
299 // Memcache will invalidate items; That items memory allocation is then
300 // freed up and reused. So nothing needs to be deleted/cleaned up here.
306 public function isEmpty() {
307 // We do not know so err on the safe side? Not sure if we can know this?
312 * Returns a cache key prefixed with the current bin.
318 protected function key($cid) {
319 return $this->bin . '-' . $cid;