| // 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 |