blob: 1b14825ec53c8b34e178c3cf21ec0de3100504fd [file] [log] [blame]
// Copyright 2011 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/system/system_cache_path.h"
#include "base/logging.h"
#include "net/instaweb/rewriter/public/rewrite_driver_factory.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "pagespeed/system/system_rewrite_options.h"
#include "pagespeed/system/system_server_context.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/callback.h"
#include "pagespeed/kernel/base/message_handler.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/cache/cache_interface.h"
#include "pagespeed/kernel/cache/cache_stats.h"
#include "pagespeed/kernel/cache/file_cache.h"
#include "pagespeed/kernel/cache/lru_cache.h"
#include "pagespeed/kernel/cache/purge_context.h"
#include "pagespeed/kernel/cache/threadsafe_cache.h"
#include "pagespeed/kernel/sharedmem/shared_mem_lock_manager.h"
#include "pagespeed/kernel/util/file_system_lock_manager.h"
namespace net_instaweb {
const char SystemCachePath::kFileCache[] = "file_cache";
const char SystemCachePath::kLruCache[] = "lru_cache";
// The SystemCachePath encapsulates a cache-sharing model where a user specifies
// a file-cache path per virtual-host. With each file-cache object we keep
// a locking mechanism and an optional per-process LRUCache.
SystemCachePath::SystemCachePath(const StringPiece& path,
const SystemRewriteOptions* config,
RewriteDriverFactory* factory,
AbstractSharedMem* shm_runtime)
: path_(path.data(), path.size()),
factory_(factory),
shm_runtime_(shm_runtime),
lock_manager_(NULL),
file_cache_backend_(NULL),
lru_cache_(NULL),
file_cache_(NULL),
cache_flush_filename_(config->cache_flush_filename()),
unplugged_(config->unplugged()),
enable_cache_purge_(config->enable_cache_purge()),
clean_interval_explicitly_set_(
config->has_file_cache_clean_interval_ms()),
clean_size_explicitly_set_(config->has_file_cache_clean_size_kb()),
clean_inode_limit_explicitly_set_(
config->has_file_cache_clean_inode_limit()),
mutex_(factory->thread_system()->NewMutex()) {
if (cache_flush_filename_.empty()) {
if (enable_cache_purge_) {
cache_flush_filename_ = "cache.purge";
} else {
cache_flush_filename_ = "cache.flush";
}
}
if (cache_flush_filename_[0] != '/') {
// Implementations must ensure the file cache path is an absolute path.
// mod_pagespeed checks in mod_instaweb.cc:pagespeed_post_config while
// ngx_pagespeed checks in ngx_pagespeed.cc:ps_merge_srv_conf.
// There is at least one example where this check is violated in
// ngx_pagespeed. Example:
// server {
// pagespeed off;
// pagespeed FileCachePath "/tmp";
// location / {
// pagespeed on;
// }
// }
//
// Fixing this would require knowing if pagespeed is ever switched on within
// a deeper level of the block. When this is parsed, we just have knowledge
// of the higher-level server block.
StringPiece path(config->file_cache_path());
cache_flush_filename_ = StrCat(
path, (path.size() > 0 &&
path.ends_with("/")) ? "" : "/",
cache_flush_filename_);
}
if (config->use_shared_mem_locking()) {
shared_mem_lock_manager_.reset(new SharedMemLockManager(
shm_runtime, LockManagerSegmentName(),
factory->scheduler(), factory->hasher(), factory->message_handler()));
lock_manager_ = shared_mem_lock_manager_.get();
} else {
FallBackToFileBasedLocking();
}
FileCache::CachePolicy* policy = new FileCache::CachePolicy(
factory->timer(),
factory->hasher(),
config->file_cache_clean_interval_ms(),
config->file_cache_clean_size_kb() * 1024,
config->file_cache_clean_inode_limit());
file_cache_backend_ =
new FileCache(config->file_cache_path(), factory->file_system(),
factory->thread_system(), NULL, policy,
factory->statistics(), factory->message_handler());
factory->TakeOwnership(file_cache_backend_);
file_cache_ = new CacheStats(kFileCache, file_cache_backend_,
factory->timer(), factory->statistics());
factory->TakeOwnership(file_cache_);
if (config->lru_cache_kb_per_process() != 0) {
LRUCache* lru_cache = new LRUCache(
config->lru_cache_kb_per_process() * 1024);
factory->TakeOwnership(lru_cache);
// We only add the threadsafe-wrapper to the LRUCache. The FileCache
// is naturally thread-safe because it's got no writable member variables.
// And surrounding that slower-running class with a mutex would likely
// cause contention.
ThreadsafeCache* ts_cache =
new ThreadsafeCache(lru_cache, factory->thread_system()->NewMutex());
factory->TakeOwnership(ts_cache);
#if CACHE_STATISTICS
lru_cache_ = new CacheStats(kLruCache, ts_cache, factory->timer(),
factory->statistics());
factory->TakeOwnership(lru_cache_);
#else
lru_cache_ = ts_cache;
#endif
}
}
SystemCachePath::~SystemCachePath() {
}
// static
GoogleString SystemCachePath::CachePath(SystemRewriteOptions* config) {
return (config->unplugged()
? "<unplugged>"
: StrCat(config->file_cache_path(),
config->enable_cache_purge() ? " purge " : " flush ",
config->cache_flush_filename()));
}
void SystemCachePath::MergeConfig(const SystemRewriteOptions* config) {
FileCache::CachePolicy* policy = file_cache_backend_->mutable_cache_policy();
// For the interval, we take the smaller of the specified intervals, so
// we get at least as much cache cleaning as each vhost owner wants.
MergeEntries(config->file_cache_clean_interval_ms(),
config->has_file_cache_clean_interval_ms(),
false /* take_larger */,
"IntervalMs",
&policy->clean_interval_ms,
&clean_interval_explicitly_set_);
// For the sizes, we take the maximum value, so that the owner of any
// vhost gets at least as much disk space as they asked for. Note,
// an argument could be made either way, but there's really no right
// answer here, which is why MergeEntries prints a warning on a conflict.
MergeEntries(config->file_cache_clean_size_kb() * 1024,
config->has_file_cache_clean_size_kb(),
true, "SizeKb",
&policy->target_size_bytes,
&clean_size_explicitly_set_);
MergeEntries(config->file_cache_clean_inode_limit(),
config->has_file_cache_clean_inode_limit(),
true, "InodeLimit",
&policy->target_inode_count,
&clean_inode_limit_explicitly_set_);
}
void SystemCachePath::MergeEntries(int64 config_value, bool config_was_set,
bool take_larger,
const char* name,
int64* policy_value,
bool* policy_was_set) {
if (config_value != *policy_value) {
// If only one of these values was explicitly set, then just silently
// update to the explicitly set one.
if (config_was_set && !*policy_was_set) {
*policy_value = config_value;
*policy_was_set = true;
} else if (!config_was_set && *policy_was_set) {
// No action required; ignore default value coming from the new config.
} else {
DCHECK(config_was_set && *policy_was_set);
*policy_was_set = true;
factory_->message_handler()->Message(
kWarning,
"Conflicting settings %s!=%s for FileCacheClean%s for file-cache %s, "
"keeping the %s value",
Integer64ToString(config_value).c_str(),
Integer64ToString(*policy_value).c_str(),
name,
path_.c_str(),
take_larger ? "larger" : "smaller");
if ((take_larger && (config_value > *policy_value)) ||
(!take_larger && (config_value < *policy_value))) {
*policy_value = config_value;
}
}
}
}
void SystemCachePath::RootInit() {
factory_->message_handler()->Message(
kInfo, "Initializing shared memory for path: %s.", path_.c_str());
if ((shared_mem_lock_manager_.get() != NULL) &&
!shared_mem_lock_manager_->Initialize()) {
FallBackToFileBasedLocking();
}
}
void SystemCachePath::ChildInit(SlowWorker* cache_clean_worker) {
if (unplugged_) {
return;
}
factory_->message_handler()->Message(
kInfo, "Reusing shared memory for path: %s.", path_.c_str());
if ((shared_mem_lock_manager_.get() != NULL) &&
!shared_mem_lock_manager_->Attach()) {
FallBackToFileBasedLocking();
}
if (file_cache_backend_ != NULL) {
file_cache_backend_->set_worker(cache_clean_worker);
}
purge_context_.reset(new PurgeContext(cache_flush_filename_,
factory_->file_system(),
factory_->timer(),
RewriteOptions::kCachePurgeBytes,
factory_->thread_system(),
lock_manager_,
factory_->scheduler(),
factory_->statistics(),
factory_->message_handler()));
purge_context_->set_enable_purge(enable_cache_purge_);
purge_context_->SetUpdateCallback(NewPermanentCallback(
this, &SystemCachePath::UpdateCachePurgeSet));
}
void SystemCachePath::GlobalCleanup(MessageHandler* handler) {
if (shared_mem_lock_manager_.get() != NULL) {
shared_mem_lock_manager_->GlobalCleanup(
shm_runtime_, LockManagerSegmentName(), handler);
}
}
void SystemCachePath::FallBackToFileBasedLocking() {
if ((shared_mem_lock_manager_.get() != NULL) || (lock_manager_ == NULL)) {
shared_mem_lock_manager_.reset(NULL);
file_system_lock_manager_.reset(new FileSystemLockManager(
factory_->file_system(), path_,
factory_->scheduler(), factory_->message_handler()));
lock_manager_ = file_system_lock_manager_.get();
}
}
GoogleString SystemCachePath::LockManagerSegmentName() const {
return StrCat(path_, "/named_locks");
}
void SystemCachePath::FlushCacheIfNecessary() {
if (!unplugged_) {
purge_context_->PollFileSystem();
}
}
void SystemCachePath::AddServerContext(SystemServerContext* server_context) {
ScopedMutex lock(mutex_.get());
server_context_set_.insert(server_context);
}
void SystemCachePath::RemoveServerContext(SystemServerContext* server_context) {
ScopedMutex lock(mutex_.get());
server_context_set_.erase(server_context);
}
void SystemCachePath::UpdateCachePurgeSet(
const CopyOnWrite<PurgeSet>& purge_set) {
ScopedMutex lock(mutex_.get());
for (ServerContextSet::iterator p = server_context_set_.begin(),
e = server_context_set_.end(); p != e; ++p) {
SystemServerContext* server_context = *p;
server_context->UpdateCachePurgeSet(purge_set);
}
}
} // namespace net_instaweb