blob: 553f89363e071e5fd63795a6d8ba73399d38af02 [file] [log] [blame]
/*
* 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: pulkitg@google.com (Pulkit Goyal)
#include "pagespeed/opt/http/two_level_property_store.h"
#include "base/logging.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/callback.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/opt/http/abstract_property_store_get_callback.h"
#include "pagespeed/opt/http/property_cache.pb.h"
namespace net_instaweb {
namespace {
// This class manages the lookup across two property stores. This class ensures
// following things:
// - If lookup was successful and all cohorts are available in
// primary_property_store, then call the done_ callback without calling
// lookup on secondary_property_store.
// - If lookup was not successful or some of the cohorts are not available in
// primary_property_store, then issue a lookup on secondary_property_store.
// - If FastFinishLookup is called, it checks:
// - If primary_property_store lookup is not yet finished, then wait until
// primary lookup is finished and after it is finished, call the the done_
// callback even if all the cohort are not available and don't issue
// secondary lookup.
// - If primary_property_store lookup is finished and secondary lookup is in
// progress, then call FinishFastLookup on secondary property store, so
// that done_ callback can be called as soon as possible.
// - If both lookups are finished, do nothing.
// - If DeleteWhenDone() is called, it checks:
// - If both lookups are done, delete itself.
// - If lookup is in progress, mark a bit delete_when_done_ to true, so that
// it deletes itself whenever lookup is finished.
class TwoLevelPropertyStoreGetCallback
: public AbstractPropertyStoreGetCallback {
public:
typedef Callback1<bool> BoolCallback;
TwoLevelPropertyStoreGetCallback(
const GoogleString& url,
const GoogleString& options_signature_hash,
const GoogleString& cache_key_suffix,
const PropertyCache::CohortVector& cohort_list,
PropertyPage* page,
BoolCallback* done,
AbstractMutex* mutex,
PropertyStore* primary_property_store,
PropertyStore* secondary_property_store)
: url_(url),
options_signature_hash_(options_signature_hash),
cache_key_suffix_(cache_key_suffix),
page_(page),
done_(done),
mutex_(mutex),
primary_property_store_(primary_property_store),
secondary_property_store_(secondary_property_store),
secondary_property_store_get_callback_(NULL),
fast_finish_lookup_called_(false),
lookup_level_(kFirstLevelLooking),
delete_when_done_(false),
first_level_result_(false),
secondary_lookup_(false) {
for (int j = 0, n = cohort_list.size(); j < n; ++j) {
const PropertyCache::Cohort* cohort = cohort_list[j];
cohort_list_.push_back(cohort);
}
}
virtual ~TwoLevelPropertyStoreGetCallback() {
if (secondary_property_store_get_callback_ != NULL) {
secondary_property_store_get_callback_->DeleteWhenDone();
}
}
virtual void FastFinishLookup() {
AbstractPropertyStoreGetCallback* secondary_property_store_get_callback =
NULL;
{
ScopedMutex lock(mutex_.get());
fast_finish_lookup_called_ = true;
if (lookup_level_ != kSecondLevelLooking) {
// Return without calling the done_ callback if:
// First level lookup is in progress : We always want first level lookup
// to be completed.
// Both lookup are completed: done_ callback was already called by
// Done() function.
return;
}
DCHECK(secondary_property_store_get_callback_ != NULL);
secondary_property_store_get_callback =
secondary_property_store_get_callback_;
}
// Fast finish the lookup from secondary property store.
secondary_property_store_get_callback->FastFinishLookup();
}
virtual void DeleteWhenDone() {
{
ScopedMutex lock(mutex_.get());
delete_when_done_ = true;
// We should only ever delete ourselves if lookup is done.
if (!ShouldDeleteLocked()) {
return;
}
}
delete this;
}
// Called after the primary lookup is done.
void PrimaryLookupDone(bool success) {
bool should_delete = false;
BoolCallback* done = NULL;
{
ScopedMutex lock(mutex_.get());
first_level_result_ = success;
// Check if it is FastFinishLookup() is called or lookup for any cohort
// ended up in a miss.
for (int j = 0, n = cohort_list_.size(); j < n; ++j) {
// Collect all the cohorts which is not present after lookup in
// primary_property_store is done.
const PropertyCache::Cohort* cohort = cohort_list_[j];
if (!page_->IsCohortPresent(cohort)) {
secondary_lookup_cohort_list_.push_back(cohort);
}
}
if (fast_finish_lookup_called_ || secondary_lookup_cohort_list_.empty()) {
lookup_level_ = kDone;
done = done_;
done_ = NULL;
// We should only ever delete ourselves if we've been canceled or we got
// results for all cohorts from the first level property store.
should_delete = ShouldDeleteLocked();
} else {
secondary_lookup_ = true;
// Do not issue the secondary level lookup while holding mutex as it may
// lead to deadlock if secondary lookup finishes in the same thread and
// call Done which also tries to get the Mutex.
}
DCHECK(!(should_delete && (lookup_level_ != kDone)));
}
if (!secondary_lookup_) {
// Run the done_ callback if secondary lookup is not needed and the delete
// the callback if DeleteWhenDone is already called.
done->Run(success);
if (should_delete) {
delete this;
}
return;
}
// Second level lookup will be initiated only if FastFinishLookup() is not
// called and some cohorts are not found in first level lookup.
IssueSecondaryGet();
}
void SecondaryLookupDone(bool success) {
bool should_delete = false;
BoolCallback* done = NULL;
{
ScopedMutex lock(mutex_.get());
DCHECK(done_ != NULL);
// Second Level lookup finished.
lookup_level_ = kDone;
success |= first_level_result_;
done = done_;
done_ = NULL;
// We should only ever delete ourselves if all internal states are
// updated.
should_delete = ShouldDeleteLocked();
}
if (success) {
for (int j = 0, n = secondary_lookup_cohort_list_.size(); j < n; ++j) {
const PropertyCache::Cohort* cohort = secondary_lookup_cohort_list_[j];
PropertyCacheValues values;
if (page_->EncodePropertyCacheValues(cohort, &values)) {
primary_property_store_->Put(
url_,
options_signature_hash_,
cache_key_suffix_,
cohort,
&values,
NULL);
}
}
}
// No class level variable is safe to use beyond this point.
done->Run(success);
if (should_delete) {
delete this;
}
}
private:
// Issue lookup from secondary_primary_store.
void IssueSecondaryGet() {
AbstractPropertyStoreGetCallback* secondary_property_store_get_callback =
NULL;
secondary_property_store_->Get(
url_,
options_signature_hash_,
cache_key_suffix_,
secondary_lookup_cohort_list_,
page_,
NewCallback(this,
&TwoLevelPropertyStoreGetCallback::SecondaryLookupDone),
&secondary_property_store_get_callback);
bool fast_finish_lookup_called = false;
bool should_delete = false;
{
ScopedMutex lock(mutex_.get());
secondary_property_store_get_callback_ =
secondary_property_store_get_callback;
// lookup_level_ will be kDone if secondary lookup finishes immediately.
if (lookup_level_ != kDone) {
lookup_level_ = kSecondLevelLooking;
fast_finish_lookup_called = fast_finish_lookup_called_;
} else {
// We'll only delete ourselves if the second level lookup finished
// inline.
should_delete = ShouldDeleteLocked();
}
}
if (fast_finish_lookup_called) {
// FastFinishLookup() is called before
// secondary_property_store_get_callback is assigned.
secondary_property_store_get_callback->FastFinishLookup();
}
if (should_delete) {
delete this;
}
}
// Returns true if it is safe to delete this callback, false otherwise.
bool ShouldDeleteLocked() {
mutex_->DCheckLocked();
if (secondary_lookup_ &&
secondary_property_store_get_callback_ == NULL) {
// We've decided to issue the second lookup but haven't yet updated our
// internal state.
return false;
}
if (lookup_level_ != kDone) {
// Lookup is not yet finished.
return false;
}
if (!delete_when_done_) {
// DeleteWhenDone() is not yet called.
return false;
}
// Safe to delete the callback.
return true;
}
enum LookupLevel {
kFirstLevelLooking, // Lookup from primary_property_store is in progress.
kSecondLevelLooking, // Lookup from secondary_property_store is in
// progress.
kDone, // Lookup is finished.
};
GoogleString url_;
GoogleString options_signature_hash_;
const GoogleString& cache_key_suffix_;
PropertyCache::CohortVector cohort_list_;
PropertyPage* page_; // page_ becomes NULL as soon as Done() is called.
BoolCallback* done_;
scoped_ptr<AbstractMutex> mutex_;
PropertyStore* primary_property_store_;
PropertyStore* secondary_property_store_;
AbstractPropertyStoreGetCallback* secondary_property_store_get_callback_;
bool fast_finish_lookup_called_;
LookupLevel lookup_level_;
bool delete_when_done_;
bool first_level_result_;
bool secondary_lookup_;
PropertyCache::CohortVector secondary_lookup_cohort_list_;
DISALLOW_COPY_AND_ASSIGN(TwoLevelPropertyStoreGetCallback);
};
} // namespace
TwoLevelPropertyStore::TwoLevelPropertyStore(
PropertyStore* primary_property_store,
PropertyStore* secondary_property_store,
ThreadSystem* thread_system)
: primary_property_store_(primary_property_store),
secondary_property_store_(secondary_property_store),
thread_system_(thread_system) {
CHECK(primary_property_store_ != NULL);
CHECK(secondary_property_store_ != NULL);
secondary_property_store_->set_enable_get_cancellation(true);
}
TwoLevelPropertyStore::~TwoLevelPropertyStore() {
}
void TwoLevelPropertyStore::Get(
const GoogleString& url,
const GoogleString& options_signature_hash,
const GoogleString& cache_key_suffix,
const PropertyCache::CohortVector& cohort_list,
PropertyPage* page,
BoolCallback* done,
AbstractPropertyStoreGetCallback** callback) {
TwoLevelPropertyStoreGetCallback* two_level_property_store_get_callback =
new TwoLevelPropertyStoreGetCallback(
url,
options_signature_hash,
cache_key_suffix,
cohort_list,
page,
done,
thread_system_->NewMutex(),
primary_property_store_,
secondary_property_store_);
*callback = two_level_property_store_get_callback;
AbstractPropertyStoreGetCallback* primary_property_store_get_callback = NULL;
primary_property_store_->Get(
url,
options_signature_hash,
cache_key_suffix,
cohort_list,
page,
NewCallback(two_level_property_store_get_callback,
&TwoLevelPropertyStoreGetCallback::PrimaryLookupDone),
&primary_property_store_get_callback);
if (primary_property_store_get_callback != NULL) {
// Delete the primary store get callback when it is done as it is not needed
// any more.
primary_property_store_get_callback->DeleteWhenDone();
}
}
void TwoLevelPropertyStore::Put(
const GoogleString& url,
const GoogleString& options_signature_hash,
const GoogleString& cache_key_suffix,
const PropertyCache::Cohort* cohort,
const PropertyCacheValues* values,
BoolCallback* done) {
// TODO(pulkitg): Pass actual callback instead of NULL.
primary_property_store_->Put(
url, options_signature_hash, cache_key_suffix, cohort, values, NULL);
secondary_property_store_->Put(
url, options_signature_hash, cache_key_suffix, cohort, values, NULL);
if (done != NULL) {
done->Run(true);
}
}
GoogleString TwoLevelPropertyStore::Name() const {
return StrCat(
"1:", primary_property_store_->Name(),
",2:", secondary_property_store_->Name());
}
} // namespace net_instaweb