13b6da731136ec91fc050cc20a0da5ce7618629d
[yaffs-website] / web / modules / contrib / memcache / src / MemcacheBackend.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\memcache\MemcacheBackend.
6  */
7
8 namespace Drupal\memcache;
9
10 use Drupal\Core\Cache\Cache;
11 use Drupal\Core\Cache\CacheBackendInterface;
12 use Drupal\Core\Cache\CacheTagsChecksumInterface;
13 use Drupal\Core\Lock\LockBackendInterface;
14
15 /**
16  * Defines a Memcache cache backend.
17  */
18 class MemcacheBackend implements CacheBackendInterface {
19
20   /**
21    * The cache bin to use.
22    *
23    * @var string
24    */
25   protected $bin;
26
27   /**
28    * The lock count.
29    *
30    * @var int
31    */
32   protected $lockCount = 0;
33
34   /**
35    * The memcache wrapper object.
36    *
37    * @var \Drupal\memcache\DrupalMemcacheInterface
38    */
39   protected $memcache;
40
41   /**
42    * The lock backend that should be used.
43    *
44    * @var \Drupal\Core\Lock\LockBackendInterface
45    */
46   protected $lock;
47
48   /**
49    * The Settings instance.
50    *
51    * @var \Drupal\memcache\DrupalMemcacheConfig
52    */
53   protected $settings;
54
55   /**
56    * The cache tags checksum provider.
57    *
58    * @var \Drupal\Core\Cache\CacheTagsChecksumInterface
59    */
60   protected $checksumProvider;
61
62   /**
63    * Constructs a MemcacheBackend object.
64    *\Drupal\Core\Site\Settings
65    * @param string $bin
66    *   The bin name.
67    * @param \Drupal\memcache\DrupalMemcacheInterface $memcache
68    *   The memcache object.
69    * @param \Drupal\Core\Lock\LockBackendInterface $lock
70    *   The lock backend.
71    * @param \Drupal\memcache\DrupalMemcacheConfig $settings
72    *   The settings instance.
73    * @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
74    *   The cache tags checksum service.
75    */
76   public function __construct($bin, DrupalMemcacheInterface $memcache, LockBackendInterface $lock, DrupalMemcacheConfig $settings, CacheTagsChecksumInterface $checksum_provider) {
77     $this->bin = $bin;
78     $this->memcache = $memcache;
79     $this->lock = $lock;
80     $this->settings = $settings;
81     $this->checksumProvider = $checksum_provider;
82   }
83
84   /**
85    * {@inheritdoc}
86    */
87   public function get($cid, $allow_invalid = FALSE) {
88     $cids = array($cid);
89     $cache = $this->getMultiple($cids, $allow_invalid);
90     return reset($cache);
91   }
92
93   /**
94    * {@inheritdoc}
95    */
96   public function getMultiple(&$cids, $allow_invalid = FALSE) {
97     $keys = array_map(function($cid) {
98       return $this->key($cid);
99     }, $cids);
100
101     $cache = $this->memcache->getMulti($keys);
102     $fetched = [];
103
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;
108       }
109     }
110
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));
114
115     return $fetched;
116   }
117
118   /**
119    * Determines if the cache item is valid.
120    *
121    * This also alters the valid property of the cache item itself.
122    *
123    * @param string $cid
124    *   The cache ID.
125    * @param \stdClass $cache
126    *   The cache item.
127    *
128    * @return bool
129    */
130   protected function valid($cid, \stdClass $cache) {
131     $lock_key = "memcache_$cid:$this->bin";
132     $cache->valid = FALSE;
133
134     if ($cache) {
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
139         // the rest.
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;
145           }
146         }
147       }
148       else {
149         $cache->valid = TRUE;
150       }
151     }
152     // On cache misses, attempt to avoid stampedes when the
153     // memcache_stampede_protection variable is enabled.
154     else {
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.
165         $this->lockCount++;
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);
172         }
173       }
174     }
175
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;
179     }
180
181     return (bool) $cache->valid;
182   }
183
184   /**
185    * {@inheritdoc}
186    */
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.
191     sort($tags);
192
193     // Create new cache object.
194     $cache = new \stdClass();
195     $cache->cid = $cid;
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);
201
202     // Cache all items permanently. We handle expiration in our own logic.
203     return $this->memcache->set($this->key($cid), $cache);
204   }
205
206   /**
207    * {@inheritdoc}
208    */
209   public function setMultiple(array $items) {
210     foreach ($items as $cid => $item) {
211       $item += array(
212         'expire' => CacheBackendInterface::CACHE_PERMANENT,
213         'tags' => array(),
214       );
215
216       $this->set($cid, $item['data'], $item['expire'], $item['tags']);
217     }
218   }
219
220   /**
221    * {@inheritdoc}
222    */
223   public function delete($cid) {
224     $this->memcache->delete($this->key($cid));
225   }
226
227   /**
228    * {@inheritdoc}
229    */
230   public function deleteMultiple(array $cids) {
231     foreach ($cids as $cid) {
232       $this->memcache->delete($this->key($cid));
233     }
234   }
235
236   /**
237    * {@inheritdoc}
238    */
239   public function deleteAll() {
240     // Invalidate all keys, as we can't actually delete all?
241     $this->invalidateAll();
242   }
243
244   /**
245    * {@inheritdoc}
246    */
247   public function invalidate($cid) {
248     $this->invalidateMultiple((array) $cid);
249   }
250
251   /**
252    * Marks cache items as invalid.
253    *
254    * Invalid items may be returned in later calls to get(), if the
255    * $allow_invalid argument is TRUE.
256    *
257    * @param array $cids
258    *   An array of cache IDs to invalidate.
259    *
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()
264    */
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);
270       }
271     }
272   }
273
274   /**
275    * {@inheritdoc}
276    */
277   public function invalidateAll() {
278     $this->memcache->flush();
279   }
280
281   /**
282    * {@inheritdoc}
283    */
284   public function invalidateTags(array $tags) {
285     $this->checksumProvider->invalidateTags($tags);
286   }
287
288   /**
289    * {@inheritdoc}
290    */
291   public function removeBin() {
292     // Do nothing here too?
293   }
294
295   /**
296    * {@inheritdoc}
297    */
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.
301   }
302
303   /**
304    * (@inheritdoc)
305    */
306   public function isEmpty() {
307     // We do not know so err on the safe side? Not sure if we can know this?
308     return TRUE;
309   }
310
311   /**
312    * Returns a cache key prefixed with the current bin.
313    *
314    * @param string $cid
315    *
316    * @return string
317    */
318   protected function key($cid) {
319     return $this->bin . '-' . $cid;
320   }
321
322 }