| /* |
| * Copyright 2012 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/fallback_cache.h" |
| |
| #include "base/logging.h" |
| #include "pagespeed/kernel/base/shared_string.h" |
| |
| namespace { |
| |
| // We decide in Put whether the value goes to large_object_cache_ |
| // or small_object_cache_. When a value goes to small_object_cache_, |
| // we suffix it with an 'S'. When it goes to the large_object_cache_, we |
| // put into small_object_cache_ a single 'L', which will tell us |
| // to look in large_object_cache_. Values stored in large_object_cache_ |
| // do not have a suffix. |
| // |
| // We use suffixes so that we can strip the value from the |
| // SharedString via RemoveSuffix, which does not require mutating the |
| // base string data, which might, at least in the case of LRUCache, be |
| // accessed concurrently by multiple threads. |
| char kInSmallObjectCache = 'S'; |
| char kInLargeObjectCache = 'L'; |
| |
| } // namespace |
| |
| namespace net_instaweb { |
| |
| namespace { |
| |
| // Forwards Get requests to the large_cache when the response matches |
| // kInLargeObjectCache. For kInSmallObjectCache, unwraps the payload |
| // and delivers it to the callback. |
| class FallbackCallback : public CacheInterface::Callback { |
| public: |
| FallbackCallback(CacheInterface::Callback* callback, |
| CacheInterface* large_object_cache) |
| : callback_(callback), |
| large_object_cache_(large_object_cache), |
| validate_candidate_called_(false) { |
| } |
| |
| virtual ~FallbackCallback() { |
| } |
| |
| virtual void Done(CacheInterface::KeyState state) { |
| DCHECK(validate_candidate_called_); |
| if (callback_ != NULL) { |
| callback_->DelegatedDone(state); |
| } |
| delete this; |
| } |
| |
| // This validation is called by the small-object cache. We need to decode |
| // the value and decide whether to unwrap the small value, or forward the |
| // request to the large_object_cache_. |
| virtual bool ValidateCandidate(const GoogleString& key, |
| CacheInterface::KeyState state) { |
| validate_candidate_called_ = true; |
| size_t size = value()->size(); |
| const char* val = value()->data(); |
| if ((size == 1) && (val[0] == kInLargeObjectCache)) { |
| // Delegate the fetch to large_object_cache_, passing the |
| // original callback directly to the large_object_cache_. |
| // We erase callback_ so we don't forward the Done report |
| // from the small cache. |
| Callback* callback = callback_; |
| callback_ = NULL; |
| large_object_cache_->Get(key, callback); |
| return true; // The forwarding-marker in the small object cache is OK. |
| } else if ((size >= 1) && (val[size - 1] == kInSmallObjectCache)) { |
| // Link values together, but strip the marker from the new view. |
| *callback_->value() = *value(); |
| callback_->value()->RemoveSuffix(1); |
| return callback_->DelegatedValidateCandidate(key, state); |
| } |
| // The value in the cache was missing or encoded incorrectly. |
| callback_->DelegatedValidateCandidate(key, CacheInterface::kNotFound); |
| return false; |
| } |
| |
| private: |
| CacheInterface::Callback* callback_; |
| CacheInterface* large_object_cache_; |
| bool validate_candidate_called_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FallbackCallback); |
| }; |
| |
| } // namespace |
| |
| FallbackCache::FallbackCache(CacheInterface* small_object_cache, |
| CacheInterface* large_object_cache, |
| int threshold_bytes, |
| MessageHandler* handler) |
| : small_object_cache_(small_object_cache), |
| large_object_cache_(large_object_cache), |
| threshold_bytes_(threshold_bytes), |
| account_for_key_size_(true), |
| message_handler_(handler) { |
| } |
| |
| FallbackCache::~FallbackCache() { |
| } |
| |
| GoogleString FallbackCache::FormatName(StringPiece small, StringPiece large) { |
| return StrCat("Fallback(small=", small, ",large=", large, ")"); |
| } |
| |
| void FallbackCache::Get(const GoogleString& key, Callback* callback) { |
| callback = new FallbackCallback(callback, large_object_cache_); |
| small_object_cache_->Get(key, callback); |
| } |
| |
| void FallbackCache::MultiGet(MultiGetRequest* request) { |
| for (int i = 0, n = request->size(); i < n; ++i) { |
| KeyCallback& key_callback = (*request)[i]; |
| key_callback.callback = new FallbackCallback(key_callback.callback, |
| large_object_cache_); |
| } |
| small_object_cache_->MultiGet(request); |
| } |
| |
| void FallbackCache::Put(const GoogleString& key, SharedString* value) { |
| int store_size = value->size(); |
| if (account_for_key_size_) { |
| store_size += static_cast<int>(key.size()); |
| } |
| store_size += 1; // For kInSmallObjectCache marker. |
| |
| if (store_size > threshold_bytes_) { |
| SharedString forwarding_value; |
| forwarding_value.Assign(&kInLargeObjectCache, 1); |
| small_object_cache_->Put(key, &forwarding_value); |
| large_object_cache_->Put(key, value); |
| } else { |
| SharedString wrapped_value(*value); |
| wrapped_value.Append(&kInSmallObjectCache, 1); |
| small_object_cache_->Put(key, &wrapped_value); |
| } |
| } |
| |
| void FallbackCache::Delete(const GoogleString& key) { |
| small_object_cache_->Delete(key); |
| large_object_cache_->Delete(key); |
| } |
| |
| } // namespace net_instaweb |