3 namespace Drupal\memcache\Cache;
5 use Drupal\Core\Cache\CacheTagsChecksumInterface;
6 use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
7 use Drupal\memcache\Invalidator\TimestampInvalidatorInterface;
10 * Cache tags invalidations checksum implementation by timestamp invalidation.
12 class TimestampCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface {
15 * The timestamp invalidator object.
17 * @var \Drupal\memcache\Invalidator\TimestampInvalidatorInterface
19 protected $invalidator;
22 * Contains already loaded cache invalidations from the backend.
26 protected $tagCache = [];
29 * A list of tags that have already been invalidated in this request.
31 * Used to prevent the invalidation of the same cache tag multiple times.
35 protected $invalidatedTags = [];
38 * Constructs a TimestampCacheTagsChecksum object.
40 * @param \Drupal\memcache\Invalidator\TimestampInvalidatorInterface $invalidator
41 * The timestamp invalidator object.
43 public function __construct(TimestampInvalidatorInterface $invalidator) {
44 $this->invalidator = $invalidator;
50 public function invalidateTags(array $tags) {
51 foreach ($tags as $tag) {
52 // @todo Revisit this behavior and determine a better way to handle.
53 // Only invalidate tags once per request unless they are written again.
54 if (isset($this->invalidatedTags[$tag])) {
57 $this->invalidatedTags[$tag] = TRUE;
58 $this->tagCache[$tag] = $this->invalidator->invalidateTimestamp($tag);
65 public function getCurrentChecksum(array $tags) {
66 // @todo Revisit the invalidatedTags hack.
67 // Remove tags that were already invalidated during this request from the
68 // static caches so that another invalidation can occur later in the same
69 // request. Without that, written cache items would not be invalidated
71 foreach ($tags as $tag) {
72 unset($this->invalidatedTags[$tag]);
74 // Taking the minimum of the current timestamp and the checksum is used to
75 // ensure that items that are not valid yet are identified properly as not
76 // valid. The checksum will change continuously until the item is valid,
77 // at which point the checksum will match and freeze at that value.
78 return min($this->invalidator->getCurrentTimestamp(), $this->calculateChecksum($tags));
84 public function isValid($checksum, array $tags) {
86 // If there weren't any tags, the checksum should always be 0 or FALSE.
87 return $checksum == 0;
89 return $checksum == $this->calculateChecksum($tags);
93 * Calculates the current checksum for a given set of tags.
96 * The array of tags to calculate the checksum for.
99 * The calculated checksum.
101 protected function calculateChecksum(array $tags) {
103 $query_tags = array_diff($tags, array_keys($this->tagCache));
105 $backend_tags = $this->invalidator->getLastInvalidationTimestamps($query_tags);
106 $this->tagCache += $backend_tags;
107 $invalid = array_diff($query_tags, array_keys($backend_tags));
108 if (!empty($invalid)) {
109 // Invalidate any missing tags now. This is necessary because we cannot
110 // zero-optimize our tag list -- we can't tell the difference between
111 // a tag that has never been invalidated and a tag that was
112 // garbage-collected by the backend!
114 // This behavioral difference is the main change that allows us to use
115 // an unreliable backend to track cache tag invalidation.
117 // Invalidating the tag will cause it to start being tracked, so it can
118 // be matched against the checksums stored on items.
119 // All items cached after that point with the tag will end up with
120 // a valid checksum, and all items cached before that point with the tag
121 // will have an invalid checksum, because missing invalidations will
122 // keep moving forward in time as they get garbage collected and are
125 // The main effect of all this is that a tag going missing
126 // will automatically cause the cache items tagged with it to no longer
127 // have the correct checksum.
128 foreach ($invalid as $invalid_tag) {
129 $this->invalidator->invalidateTimestamp($invalid_tag);
134 // The checksum is equal to the *most recent* invalidation of an applicable
135 // tag. If the item is untagged, the checksum is always 0.
136 return max([0] + array_intersect_key($this->tagCache, array_flip($tags)));
142 public function reset() {
143 $this->tagCache = [];
144 $this->invalidatedTags = [];