Chris@0: consistentBackend = $consistent_backend; Chris@0: $this->fastBackend = $fast_backend; Chris@0: $this->bin = 'cache_' . $bin; Chris@0: $this->lastWriteTimestamp = NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function get($cid, $allow_invalid = FALSE) { Chris@0: $cids = [$cid]; Chris@0: $cache = $this->getMultiple($cids, $allow_invalid); Chris@0: return reset($cache); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getMultiple(&$cids, $allow_invalid = FALSE) { Chris@0: $cids_copy = $cids; Chris@0: $cache = []; Chris@0: Chris@0: // If we can determine the time at which the last write to the consistent Chris@0: // backend occurred (we might not be able to if it has been recently Chris@0: // flushed/restarted), then we can use that to validate items from the fast Chris@0: // backend, so try to get those first. Otherwise, we can't assume that Chris@0: // anything in the fast backend is valid, so don't even bother fetching Chris@0: // from there. Chris@0: $last_write_timestamp = $this->getLastWriteTimestamp(); Chris@0: if ($last_write_timestamp) { Chris@0: // Items in the fast backend might be invalid based on their timestamp, Chris@0: // but we can't check the timestamp prior to getting the item, which Chris@0: // includes unserializing it. However, unserializing an invalid item can Chris@0: // throw an exception. For example, a __wakeup() implementation that Chris@0: // receives object properties containing references to code or data that Chris@0: // no longer exists in the application's current state. Chris@0: // Chris@0: // Unserializing invalid data, whether it throws an exception or not, is Chris@0: // a waste of time, but we only incur it while a cache invalidation has Chris@0: // not yet finished propagating to all the fast backend instances. Chris@0: // Chris@0: // Most cache backend implementations should not wrap their internal Chris@0: // get() implementations with a try/catch, because they have no reason to Chris@0: // assume that their data is invalid, and doing so would mask Chris@0: // unserialization errors of valid data. We do so here, only because the Chris@0: // fast backend is non-authoritative, and after discarding its Chris@0: // exceptions, we proceed to check the consistent (authoritative) backend Chris@0: // and allow exceptions from that to bubble up. Chris@0: try { Chris@0: $items = $this->fastBackend->getMultiple($cids, $allow_invalid); Chris@0: } Chris@0: catch (\Exception $e) { Chris@0: $cids = $cids_copy; Chris@0: $items = []; Chris@0: } Chris@0: Chris@0: // Even if items were successfully fetched from the fast backend, they Chris@0: // are potentially invalid if older than the last time the bin was Chris@0: // written to in the consistent backend, so only keep ones that aren't. Chris@0: foreach ($items as $item) { Chris@0: if ($item->created < $last_write_timestamp) { Chris@0: $cids[array_search($item->cid, $cids_copy)] = $item->cid; Chris@0: } Chris@0: else { Chris@0: $cache[$item->cid] = $item; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: // If there were any cache entries that were not available in the fast Chris@0: // backend, retrieve them from the consistent backend and store them in the Chris@0: // fast one. Chris@0: if ($cids) { Chris@0: foreach ($this->consistentBackend->getMultiple($cids, $allow_invalid) as $item) { Chris@0: $cache[$item->cid] = $item; Chris@0: // Don't write the cache tags to the fast backend as any cache tag Chris@0: // invalidation results in an invalidation of the whole fast backend. Chris@0: $this->fastBackend->set($item->cid, $item->data, $item->expire); Chris@0: } Chris@0: } Chris@0: Chris@0: return $cache; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) { Chris@0: $this->consistentBackend->set($cid, $data, $expire, $tags); Chris@0: $this->markAsOutdated(); Chris@0: // Don't write the cache tags to the fast backend as any cache tag Chris@0: // invalidation results in an invalidation of the whole fast backend. Chris@0: $this->fastBackend->set($cid, $data, $expire); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setMultiple(array $items) { Chris@0: $this->consistentBackend->setMultiple($items); Chris@0: $this->markAsOutdated(); Chris@0: // Don't write the cache tags to the fast backend as any cache tag Chris@0: // invalidation results in an invalidation of the whole fast backend. Chris@0: foreach ($items as &$item) { Chris@0: unset($item['tags']); Chris@0: } Chris@0: $this->fastBackend->setMultiple($items); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function delete($cid) { Chris@0: $this->consistentBackend->deleteMultiple([$cid]); Chris@0: $this->markAsOutdated(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function deleteMultiple(array $cids) { Chris@0: $this->consistentBackend->deleteMultiple($cids); Chris@0: $this->markAsOutdated(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function deleteAll() { Chris@0: $this->consistentBackend->deleteAll(); Chris@0: $this->markAsOutdated(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function invalidate($cid) { Chris@0: $this->invalidateMultiple([$cid]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function invalidateMultiple(array $cids) { Chris@0: $this->consistentBackend->invalidateMultiple($cids); Chris@0: $this->markAsOutdated(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function invalidateTags(array $tags) { Chris@0: if ($this->consistentBackend instanceof CacheTagsInvalidatorInterface) { Chris@0: $this->consistentBackend->invalidateTags($tags); Chris@0: } Chris@0: $this->markAsOutdated(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function invalidateAll() { Chris@0: $this->consistentBackend->invalidateAll(); Chris@0: $this->markAsOutdated(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function garbageCollection() { Chris@0: $this->consistentBackend->garbageCollection(); Chris@0: $this->fastBackend->garbageCollection(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function removeBin() { Chris@0: $this->consistentBackend->removeBin(); Chris@0: $this->fastBackend->removeBin(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @todo Document in https://www.drupal.org/node/2311945. Chris@0: */ Chris@0: public function reset() { Chris@0: $this->lastWriteTimestamp = NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the last write timestamp. Chris@0: */ Chris@0: protected function getLastWriteTimestamp() { Chris@0: if ($this->lastWriteTimestamp === NULL) { Chris@0: $cache = $this->consistentBackend->get(self::LAST_WRITE_TIMESTAMP_PREFIX . $this->bin); Chris@0: $this->lastWriteTimestamp = $cache ? $cache->data : 0; Chris@0: } Chris@0: return $this->lastWriteTimestamp; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Marks the fast cache bin as outdated because of a write. Chris@0: */ Chris@0: protected function markAsOutdated() { Chris@0: // Clocks on a single server can drift. Multiple servers may have slightly Chris@0: // differing opinions about the current time. Given that, do not assume Chris@0: // 'now' on this server is always later than our stored timestamp. Chris@0: // Also add 1 millisecond, to ensure that caches written earlier in the same Chris@0: // millisecond are invalidated. It is possible that caches will be later in Chris@0: // the same millisecond and are then incorrectly invalidated, but that only Chris@0: // costs one additional roundtrip to the persistent cache. Chris@0: $now = round(microtime(TRUE) + .001, 3); Chris@0: if ($now > $this->getLastWriteTimestamp()) { Chris@0: $this->lastWriteTimestamp = $now; Chris@0: $this->consistentBackend->set(self::LAST_WRITE_TIMESTAMP_PREFIX . $this->bin, $this->lastWriteTimestamp); Chris@0: } Chris@0: } Chris@0: Chris@0: }