| // 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) |
| // lsong@google.com (Libo Song) |
| |
| #include "pagespeed/system/system_caches.h" |
| |
| #include <cstddef> |
| #include <cstdlib> |
| #include <utility> |
| #include <tuple> |
| |
| #include "base/logging.h" |
| #include "net/instaweb/http/public/http_cache.h" |
| #include "net/instaweb/rewriter/public/rewrite_driver.h" |
| #include "net/instaweb/rewriter/public/rewrite_driver_factory.h" |
| #include "net/instaweb/rewriter/public/rewrite_options.h" |
| #include "net/instaweb/rewriter/public/server_context.h" |
| #include "pagespeed/system/apr_mem_cache.h" |
| #include "pagespeed/system/redis_cache.h" |
| #include "pagespeed/system/system_cache_path.h" |
| #include "pagespeed/system/system_rewrite_options.h" |
| #include "pagespeed/system/system_server_context.h" |
| #include "pagespeed/system/external_server_spec.h" |
| #include "net/instaweb/util/public/property_cache.h" |
| #include "pagespeed/kernel/base/abstract_shared_mem.h" |
| #include "pagespeed/kernel/base/md5_hasher.h" |
| #include "pagespeed/kernel/base/message_handler.h" |
| #include "pagespeed/kernel/base/statistics.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_writer.h" |
| #include "pagespeed/kernel/base/thread_system.h" |
| #include "pagespeed/kernel/cache/async_cache.h" |
| #include "pagespeed/kernel/cache/cache_batcher.h" |
| #include "pagespeed/kernel/cache/cache_stats.h" |
| #include "pagespeed/kernel/cache/compressed_cache.h" |
| #include "pagespeed/kernel/cache/fallback_cache.h" |
| #include "pagespeed/kernel/cache/file_cache.h" |
| #include "pagespeed/kernel/cache/purge_context.h" |
| #include "pagespeed/kernel/cache/write_through_cache.h" |
| #include "pagespeed/kernel/thread/queued_worker_pool.h" |
| #include "pagespeed/kernel/thread/slow_worker.h" |
| |
| namespace net_instaweb { |
| |
| const char SystemCaches::kMemcachedAsync[] = "memcached_async"; |
| const char SystemCaches::kMemcachedBlocking[] = "memcached_blocking"; |
| const char SystemCaches::kRedisAsync[] = "redis_async"; |
| const char SystemCaches::kRedisBlocking[] = "redis_blocking"; |
| const char SystemCaches::kShmCache[] = "shm_cache"; |
| const char SystemCaches::kDefaultSharedMemoryPath[] = "pagespeed_default_shm"; |
| |
| SystemCaches::SystemCaches( |
| RewriteDriverFactory* factory, AbstractSharedMem* shm_runtime, |
| int thread_limit) |
| : factory_(factory), |
| shared_mem_runtime_(shm_runtime), |
| thread_limit_(thread_limit), |
| is_root_process_(true), |
| was_shut_down_(false), |
| cache_hasher_(20), |
| default_shm_metadata_cache_creation_failed_(false) { |
| } |
| |
| SystemCaches::~SystemCaches() { |
| DCHECK(was_shut_down_); |
| } |
| |
| void SystemCaches::ShutDown(MessageHandler* message_handler) { |
| DCHECK(!was_shut_down_); |
| if (was_shut_down_) { |
| return; |
| } |
| |
| was_shut_down_ = true; |
| |
| // Shut down the cache cleaning thread so we no longer have to worry about |
| // outstanding jobs in the slow_worker_ trying |
| // to access FileCache and similar objects we're about to blow away. |
| if (!is_root_process_) { |
| slow_worker_->ShutDown(); |
| } |
| |
| // Take down any threads serving external caches, then wait for shut down to |
| // complete and free memory. Note that this will block waiting for any |
| // operations currently in progress to terminate. It's the reason we initiate |
| // shutdown and wait for it instead of simple calling ShutDown(). |
| // |
| // In case of memcached it can possibly require kill -9 to restart Apache if |
| // memcached is permanently hung. In pracice, the patches made in |
| // src/third_party/aprutil/apr_memcache2.c make that very unlikely. |
| // |
| // The alternative scenario of exiting with pending I/O will often |
| // crash and always leak memory. Note that if memcached crashes, as |
| // opposed to hanging, it will probably not appear wedged. |
| if (memcached_pool_) { |
| memcached_pool_->InitiateShutDown(); |
| } |
| if (redis_pool_) { |
| redis_pool_->InitiateShutDown(); |
| } |
| if (memcached_pool_) { |
| memcached_pool_->WaitForShutDownComplete(); |
| memcached_pool_.reset(nullptr); |
| } |
| if (redis_pool_) { |
| redis_pool_->WaitForShutDownComplete(); |
| redis_pool_.reset(nullptr); |
| } |
| |
| if (is_root_process_) { |
| // Cleanup per-path shm resources. |
| for (PathCacheMap::iterator p = path_cache_map_.begin(), |
| e = path_cache_map_.end(); p != e; ++p) { |
| p->second->GlobalCleanup(message_handler); |
| } |
| |
| // And all the SHM caches. |
| for (MetadataShmCacheMap::iterator p = metadata_shm_caches_.begin(), |
| e = metadata_shm_caches_.end(); p != e; ++p) { |
| if (p->second->cache_backend != NULL && p->second->initialized) { |
| MetadataShmCache::GlobalCleanup(shared_mem_runtime_, p->second->segment, |
| message_handler); |
| } |
| } |
| } |
| } |
| |
| SystemCachePath* SystemCaches::GetCache(SystemRewriteOptions* config) { |
| GoogleString path = SystemCachePath::CachePath(config); |
| SystemCachePath* system_cache_path = NULL; |
| std::pair<PathCacheMap::iterator, bool> result = path_cache_map_.insert( |
| PathCacheMap::value_type(path, system_cache_path)); |
| PathCacheMap::iterator iter = result.first; |
| if (result.second) { |
| iter->second = system_cache_path = |
| new SystemCachePath(path, config, factory_, shared_mem_runtime_); |
| factory_->TakeOwnership(system_cache_path); |
| } else { |
| system_cache_path = iter->second; |
| system_cache_path->MergeConfig(config); |
| } |
| return system_cache_path; |
| } |
| |
| SystemCaches::ExternalCacheInterfaces |
| SystemCaches::ConstructExternalCacheInterfacesFromBlocking( |
| CacheInterface* backend, |
| QueuedWorkerPool* pool, int batcher_max_parallel_lookups, |
| const char* async_stats_name, const char* blocking_stats_name) { |
| |
| ExternalCacheInterfaces result; |
| |
| if (pool == NULL) { |
| result.async = backend; |
| } else { |
| result.async = new AsyncCache(backend, pool); |
| factory_->TakeOwnership(result.async); |
| } |
| |
| // Put the batcher above the stats so that the stats sees the MultiGets |
| // and can show us the histogram of how they are sized. |
| result.async = new CacheStats(async_stats_name, |
| result.async, |
| factory_->timer(), |
| factory_->statistics()); |
| factory_->TakeOwnership(result.async); |
| |
| CacheBatcher::Options options; |
| if (batcher_max_parallel_lookups != -1) { |
| options.max_parallel_lookups = batcher_max_parallel_lookups; |
| } |
| CacheBatcher* batcher = new CacheBatcher( |
| options, |
| result.async, |
| factory_->thread_system()->NewMutex(), |
| factory_->statistics()); |
| factory_->TakeOwnership(batcher); |
| result.async = batcher; |
| |
| // Populate the blocking interface, giving it its own |
| // statistics wrapper. |
| result.blocking = new CacheStats(blocking_stats_name, backend, |
| factory_->timer(), factory_->statistics()); |
| factory_->TakeOwnership(result.blocking); |
| return result; |
| } |
| |
| SystemCaches::ExternalCacheInterfaces SystemCaches::NewMemcached( |
| SystemRewriteOptions* config) { |
| const ExternalClusterSpec& servers_specs = config->memcached_servers(); |
| AprMemCache* mem_cache = |
| new AprMemCache(servers_specs, thread_limit_, &cache_hasher_, |
| factory_->statistics(), factory_->timer(), |
| factory_->message_handler()); |
| factory_->TakeOwnership(mem_cache); |
| mem_cache->set_timeout_us(config->memcached_timeout_us()); |
| memcache_servers_.push_back(mem_cache); |
| |
| int num_threads = config->memcached_threads(); |
| if (num_threads != 0) { |
| if (num_threads != 1) { |
| factory_->message_handler()->Message( |
| kWarning, "ModPagespeedMemcachedThreads support for >1 thread " |
| "is not supported yet; changing to 1 thread (was %d)", |
| num_threads); |
| num_threads = 1; |
| } |
| |
| if (memcached_pool_.get() == NULL) { |
| // Note -- we will use the first value of ModPagespeedMemCacheThreads |
| // that we see in a VirtualHost, ignoring later ones. |
| memcached_pool_.reset( |
| new QueuedWorkerPool(num_threads, "memcached", |
| factory_->thread_system())); |
| } |
| return ConstructExternalCacheInterfacesFromBlocking( |
| mem_cache, memcached_pool_.get(), num_threads, kMemcachedAsync, |
| kMemcachedBlocking); |
| } else { |
| return ConstructExternalCacheInterfacesFromBlocking( |
| mem_cache, |
| NULL, // No worker pool. |
| -1, // Do not change batcher's max_parallel_lookups. |
| kMemcachedAsync, kMemcachedBlocking); |
| } |
| } |
| |
| SystemCaches::ExternalCacheInterfaces SystemCaches::NewRedis( |
| SystemRewriteOptions* config) { |
| const ExternalServerSpec& server_spec = config->redis_server(); |
| // using database index -1 when property not specified in config |
| const int redis_database_index = |
| config->has_redis_database_index() ? config->redis_database_index() : -1; |
| RedisCache* redis_server = new RedisCache( |
| server_spec.host, server_spec.port, factory_->thread_system(), |
| factory_->message_handler(), factory_->timer(), |
| config->redis_reconnection_delay_ms(), config->redis_timeout_us(), |
| factory_->statistics(), redis_database_index); |
| factory_->TakeOwnership(redis_server); |
| redis_servers_.push_back(redis_server); |
| if (redis_pool_.get() == NULL) { |
| // TODO(yeputons): consider using more than one thread and making the amount |
| // configurable. For memcached using more than one thread was not boosting |
| // performance and that opportunity was eventually disabled, but that could |
| // be caused by some bug which is possibly already fixed. |
| // |
| // Looks like adding more threads won't be a win until RedisCache is async, |
| // because all queries will still be queued as they require exclusive access |
| // to RedisCache. Creating a separate single-threaded pool for different |
| // RedisCaches could be a good idea, though. |
| redis_pool_.reset( |
| new QueuedWorkerPool(1, "redis", factory_->thread_system())); |
| } |
| return ConstructExternalCacheInterfacesFromBlocking( |
| redis_server, redis_pool_.get(), 1, kRedisAsync, kRedisBlocking); |
| } |
| |
| SystemCaches::ExternalCacheInterfaces SystemCaches::NewExternalCache( |
| SystemRewriteOptions* config) { |
| bool use_redis = !config->redis_server().empty(); |
| bool use_memcached = !config->memcached_servers().empty(); |
| |
| if (use_redis && use_memcached) { |
| factory_->message_handler()->Message( |
| kWarning, "Redis and Memcached are enabled simultaneously, will use " |
| "Redis and ignore Memcached"); |
| use_memcached = false; |
| } |
| |
| // Some unique signature to distinguish server configurations. |
| GoogleString spec_signature; |
| if (use_redis) { |
| spec_signature = |
| StrCat("r;", config->redis_server().ToString(), ";", |
| IntegerToString(config->redis_reconnection_delay_ms()), ";", |
| IntegerToString(config->redis_timeout_us())); |
| } else if (use_memcached) { |
| spec_signature = StrCat("m;", config->memcached_servers().ToString(), ";", |
| IntegerToString(config->memcached_threads()), ";", |
| IntegerToString(config->memcached_timeout_us())); |
| } else { |
| return ExternalCacheInterfaces(); |
| } |
| |
| ExternalCachesMap::iterator iterator; |
| bool inserted; |
| std::tie(iterator, inserted) = external_caches_map_.insert( |
| ExternalCachesMap::value_type(spec_signature, ExternalCacheInterfaces())); |
| if (inserted) { |
| // Was not in the map, should construct new one and store it. |
| if (use_redis) { |
| iterator->second = NewRedis(config); |
| } else if (use_memcached) { |
| iterator->second = NewMemcached(config); |
| } |
| } |
| |
| // Some per-VirtualHost modifications follow, we do not want to store them in |
| // map. |
| ExternalCacheInterfaces result = iterator->second; |
| if (use_memcached) { |
| // Note that a distinct FallbackCache gets created for every VirtualHost |
| // that employs memcached, even if the memcached and file-cache |
| // specifications are identical. This does no harm, because there |
| // is no data in the cache object itself; just configuration. Sharing |
| // FallbackCache* objects would require making a map using the |
| // memcache & file-cache specs as a key (and possibly duplicating memcache |
| // objects as a result), so it's simpler to make a new small FallbackCache |
| // object for each VirtualHost. |
| |
| CacheInterface* file_cache = GetCache(config)->file_cache(); |
| |
| result.async = new FallbackCache(result.async, file_cache, |
| AprMemCache::kValueSizeThreshold, |
| factory_->message_handler()); |
| factory_->TakeOwnership(result.async); |
| |
| result.blocking = new FallbackCache(result.blocking, file_cache, |
| AprMemCache::kValueSizeThreshold, |
| factory_->message_handler()); |
| factory_->TakeOwnership(result.blocking); |
| } |
| return result; |
| } |
| |
| bool SystemCaches::CreateShmMetadataCache( |
| StringPiece name, int64 size_kb, GoogleString* error_msg) { |
| MetadataShmCacheInfo* cache_info = NULL; |
| std::pair<MetadataShmCacheMap::iterator, bool> result = |
| metadata_shm_caches_.insert( |
| MetadataShmCacheMap::value_type(name.as_string(), cache_info)); |
| if (result.second) { |
| int entries, blocks; |
| int64 size_cap; |
| const int kSectors = 128; |
| MetadataShmCache::ComputeDimensions( |
| size_kb, 2 /* block/entry ratio, based empirically off load tests */, |
| kSectors, &entries, &blocks, &size_cap); |
| |
| // Make sure the size cap is not unusably low. In particular, with 2K |
| // inlining thresholds, something like 3K is needed. (As of time of writing, |
| // that required about 4.3MiB). |
| if (size_cap < 3 * 1024) { |
| metadata_shm_caches_.erase(result.first); |
| *error_msg = "Shared memory cache unusably small."; |
| return false; |
| } else { |
| cache_info = new MetadataShmCacheInfo; |
| factory_->TakeOwnership(cache_info); |
| cache_info->segment = StrCat(name, "/metadata_cache"); |
| cache_info->cache_backend = |
| new SharedMemCache<64>( |
| shared_mem_runtime_, |
| cache_info->segment, |
| factory_->timer(), |
| factory_->hasher(), |
| kSectors, |
| entries, /* entries per sector */ |
| blocks /* blocks per sector*/, |
| factory_->message_handler()); |
| factory_->TakeOwnership(cache_info->cache_backend); |
| // We can't set cache_info->cache_to_use yet since statistics aren't ready |
| // yet. It will happen in ::RootInit(). |
| result.first->second = cache_info; |
| return true; |
| } |
| } else if (name == kDefaultSharedMemoryPath) { |
| // If the default shared memory cache already exists, and we try to create |
| // it again, that's not a problem. This happens because when we check if |
| // the cache exists yet we look at MetadataShmCacheInfo->cache_to_use which |
| // isn't actually set until RootInit(). |
| return true; |
| } else { |
| *error_msg = StrCat("Cache named ", name, " already exists."); |
| return false; |
| } |
| } |
| |
| NamedLockManager* SystemCaches::GetLockManager(SystemRewriteOptions* config) { |
| return GetCache(config)->lock_manager(); |
| } |
| |
| SystemCaches::MetadataShmCacheInfo* SystemCaches::LookupShmMetadataCache( |
| const GoogleString& name) { |
| if (name.empty()) { |
| return NULL; |
| } |
| MetadataShmCacheMap::iterator i = metadata_shm_caches_.find(name); |
| if (i != metadata_shm_caches_.end()) { |
| return i->second; |
| } |
| return NULL; |
| } |
| |
| SystemCaches::MetadataShmCacheInfo* SystemCaches::GetShmMetadataCacheOrDefault( |
| SystemRewriteOptions* config) { |
| MetadataShmCacheInfo* shm_cache = |
| LookupShmMetadataCache(config->file_cache_path()); |
| if (shm_cache != NULL) { |
| return shm_cache; // Explicitly configured. |
| } |
| if (shared_mem_runtime_->IsDummy()) { |
| // We're on a system that doesn't actually support shared memory. |
| return NULL; |
| } |
| if (config->default_shared_memory_cache_kb() == 0) { |
| return NULL; // User has disabled the default shm cache. |
| } |
| shm_cache = LookupShmMetadataCache(kDefaultSharedMemoryPath); |
| if (shm_cache != NULL) { |
| return shm_cache; // Using the default shm cache, which already exists. |
| } |
| if (default_shm_metadata_cache_creation_failed_) { |
| return NULL; // Already tried to create the default shm cache and failed. |
| } |
| // This config is for the first server context to need the default cache; |
| // create it. |
| GoogleString error_msg; |
| bool ok = CreateShmMetadataCache(kDefaultSharedMemoryPath, |
| config->default_shared_memory_cache_kb(), |
| &error_msg); |
| if (!ok) { |
| factory_->message_handler()->Message( |
| kWarning, "Default shared memory cache: %s", error_msg.c_str()); |
| default_shm_metadata_cache_creation_failed_ = true; |
| return NULL; |
| } |
| return LookupShmMetadataCache(kDefaultSharedMemoryPath); |
| } |
| |
| void SystemCaches::SetupPcacheCohorts(ServerContext* server_context, |
| bool enable_property_cache) { |
| server_context->set_enable_property_cache(enable_property_cache); |
| PropertyCache* pcache = server_context->page_property_cache(); |
| server_context->set_beacon_cohort( |
| server_context->AddCohort(RewriteDriver::kBeaconCohort, pcache)); |
| server_context->set_dom_cohort( |
| server_context->AddCohort(RewriteDriver::kDomCohort, pcache)); |
| server_context->set_dependencies_cohort( |
| server_context->AddCohort(RewriteDriver::kDependenciesCohort, pcache)); |
| } |
| |
| void SystemCaches::SetupCaches(ServerContext* server_context, |
| bool enable_property_cache) { |
| SystemRewriteOptions* config = dynamic_cast<SystemRewriteOptions*>( |
| server_context->global_options()); |
| DCHECK(config != NULL); |
| SystemCachePath* caches_for_path = GetCache(config); |
| CacheInterface* lru_cache = caches_for_path->lru_cache(); |
| CacheInterface* file_cache = caches_for_path->file_cache(); |
| MetadataShmCacheInfo* shm_metadata_cache_info = |
| GetShmMetadataCacheOrDefault(config); |
| CacheInterface* shm_metadata_cache = (shm_metadata_cache_info != NULL) ? |
| shm_metadata_cache_info->cache_to_use : NULL; |
| CacheInterface* property_store_cache = NULL; |
| CacheInterface* http_l2 = file_cache; |
| Statistics* stats = server_context->statistics(); |
| |
| ExternalCacheInterfaces external_cache = NewExternalCache(config); |
| if (external_cache.async != nullptr) { |
| CHECK(external_cache.blocking != nullptr); |
| |
| http_l2 = external_cache.async; |
| |
| // Use the blocking version of our external cache for the filesystem |
| // metadata cache AND the property store cache. Note that if there is |
| // a shared-memory cache, then we will override this setting and use it |
| // for the filesystem metadata cache below. |
| server_context->set_filesystem_metadata_cache(external_cache.blocking); |
| property_store_cache = external_cache.blocking; |
| } |
| |
| // Figure out our L1/L2 hierarchy for http cache. |
| // TODO(jmarantz): consider moving ownership of the LRU cache into the |
| // factory, rather than having one per vhost. |
| // |
| // Note that a user can disable the LRU cache by setting its byte-count |
| // to 0, and in fact this is the default setting. |
| int64 max_content_length = config->max_cacheable_response_content_length(); |
| HTTPCache* http_cache = NULL; |
| if (lru_cache == NULL) { |
| // No L1, and so backend is just the L2. |
| http_cache = new HTTPCache(http_l2, factory_->timer(), |
| factory_->hasher(), stats); |
| http_cache->SetCompressionLevel(config->http_cache_compression_level()); |
| } else { |
| // L1 is LRU, with the L2 as computed above. |
| WriteThroughCache* write_through_http_cache = new WriteThroughCache( |
| lru_cache, http_l2); |
| server_context->DeleteCacheOnDestruction(write_through_http_cache); |
| write_through_http_cache->set_cache1_limit(config->lru_cache_byte_limit()); |
| http_cache = new HTTPCache(write_through_http_cache, factory_->timer(), |
| factory_->hasher(), stats); |
| http_cache->set_cache_levels(2); |
| http_cache->SetCompressionLevel(config->http_cache_compression_level()); |
| } |
| |
| http_cache->set_max_cacheable_response_content_length(max_content_length); |
| server_context->set_http_cache(http_cache); |
| |
| // And now the metadata cache. If we only have one level, it will be in |
| // metadata_l2, with metadata_l1 set to NULL. |
| CacheInterface* metadata_l1 = NULL; |
| CacheInterface* metadata_l2 = NULL; |
| size_t l1_size_limit = WriteThroughCache::kUnlimited; |
| if (shm_metadata_cache != NULL) { |
| if (external_cache.async != nullptr) { |
| // If we have both a local SHM cache and a cache on an external server |
| // we should go L1/L2 because there are likely to be other machines |
| // that would like to use our metadata. |
| metadata_l1 = shm_metadata_cache; |
| metadata_l2 = external_cache.async; |
| |
| // Because external cache share the metadata cache across machines, |
| // we need a filesystem metadata cache to validate LoadFromFile |
| // entries. We default to using external cache for that, even though |
| // the LoadFromFile metadata is usually local to the machine, |
| // unless the user specifies an NFS directory in LoadFromFile. |
| // This is OK because it is keyed to the machine name. But if |
| // we have a shm cache, then use it instead for the metadata |
| // cache. |
| // |
| // Note that we don't need to use a writethrough or fallback |
| // strategy as the data is reasonably inexpensive to recompute |
| // on a restart, unlike the metadata_cache which has |
| // optimization results, and the payloads are all small (message |
| // InputInfo in ../rewriter/cached_result.proto). |
| server_context->set_filesystem_metadata_cache(shm_metadata_cache); |
| |
| // We checkpoint the shm cache when running with external caches, and |
| // restore it (to local shm only) on restart. Not checkpointing in this |
| // case would also be defensible, but the implementation complexity would |
| // be very high. For example, you might have two vhosts that both use the |
| // default-enabled shared memory metadata cache, but only one has an |
| // external cache enabled. |
| } else { |
| // For persistence across restarts, we checkpoint the shm_metadata_cache |
| // to disk every so often, and restore it on restart. This means we don't |
| // need to write most objects through to the file cache, just ones too big |
| // to store in the SHM cache. |
| FallbackCache* metadata_fallback = |
| new FallbackCache( |
| shm_metadata_cache, file_cache, |
| shm_metadata_cache_info->cache_backend->MaxValueSize(), |
| factory_->message_handler()); |
| // SharedMemCache uses hash-produced fixed size keys internally, so its |
| // value size limit isn't affected by key length changes. |
| metadata_fallback->set_account_for_key_size(false); |
| server_context->DeleteCacheOnDestruction(metadata_fallback); |
| metadata_l2 = metadata_fallback; |
| |
| // TODO(jmarantz): do we really want to use the shm-cache as a |
| // pcache? The potential for inconsistent data across a |
| // multi-server setup seems like it could give confusing results. |
| } |
| } else { |
| l1_size_limit = config->lru_cache_byte_limit(); |
| metadata_l1 = lru_cache; // may be NULL |
| metadata_l2 = http_l2; // external or file cache. |
| } |
| |
| CacheInterface* metadata_cache; |
| |
| if (metadata_l1 != NULL) { |
| WriteThroughCache* write_through_cache = new WriteThroughCache( |
| metadata_l1, metadata_l2); |
| server_context->DeleteCacheOnDestruction(write_through_cache); |
| write_through_cache->set_cache1_limit(l1_size_limit); |
| metadata_cache = write_through_cache; |
| } else { |
| metadata_cache = metadata_l2; |
| } |
| |
| // TODO(jmarantz): We probably want to store HTTP cache compressed |
| // even without this flag, but we should do it differently, storing |
| // only the content compressed and putting in content-encoding:gzip |
| // so that mod_gzip doesn't have to recompress on every request. |
| if (property_store_cache == NULL) { |
| property_store_cache = metadata_l2; |
| } |
| if (config->compress_metadata_cache()) { |
| metadata_cache = new CompressedCache(metadata_cache, stats); |
| server_context->DeleteCacheOnDestruction(metadata_cache); |
| property_store_cache = new CompressedCache(property_store_cache, stats); |
| server_context->DeleteCacheOnDestruction(property_store_cache); |
| } |
| DCHECK(property_store_cache->IsBlocking()); |
| server_context->MakePagePropertyCache( |
| server_context->CreatePropertyStore(property_store_cache)); |
| server_context->set_metadata_cache(metadata_cache); |
| SetupPcacheCohorts(server_context, enable_property_cache); |
| SystemServerContext* system_server_context = |
| dynamic_cast<SystemServerContext*>(server_context); |
| system_server_context->SetCachePath(caches_for_path); |
| } |
| |
| void SystemCaches::RegisterConfig(SystemRewriteOptions* config) { |
| // Should fill in path_cache_map_. |
| GetCache(config); |
| // Should fill in external_caches_map_, memcache_servers_, and |
| // redis_servers_. |
| NewExternalCache(config); |
| |
| // GetShmMetadataCacheOrDefault will create a default cache if one is needed |
| // and doesn't exist yet. |
| GetShmMetadataCacheOrDefault(config); |
| } |
| |
| void SystemCaches::RootInit() { |
| const SystemRewriteOptions* global_options = |
| SystemRewriteOptions::DynamicCast(factory_->default_options()); |
| for (MetadataShmCacheMap::iterator p = metadata_shm_caches_.begin(), |
| e = metadata_shm_caches_.end(); p != e; ++p) { |
| MetadataShmCacheInfo* cache_info = p->second; |
| |
| // If we're using the default shared memory cache and different vhosts have |
| // set the FileCachePath differently, then where should we store the |
| // snapshots? There's no good answer here, because the FileCachePath is the |
| // only path like this we require people to specify. To handle this, |
| // SetPathForSnapshots expects to be called multiple times and uses the file |
| // cache matching its configured path, or if none match then whichever value |
| // comes first alphabetically. |
| // |
| // We don't need separate directories here for explicit and default shm |
| // caches, because if you set an explicit shm cache for a file cache path we |
| // don't create a default one for vhosts using that path. |
| // |
| // Note: we couldn't set the FileCache when constructing the shm cache |
| // because at the time we parsed the directive to construct the shm cache |
| // we might not have seen the file cache directive yet. |
| // |
| // Tell the shm cache about file caches and let it pick one to use for |
| // checkpointing. |
| for (PathCacheMap::iterator q = path_cache_map_.begin(), |
| f = path_cache_map_.end(); q != f; ++q) { |
| FileCache* file_cache = q->second->file_cache_backend(); |
| // It's fine to call RegisterSnapshotFileCache multiple times: it |
| // considers all the inputs and picks the best one. |
| cache_info->cache_backend->RegisterSnapshotFileCache( |
| file_cache, |
| global_options->shm_metadata_cache_checkpoint_interval_sec()); |
| } |
| |
| if (cache_info->cache_backend->Initialize()) { |
| cache_info->initialized = true; |
| cache_info->cache_to_use = |
| new CacheStats(kShmCache, cache_info->cache_backend, |
| factory_->timer(), factory_->statistics()); |
| factory_->TakeOwnership(cache_info->cache_to_use); |
| } else { |
| factory_->message_handler()->Message( |
| kWarning, "Unable to initialize shared memory cache: %s.", |
| p->first.c_str()); |
| cache_info->cache_backend = NULL; |
| cache_info->cache_to_use = NULL; |
| } |
| } |
| |
| for (PathCacheMap::iterator p = path_cache_map_.begin(), |
| e = path_cache_map_.end(); p != e; ++p) { |
| SystemCachePath* cache = p->second; |
| cache->RootInit(); |
| } |
| } |
| |
| void SystemCaches::ChildInit() { |
| is_root_process_ = false; |
| |
| slow_worker_.reset( |
| new SlowWorker("slow_work_thread", factory_->thread_system())); |
| for (MetadataShmCacheMap::iterator p = metadata_shm_caches_.begin(), |
| e = metadata_shm_caches_.end(); p != e; ++p) { |
| MetadataShmCacheInfo* cache_info = p->second; |
| if ((cache_info->cache_backend != NULL) && |
| !cache_info->cache_backend->Attach()) { |
| factory_->message_handler()->Message( |
| kWarning, "Unable to attach to shared memory cache: %s.", |
| p->first.c_str()); |
| delete cache_info->cache_backend; |
| cache_info->cache_backend = NULL; |
| cache_info->cache_to_use = NULL; |
| } |
| } |
| |
| for (PathCacheMap::iterator p = path_cache_map_.begin(), |
| e = path_cache_map_.end(); p != e; ++p) { |
| SystemCachePath* cache = p->second; |
| cache->ChildInit(slow_worker_.get()); |
| } |
| |
| // TODO(yeputons): think about moving StartUp() to some base class of |
| // RedisCache and AprMemCache and collapsing these two loops into one. |
| for (int i = 0, n = memcache_servers_.size(); i < n; ++i) { |
| AprMemCache* mem_cache = memcache_servers_[i]; |
| // TODO(yeputons): looks like this line does not really "connect", but just |
| // loads list of servers into apr_memcached and connects later. Maybe the |
| // name should be fixed. |
| if (!mem_cache->Connect()) { |
| factory_->message_handler()->MessageS(kError, "Memory cache failed"); |
| abort(); // TODO(jmarantz): is there a better way to exit? |
| } |
| } |
| |
| for (RedisCache* redis_cache : redis_servers_) { |
| redis_cache->StartUp(); |
| } |
| } |
| |
| void SystemCaches::StopCacheActivity() { |
| if (is_root_process_) { |
| // No caches used in root process, so nothing to shutdown. We could run the |
| // shutdown code anyway, except that starts a thread which is unsafe to do |
| // in a forking server like Nginx. |
| return; |
| } |
| |
| // Iterate through the map of ExternalCacheInterface objects constructed and |
| // try to stop pending operations on async caches. Note that these are not |
| // typically AprMemCache* or RedisCache* objects, but instead are a hierarchy |
| // of CacheStats*, CacheBatcher*, AsyncCache*, all of which must be stopped. |
| for (auto item : external_caches_map_) { |
| ExternalCacheInterfaces cache = item.second; |
| cache.async->ShutDown(); |
| } |
| |
| // TODO(morlovich): Also shutdown shm caches |
| } |
| |
| void SystemCaches::InitStats(Statistics* statistics) { |
| AprMemCache::InitStats(statistics); |
| FileCache::InitStats(statistics); |
| CacheStats::InitStats(SystemCachePath::kFileCache, statistics); |
| CacheStats::InitStats(SystemCachePath::kLruCache, statistics); |
| CacheStats::InitStats(kShmCache, statistics); |
| CacheStats::InitStats(kMemcachedAsync, statistics); |
| CacheStats::InitStats(kMemcachedBlocking, statistics); |
| CacheStats::InitStats(kRedisAsync, statistics); |
| CacheStats::InitStats(kRedisBlocking, statistics); |
| CompressedCache::InitStats(statistics); |
| PurgeContext::InitStats(statistics); |
| RedisCache::InitStats(statistics); |
| } |
| |
| void SystemCaches::PrintCacheStats(StatFlags flags, GoogleString* out) { |
| // We don't want to print this in per-vhost info since it would leak |
| // all the declared caches. |
| if (flags & kGlobalView) { |
| for (MetadataShmCacheMap::iterator p = metadata_shm_caches_.begin(), |
| e = metadata_shm_caches_.end(); p != e; ++p) { |
| MetadataShmCacheInfo* cache_info = p->second; |
| if (cache_info->cache_backend != NULL) { |
| StrAppend(out, "\nShared memory metadata cache '", p->first, |
| "' statistics:\n"); |
| StringWriter writer(out); |
| writer.Write(cache_info->cache_backend->DumpStats(), |
| factory_->message_handler()); |
| } |
| } |
| } |
| |
| if (flags & kIncludeMemcached) { |
| for (int i = 0, n = memcache_servers_.size(); i < n; ++i) { |
| AprMemCache* mem_cache = memcache_servers_[i]; |
| if (!mem_cache->GetStatus(out)) { |
| StrAppend(out, "\nError getting memcached server status for ", |
| mem_cache->cluster_spec().ToString()); |
| } |
| } |
| } |
| |
| if (flags & kIncludeRedis) { |
| for (RedisCache* redis : redis_servers_) { |
| // We can have partial failures, where some cluster machines give errors |
| // and others don't, so have GetStatus handle error reporting. |
| redis->GetStatus(out); |
| } |
| } |
| } |
| |
| } // namespace net_instaweb |