| /* |
| * Copyright 2013 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // Author: jmarantz@google.com (Joshua Marantz) |
| |
| #include "pagespeed/kernel/cache/purge_set.h" |
| |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/base/time_util.h" |
| |
| namespace net_instaweb { |
| |
| PurgeSet::PurgeSet() |
| : global_invalidation_timestamp_ms_(kInitialTimestampMs), |
| last_invalidation_timestamp_ms_(0), |
| helper_(this), |
| lru_(new Lru(1, &helper_)) { // 1 byte max size till someone sets it. |
| } |
| |
| PurgeSet::PurgeSet(size_t max_size) |
| : global_invalidation_timestamp_ms_(kInitialTimestampMs), |
| last_invalidation_timestamp_ms_(0), |
| helper_(this), |
| lru_(new Lru(max_size, &helper_)) { |
| } |
| |
| PurgeSet::PurgeSet(const PurgeSet& src) |
| : global_invalidation_timestamp_ms_(kInitialTimestampMs), |
| last_invalidation_timestamp_ms_(0), |
| helper_(this), |
| lru_(new Lru(src.lru_->max_bytes_in_cache(), &helper_)) { |
| Merge(src); |
| } |
| |
| PurgeSet::~PurgeSet() { |
| } |
| |
| PurgeSet& PurgeSet::operator=(const PurgeSet& src) { |
| if (&src != this) { |
| Clear(); |
| lru_->set_max_bytes_in_cache(src.lru_->max_bytes_in_cache()); |
| Merge(src); |
| } |
| return *this; |
| } |
| |
| void PurgeSet::Clear() { |
| lru_->Clear(); |
| global_invalidation_timestamp_ms_ = kInitialTimestampMs; |
| } |
| |
| namespace { |
| |
| // Maintains temporary storage of keys and values for executing a merge. |
| // Note that the keys may be of non-trivial size and we'd prefer not |
| // to copy the keys from the merge source, but we'll need to copy the |
| // keys from this so we can clear it before re-inserting the data in |
| // merged order. |
| class MergeContext { |
| public: |
| MergeContext(int64 global_invalidation_timestamp_ms, |
| int64 this_num_elements, |
| int64 src_num_elements) |
| : global_invalidation_timestamp_ms_(global_invalidation_timestamp_ms) { |
| int total_size = this_num_elements + src_num_elements; |
| key_copies_.reserve(this_num_elements); |
| keys_.reserve(total_size); |
| values_.reserve(total_size); |
| } |
| |
| void AddPurgeCopyingKey(const GoogleString& key, int64 value) { |
| if (value > global_invalidation_timestamp_ms_) { |
| key_copies_.push_back(key); |
| keys_.push_back(&key_copies_.back()); |
| values_.push_back(value); |
| } |
| } |
| |
| void AddPurgeSharingKey(const GoogleString& key, int64 value) { |
| if (value > global_invalidation_timestamp_ms_) { |
| keys_.push_back(&key); |
| values_.push_back(value); |
| } |
| } |
| |
| int size() const { return keys_.size(); } |
| const GoogleString& key(int index) const { return *keys_[index]; } |
| int64 value(int index) const { return values_[index]; } |
| |
| private: |
| int64 global_invalidation_timestamp_ms_; |
| StringVector key_copies_; |
| ConstStringStarVector keys_; |
| std::vector<int64> values_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MergeContext); |
| }; |
| |
| } // namespace |
| |
| void PurgeSet::Merge(const PurgeSet& src) { |
| // Note that this can almost be implemented using simple ordered |
| // loop through src's list, putting values will into this. But that |
| // will get the ordering wrong, and thus get the wrong answer for |
| // PurgeSetTest.Merge. |
| // |
| // Note that ordering matters for evicting the oldest invalidation records. |
| // |
| // Note also that std::merge could do the right thing, except that we |
| // don't have an OutputIterator for our Lru object which must keep two |
| // data structures (list & map) synced. And it's a hard to provide |
| // all the STL output-iterator semantics needed for std::merge to work. |
| // We could use std::merge into the vector but note here how we treat |
| // the values from this differently: we need to copy the strings, whereas |
| // for src we can just use StringPiece. |
| // |
| // So instead we have a simple merge-sort into some temp arrays, |
| // clear the LRU cache, then re-populate it from the arrays. This |
| // is O(this->size + src.size()). We might be able to contrive |
| // something more sophisticated that's just O(src.size() + |
| // log(this->size())), but then we'd have to re-do the |
| // keep-2-data-structures-in-sync code in LRUCacheBase using two |
| // maps instead of a list and a map. |
| // |
| // However, the intended usage for this involves re-reading the entire |
| // contents from a file on any change in order to keep multiple processes |
| // in sync, so quibbling about an extra O(n) walk thorugh the in-memory |
| // data does not seem worthwhile. |
| global_invalidation_timestamp_ms_ = std::max( |
| global_invalidation_timestamp_ms_, |
| src.global_invalidation_timestamp_ms_); |
| MergeContext merge_context(global_invalidation_timestamp_ms_, |
| lru_->num_elements(), |
| src.lru_->num_elements()); |
| Lru::Iterator src_iter = src.lru_->Begin(), src_end = src.lru_->End(); |
| Lru::Iterator this_iter = lru_->Begin(), this_end = lru_->End(); |
| while ((src_iter != src_end) && (this_iter != this_end)) { |
| if (src_iter.Value() < this_iter.Value()) { |
| merge_context.AddPurgeSharingKey(src_iter.Key(), src_iter.Value()); |
| ++src_iter; |
| } else { |
| merge_context.AddPurgeCopyingKey(this_iter.Key(), this_iter.Value()); |
| ++this_iter; |
| } |
| } |
| |
| for (; src_iter != src_end; ++src_iter) { |
| merge_context.AddPurgeSharingKey(src_iter.Key(), src_iter.Value()); |
| } |
| for (; this_iter != this_end; ++this_iter) { |
| merge_context.AddPurgeCopyingKey(this_iter.Key(), this_iter.Value()); |
| } |
| |
| lru_->Clear(); |
| lru_->ClearStats(); |
| last_invalidation_timestamp_ms_ = global_invalidation_timestamp_ms_; |
| for (int i = 0, n = merge_context.size(); i < n; ++i) { |
| CHECK(Put(merge_context.key(i), merge_context.value(i))); |
| } |
| } |
| |
| bool PurgeSet::UpdateGlobalInvalidationTimestampMs(int64 timestamp_ms) { |
| if (!SanitizeTimestamp(×tamp_ms)) { |
| return false; |
| } |
| global_invalidation_timestamp_ms_ = |
| std::max(timestamp_ms, global_invalidation_timestamp_ms_); |
| return true; |
| } |
| |
| void PurgeSet::EvictNotify(int64 evicted_record_timestamp_ms) { |
| // We do not call UpdateGlobalInvalidationTimestampMs because we don't |
| // want to silently sanitize the global timestamp to the latest known |
| // timestamp. |
| DCHECK_GE(last_invalidation_timestamp_ms_, evicted_record_timestamp_ms); |
| global_invalidation_timestamp_ms_ = |
| std::max(evicted_record_timestamp_ms, global_invalidation_timestamp_ms_); |
| } |
| |
| bool PurgeSet::Put(const GoogleString& key, int64 timestamp_ms) { |
| if (!SanitizeTimestamp(×tamp_ms)) { |
| return true; |
| } |
| // Ignore invalidations of individual URLs predating the global |
| // invalidation timestamp. |
| if (timestamp_ms > global_invalidation_timestamp_ms_) { |
| lru_->Put(key, ×tamp_ms); |
| } |
| return true; |
| } |
| |
| bool PurgeSet::SanitizeTimestamp(int64* timestamp_ms) { |
| int64 amount_in_past_ms = last_invalidation_timestamp_ms_ - *timestamp_ms; |
| if (amount_in_past_ms <= 0) { |
| // Time is moving forward. All is well. |
| last_invalidation_timestamp_ms_ = *timestamp_ms; |
| } else if (amount_in_past_ms <= kClockSkewAllowanceMs) { |
| // A small clock-skew was detected. Simply clamp it to our last |
| // known good timestamp, and accept the fact that this may invalidate |
| // a bit into the future. |
| *timestamp_ms = last_invalidation_timestamp_ms_; |
| } else { |
| // Time has moved backward quite a bit. There is no clear corrective |
| // action that doesn't risk leaving far-future invalidations |
| // in our system, so reject the request. |
| return false; |
| } |
| return true; |
| } |
| |
| bool PurgeSet::IsValid(const GoogleString& key, int64 timestamp_ms) const { |
| if (timestamp_ms <= global_invalidation_timestamp_ms_) { |
| return false; |
| } |
| int64* purge_timestamp_ms = lru_->GetNoFreshen(key); |
| return ((purge_timestamp_ms == NULL) || (timestamp_ms > *purge_timestamp_ms)); |
| } |
| |
| void PurgeSet::Swap(PurgeSet* that) { |
| lru_.swap(that->lru_); // scoped_ptr::swap |
| std::swap(global_invalidation_timestamp_ms_, |
| that->global_invalidation_timestamp_ms_); |
| helper_.Swap(&that->helper_); |
| } |
| |
| bool PurgeSet::Equals(const PurgeSet& that) const { |
| if (this == &that) { |
| return true; |
| } |
| |
| if (global_invalidation_timestamp_ms_ != |
| that.global_invalidation_timestamp_ms_) { |
| return false; |
| } |
| |
| if (lru_->num_elements() != that.lru_->num_elements()) { |
| return false; |
| } |
| |
| Lru::Iterator that_iter = that.lru_->Begin(), that_end = that.lru_->End(); |
| Lru::Iterator this_iter = lru_->Begin(), this_end = lru_->End(); |
| for (; this_iter != this_end; ++this_iter, ++that_iter) { |
| DCHECK(that_iter != that_end); |
| if ((that_iter.Value() != this_iter.Value()) || |
| (that_iter.Key() != this_iter.Key())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool PurgeSet::empty() const { |
| return ((global_invalidation_timestamp_ms_ == kInitialTimestampMs) && |
| (lru_->num_elements() == 0)); |
| } |
| |
| GoogleString PurgeSet::ToString() const { |
| GoogleString str("Global@"); |
| GoogleString buf; |
| if (ConvertTimeToString(global_invalidation_timestamp_ms_, &buf)) { |
| StrAppend(&str, buf); |
| } else { |
| StrAppend(&str, Integer64ToString(global_invalidation_timestamp_ms_)); |
| } |
| Lru::Iterator this_iter = lru_->Begin(), this_end = lru_->End(); |
| for (; this_iter != this_end; ++this_iter) { |
| StrAppend(&str, "\n", this_iter.Key(), "@"); |
| buf.clear(); |
| if (ConvertTimeToString(this_iter.Value(), &buf)) { |
| StrAppend(&str, buf); |
| } else { |
| StrAppend(&str, Integer64ToString(this_iter.Value())); |
| } |
| } |
| return str; |
| } |
| |
| } // namespace net_instaweb |