| /* |
| * 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: morlovich@google.com (Maksim Orlovich) |
| // Unit tests for common cache configuration code. |
| |
| #include "pagespeed/system/system_caches.h" |
| |
| #include <cstdlib> |
| #include <vector> |
| #include <sys/socket.h> |
| |
| #include "apr_poll.h" |
| #include "apr_version.h" |
| #include "apr_pools.h" |
| #include "apr_thread_proc.h" |
| #include "base/logging.h" |
| #include "net/instaweb/http/public/async_fetch.h" |
| #include "net/instaweb/http/public/http_cache.h" |
| #include "net/instaweb/http/public/http_value.h" |
| #include "net/instaweb/http/public/request_context.h" |
| #include "net/instaweb/rewriter/public/custom_rewrite_test_base.h" |
| #include "net/instaweb/rewriter/public/rewrite_driver.h" |
| #include "net/instaweb/rewriter/public/rewrite_test_base.h" |
| #include "net/instaweb/rewriter/public/server_context.h" |
| #include "net/instaweb/rewriter/public/test_rewrite_driver_factory.h" |
| #include "pagespeed/system/admin_site.h" |
| #include "pagespeed/system/apr_mem_cache.h" |
| #include "pagespeed/system/system_cache_path.h" |
| #include "pagespeed/system/system_rewrite_options.h" |
| #include "pagespeed/system/system_server_context.h" |
| #include "net/instaweb/util/public/cache_property_store.h" |
| #include "net/instaweb/util/public/property_cache.h" |
| #include "net/instaweb/util/public/property_store.h" |
| #include "pagespeed/kernel/base/abstract_shared_mem.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/md5_hasher.h" |
| #include "pagespeed/kernel/base/mem_file_system.h" |
| #include "pagespeed/kernel/base/message_handler.h" |
| #include "pagespeed/kernel/base/mock_message_handler.h" |
| #include "pagespeed/kernel/base/named_lock_manager.h" |
| #include "pagespeed/kernel/base/named_lock_tester.h" |
| #include "pagespeed/kernel/base/null_shared_mem.h" |
| #include "pagespeed/kernel/base/scoped_ptr.h" |
| #include "pagespeed/kernel/base/shared_string.h" |
| #include "pagespeed/kernel/base/stack_buffer.h" |
| #include "pagespeed/kernel/base/stl_util.h" |
| #include "pagespeed/kernel/base/thread_system.h" |
| #include "pagespeed/kernel/base/timer.h" |
| #include "pagespeed/kernel/cache/async_cache.h" |
| #include "pagespeed/kernel/cache/cache_batcher.h" |
| #include "pagespeed/kernel/cache/cache_interface.h" |
| #include "pagespeed/kernel/cache/cache_spammer.h" |
| #include "pagespeed/kernel/cache/cache_stats.h" |
| #include "pagespeed/kernel/cache/cache_test_base.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/lru_cache.h" |
| #include "pagespeed/kernel/cache/threadsafe_cache.h" |
| #include "pagespeed/kernel/cache/write_through_cache.h" |
| #include "pagespeed/kernel/http/content_type.h" |
| #include "pagespeed/kernel/http/request_headers.h" |
| #include "pagespeed/kernel/http/response_headers.h" |
| #include "pagespeed/kernel/sharedmem/inprocess_shared_mem.h" |
| #include "pagespeed/kernel/sharedmem/shared_mem_lock_manager.h" |
| #include "pagespeed/kernel/thread/scheduler_based_abstract_lock.h" |
| #include "pagespeed/kernel/thread/worker_test_base.h" |
| #include "pagespeed/kernel/util/file_system_lock_manager.h" |
| #include "pagespeed/kernel/util/platform.h" |
| #include "pagespeed/kernel/util/simple_random.h" |
| |
| namespace net_instaweb { |
| |
| namespace { |
| |
| const char kCachePath[] = "/mem/path/"; |
| const char kAltCachePath[] = "/mem/path_alt/"; |
| const char kAltCachePath2[] = "/mem/path_alt2/"; |
| const char kUrl1[] = "http://example.com/a.css"; |
| const char kUrl2[] = "http://example.com/b.css"; |
| |
| class SystemServerContextNoProxyHtml : public SystemServerContext { |
| public: |
| explicit SystemServerContextNoProxyHtml(RewriteDriverFactory* factory) |
| : SystemServerContext(factory, "fake_hostname", 80 /* fake port */) { |
| } |
| |
| virtual bool ProxiesHtml() const { return false; } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SystemServerContextNoProxyHtml); |
| }; |
| |
| class SystemCachesTest : public CustomRewriteTestBase<SystemRewriteOptions> { |
| public: |
| void PurgeDone(bool success) { |
| purge_done_ = true; |
| purge_success_ = success; |
| } |
| |
| void MemCacheStressTestHelper(bool do_deletes) { |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(0); |
| options_->set_memcached_servers(MemCachedServerSpec()); |
| options_->set_memcached_threads(1); |
| options_->set_default_shared_memory_cache_kb(0); |
| options_->set_compress_metadata_cache(false); |
| PrepareWithConfig(options_.get()); |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| CacheInterface* cache = server_context->metadata_cache(); |
| SimpleRandom random(new NullMutex); |
| GoogleString value = random.GenerateHighEntropyString(20000); |
| CacheSpammer::RunTests(4 /* num_threads */, |
| 200 /* iters */, |
| 200 /* inserts */, |
| false /* expecting_evictions */, |
| do_deletes, |
| value.c_str(), |
| cache, |
| thread_system_.get()); |
| } |
| |
| apr_size_t get_fake_memcached_port() { |
| return fake_memcached_port_; |
| } |
| |
| void set_fake_memcached_port(apr_size_t port) { |
| fake_memcached_port_ = port; |
| } |
| |
| protected: |
| static const int kThreadLimit = 3; |
| static const int kUsableMetadataCacheSize = 8 * 1024; |
| apr_size_t fake_memcached_port_; |
| |
| // Helper that blocks for async cache lookups. |
| class BlockingCallback : public CacheInterface::Callback { |
| public: |
| explicit BlockingCallback(ThreadSystem* threads) |
| : sync_(threads), result_(CacheInterface::kNotFound) {} |
| |
| CacheInterface::KeyState result() const { return result_; } |
| GoogleString value() const { return value_; } |
| |
| void Block() { |
| sync_.Wait(); |
| } |
| |
| protected: |
| virtual void Done(CacheInterface::KeyState state) { |
| result_ = state; |
| CacheInterface::Callback::value()->Value().CopyToString(&value_); |
| sync_.Notify(); |
| } |
| |
| private: |
| WorkerTestBase::SyncPoint sync_; |
| CacheInterface::KeyState result_; |
| GoogleString value_; |
| }; |
| |
| // Helper that blocks for async HTTP cache lookups. |
| class HTTPBlockingCallback : public HTTPCache::Callback { |
| public: |
| explicit HTTPBlockingCallback(ThreadSystem* threads) |
| : Callback(RequestContext::NewTestRequestContext(threads)), |
| sync_(threads) {} |
| |
| HTTPCache::FindResult result() const { return result_; } |
| GoogleString value() const { return value_; } |
| |
| void Block() { |
| sync_.Wait(); |
| } |
| |
| // RespectVary not relevant in this context. |
| virtual ResponseHeaders::VaryOption RespectVaryOnResources() const { |
| return ResponseHeaders::kRespectVaryOnResources; |
| } |
| |
| protected: |
| virtual void Done(HTTPCache::FindResult state) { |
| result_ = state; |
| if (state.status == HTTPCache::kFound) { |
| StringPiece contents; |
| http_value()->ExtractContents(&contents); |
| contents.CopyToString(&value_); |
| } |
| sync_.Notify(); |
| } |
| |
| virtual bool IsCacheValid(const GoogleString& key, |
| const ResponseHeaders& headers) { |
| return true; |
| } |
| |
| private: |
| WorkerTestBase::SyncPoint sync_; |
| HTTPCache::FindResult result_; |
| GoogleString value_; |
| }; |
| |
| class FakeMemcacheServerThread : public ThreadSystem::Thread { |
| public: |
| FakeMemcacheServerThread(SystemCachesTest* owner) |
| : Thread(owner->thread_system_.get(), "fake_memcache", |
| ThreadSystem::kJoinable), |
| owner_(owner), |
| mutex_(owner->thread_system_->NewMutex()), |
| notify_port_(mutex_->NewCondvar()) {} |
| virtual void Run() { |
| static const char kMessage[] = "blah\n"; |
| apr_size_t message_size = STATIC_STRLEN(kMessage); |
| char buf[kStackBufferSize]; |
| apr_size_t size = sizeof(buf) - 1; |
| apr_socket_t* accepted_socket = NULL; |
| apr_status_t status = apr_pool_create(&pool_, NULL); |
| ASSERT_EQ(APR_SUCCESS, status); |
| CreateSocketHelper(); |
| // Bind to an open port randomly. |
| // Blocks on connection from main thread. |
| apr_socket_bind(listening_socket_, sock_addr_); |
| apr_socket_listen(listening_socket_, SOMAXCONN); |
| apr_socket_accept(&accepted_socket, listening_socket_, pool_); |
| apr_socket_recv(accepted_socket, buf, &size); |
| apr_socket_send(accepted_socket, kMessage, &message_size); |
| apr_pool_destroy(pool_); |
| } |
| |
| void WaitForReady() { |
| ScopedMutex hold_lock(mutex_.get()); |
| while (owner_->get_fake_memcached_port() == 0) { |
| notify_port_->Wait(); |
| } |
| } |
| |
| private: |
| void CreateSocketHelper() { |
| ScopedMutex hold_lock(mutex_.get()); |
| SimpleRandom simple_random(owner_->thread_system_.get()->NewMutex()); |
| apr_size_t port_num; |
| const apr_int32_t kFamily = APR_INET; |
| sock_addr_ = NULL; |
| listening_socket_ = NULL; |
| apr_status_t status; |
| // Bind to an open port randomly. |
| do { |
| // Set port_num to 1024 - 65535 |
| port_num = (simple_random.Next() % 64511) + 1024; |
| status = |
| apr_sockaddr_info_get(&sock_addr_, 0, kFamily, port_num, 0, pool_); |
| if (status == APR_SUCCESS) { |
| status = apr_socket_create(&listening_socket_, sock_addr_->family, |
| SOCK_STREAM, APR_PROTO_TCP, pool_); |
| } |
| } while (status != APR_SUCCESS); |
| owner_->set_fake_memcached_port(port_num); |
| notify_port_->Signal(); |
| } |
| |
| SystemCachesTest* owner_; |
| scoped_ptr<ThreadSystem::CondvarCapableMutex> mutex_; |
| scoped_ptr<ThreadSystem::Condvar> notify_port_; |
| apr_pool_t* pool_; |
| apr_sockaddr_t* sock_addr_; |
| apr_socket_t* listening_socket_; |
| }; |
| |
| SystemCachesTest() |
| : thread_system_(Platform::CreateThreadSystem()), |
| options_(new SystemRewriteOptions(thread_system_.get())), |
| purge_done_(false), |
| purge_success_(false) { |
| shared_mem_.reset(new InProcessSharedMem(thread_system_.get())); |
| factory_->set_hasher(new MD5Hasher()); |
| Statistics* stats = factory()->statistics(); |
| SystemCaches::InitStats(stats); |
| SystemServerContext::InitStats(stats); |
| CacheStats::InitStats( |
| PropertyCache::GetStatsPrefix(RewriteDriver::kBeaconCohort), |
| stats); |
| CacheStats::InitStats( |
| PropertyCache::GetStatsPrefix(RewriteDriver::kDomCohort), |
| stats); |
| } |
| |
| virtual void SetUp() { |
| // TODO(jcrowell) factor out apr_initialize/terminate to share in static |
| // constructor similar to rewrite_test_base.cc. |
| apr_initialize(); |
| atexit(apr_terminate); |
| system_caches_.reset( |
| new SystemCaches(factory(), shared_mem_.get(), kThreadLimit)); |
| CustomRewriteTestBase<SystemRewriteOptions>::SetUp(); |
| } |
| |
| virtual void TearDown() { |
| system_caches_->StopCacheActivity(); |
| RewriteTestBase::TearDown(); |
| system_caches_->ShutDown(factory()->message_handler()); |
| } |
| |
| void PrepareWithConfig(SystemRewriteOptions* config) { |
| system_caches_->RegisterConfig(config); |
| system_caches_->RootInit(); |
| // pretend we fork here. |
| system_caches_->ChildInit(); |
| } |
| |
| // Takes ownership of config. |
| SystemServerContext* SetupServerContext(SystemRewriteOptions* config) { |
| scoped_ptr<SystemServerContext> server_context( |
| new SystemServerContextNoProxyHtml(factory())); |
| server_context->reset_global_options(config); |
| server_context->set_statistics(factory()->statistics()); |
| server_context->set_timer(factory()->timer()); |
| system_caches_->SetupCaches(server_context.get(), |
| true /* enable_property_cache */); |
| |
| // Sanity-check that the two caches work. |
| TestPut(server_context->metadata_cache(), "a", "b"); |
| TestGet(server_context->metadata_cache(), "a", |
| CacheInterface::kAvailable, "b"); |
| |
| TestHttpPut(server_context->http_cache(), "http://www.example.com", |
| "fragment", "a"); |
| TestHttpGet(server_context->http_cache(), "http://www.example.com", |
| "fragment", kFoundResult, "a"); |
| return server_context.release(); |
| } |
| |
| void TestPut(CacheInterface* cache, StringPiece key, StringPiece value) { |
| GoogleString value_copy; |
| value.CopyToString(&value_copy); |
| SharedString shared_value; |
| shared_value.SwapWithString(&value_copy); |
| cache->Put(key.as_string(), &shared_value); |
| } |
| |
| void TestGet(CacheInterface* cache, StringPiece key, |
| CacheInterface::KeyState expected_result, |
| StringPiece expected_value) { |
| BlockingCallback callback(thread_system_.get()); |
| cache->Get(key.as_string(), &callback); |
| callback.Block(); |
| EXPECT_EQ(expected_result, callback.result()); |
| EXPECT_EQ(expected_value, callback.value()); |
| } |
| |
| void TestHttpPut(HTTPCache* cache, StringPiece key, |
| StringPiece fragment, StringPiece value) { |
| ResponseHeaders headers; |
| SetDefaultLongCacheHeaders(&kContentTypeText, &headers); |
| cache->Put(key.as_string(), fragment.as_string(), |
| RequestHeaders::Properties(), |
| ResponseHeaders::kRespectVaryOnResources, &headers, value, |
| factory()->message_handler()); |
| } |
| |
| void TestHttpGet(HTTPCache* cache, StringPiece key, StringPiece fragment, |
| HTTPCache::FindResult expected_state, |
| StringPiece expected_value) { |
| HTTPBlockingCallback callback(thread_system_.get()); |
| cache->Find(key.as_string(), fragment.as_string(), |
| factory()->message_handler(), &callback); |
| callback.Block(); |
| EXPECT_EQ(expected_state, callback.result()); |
| EXPECT_EQ(expected_value, callback.value()); |
| } |
| |
| // Returns empty string if not enabled. Tests should exit in that case. |
| GoogleString MemCachedServerSpec() { |
| if (server_spec_.empty()) { |
| // This matches the logic in apr_mem_cache_test. |
| const char* port_string = getenv("MEMCACHED_PORT"); |
| if (port_string == NULL) { |
| LOG(ERROR) << "AprMemCache tests are skipped because env var " |
| << "$MEMCACHED_PORT is not set. Set that to the port " |
| << "number where memcached is running to enable the " |
| << "tests. See install/run_program_with_memcached.sh"; |
| // Does not fail the test. |
| return ""; |
| } |
| server_spec_ = StrCat("localhost:", port_string); |
| } |
| return server_spec_; |
| } |
| |
| // Unwraps any wrapper cache objects. |
| CacheInterface* SkipWrappers(CacheInterface* in) { |
| CacheInterface* backend = in->Backend(); |
| if (backend != in) { |
| return SkipWrappers(backend); |
| } |
| return in; |
| } |
| |
| void TestBasicMemCacheAndNoLru(int num_threads_specified, |
| int num_threads_expected) { |
| if (MemCachedServerSpec().empty()) { |
| return; |
| } |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(0); |
| options_->set_memcached_servers(MemCachedServerSpec()); |
| options_->set_memcached_threads(num_threads_specified); |
| options_->set_default_shared_memory_cache_kb(0); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| |
| GoogleString mem_cache; |
| if (num_threads_expected == 0) { |
| mem_cache = Batcher(Stats(SystemCaches::kMemcachedAsync, |
| AprMemCache::FormatName()), |
| 1, 1000); |
| } else { |
| mem_cache = Batcher(AsyncMemCacheWithStats(), num_threads_expected, 1000); |
| } |
| |
| EXPECT_STREQ( |
| Compressed(Fallback(mem_cache, Stats("file_cache", FileCacheName()))), |
| server_context->metadata_cache()->Name()); |
| EXPECT_STREQ( |
| HttpCache( |
| Fallback(mem_cache, Stats("file_cache", FileCacheName()))), |
| server_context->http_cache()->Name()); |
| ASSERT_TRUE(server_context->filesystem_metadata_cache() != NULL); |
| |
| // That the code that queries the FSMDC from the validator in RewriteContext |
| // does a Get and needs the response to be available inline. |
| EXPECT_TRUE(server_context->filesystem_metadata_cache()->IsBlocking()); |
| EXPECT_STREQ( |
| Fallback(BlockingMemCacheWithStats(), FileCacheWithStats()), |
| server_context->filesystem_metadata_cache()->Name()); |
| } |
| |
| // Wrapper functions to format expected cache descriptor strings with |
| // concise function calls exposing the cache structure via normal code |
| // indentation. |
| GoogleString WriteThrough(StringPiece l1, StringPiece l2) { |
| return WriteThroughCache::FormatName(l1, l2); |
| } |
| |
| GoogleString HttpCache(StringPiece cache) { |
| return HTTPCache::FormatName(cache); |
| } |
| |
| GoogleString Fallback(StringPiece small, StringPiece large) { |
| return FallbackCache::FormatName(small, large); |
| } |
| |
| GoogleString Batcher(StringPiece cache, int parallel, int max) { |
| return CacheBatcher::FormatName(cache, parallel, max); |
| } |
| |
| GoogleString Stats(StringPiece prefix, StringPiece cache) { |
| return CacheStats::FormatName(prefix, cache); |
| } |
| |
| GoogleString ThreadsafeLRU() { |
| return ThreadsafeCache::FormatName(LRUCache::FormatName()); |
| } |
| |
| GoogleString FileCacheName() { return FileCache::FormatName(); } |
| |
| GoogleString AsyncMemCacheWithStats() { |
| return Stats(SystemCaches::kMemcachedAsync, |
| AsyncCache::FormatName(AprMemCache::FormatName())); |
| } |
| |
| GoogleString BlockingMemCacheWithStats() { |
| return Stats(SystemCaches::kMemcachedBlocking, AprMemCache::FormatName()); |
| } |
| |
| GoogleString FileCacheWithStats() { |
| return Stats("file_cache", FileCacheName()); |
| } |
| |
| GoogleString Pcache(StringPiece cache) { |
| return CachePropertyStore::FormatName2( |
| RewriteDriver::kBeaconCohort, |
| Stats(PropertyCache::GetStatsPrefix(RewriteDriver::kBeaconCohort), |
| cache), |
| RewriteDriver::kDomCohort, |
| Stats(PropertyCache::GetStatsPrefix(RewriteDriver::kDomCohort), |
| cache)); |
| } |
| |
| GoogleString Compressed(StringPiece cache) { |
| return CompressedCache::FormatName(cache); |
| } |
| |
| SystemServerContext* PopulateCacheForPurgeTest() { |
| options_->set_file_cache_path(kCachePath); |
| SystemRewriteOptions* options = options_.get(); |
| PrepareWithConfig(options); |
| system_server_context_.reset(SetupServerContext(options_.release())); |
| HTTPCache* http_cache = system_server_context_->http_cache(); |
| MessageHandler* handler = message_handler(); |
| system_server_context_->set_message_handler(handler); |
| ResponseHeaders headers; |
| SetDefaultLongCacheHeaders(&kContentTypeText, &headers); |
| headers.ComputeCaching(); |
| RequestHeaders::Properties req_properties; |
| http_cache->Put(kUrl1, rewrite_driver_->CacheFragment(), req_properties, |
| ResponseHeaders::kRespectVaryOnResources, |
| &headers, "a value", handler); |
| http_cache->Put(kUrl2, rewrite_driver_->CacheFragment(), req_properties, |
| ResponseHeaders::kRespectVaryOnResources, |
| &headers, "b value", handler); |
| AdvanceTimeMs(1000); |
| HTTPValue value; |
| |
| // As expected, both kUrl1 and kUrl2 are valid after Put. |
| EXPECT_EQ(kFoundResult, HttpBlockingFindWithOptions( |
| options, kUrl1, http_cache, &value, &headers)); |
| EXPECT_EQ(kFoundResult, HttpBlockingFindWithOptions( |
| options, kUrl2, http_cache, &value, &headers)); |
| AdvanceTimeMs(1000); |
| return system_server_context_.get(); |
| } |
| |
| scoped_ptr<ThreadSystem> thread_system_; |
| scoped_ptr<AbstractSharedMem> shared_mem_; |
| scoped_ptr<SystemCaches> system_caches_; |
| scoped_ptr<SystemRewriteOptions> options_; |
| scoped_ptr<SystemServerContext> system_server_context_; |
| bool purge_done_; |
| bool purge_success_; |
| |
| private: |
| GoogleString server_spec_; // Set lazily by MemCachedServerSpec() |
| }; |
| |
| TEST_F(SystemCachesTest, BasicFileAndLruCache) { |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(100); |
| options_->set_default_shared_memory_cache_kb(0); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| EXPECT_STREQ(Compressed(WriteThrough(Stats("lru_cache", ThreadsafeLRU()), |
| FileCacheWithStats())), |
| server_context->metadata_cache()->Name()); |
| EXPECT_STREQ( |
| HttpCache( |
| WriteThrough( |
| Stats("lru_cache", ThreadsafeLRU()), |
| FileCacheWithStats())), |
| server_context->http_cache()->Name()); |
| EXPECT_TRUE(server_context->filesystem_metadata_cache() == NULL); |
| } |
| |
| TEST_F(SystemCachesTest, BasicFileOnlyCache) { |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(0); |
| options_->set_default_shared_memory_cache_kb(0); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| EXPECT_STREQ(Compressed(FileCacheWithStats()), |
| server_context->metadata_cache()->Name()); |
| EXPECT_STREQ(HttpCache(FileCacheWithStats()), |
| server_context->http_cache()->Name()); |
| EXPECT_TRUE(server_context->filesystem_metadata_cache() == NULL); |
| } |
| |
| TEST_F(SystemCachesTest, UnusableShmAndLru) { |
| // Test that we properly fallback when we can't create the shm cache |
| // due to too small a size given. |
| GoogleString error_msg; |
| EXPECT_FALSE( |
| system_caches_->CreateShmMetadataCache(kCachePath, 10, &error_msg)); |
| EXPECT_STREQ("Shared memory cache unusably small.", error_msg); |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(100); |
| options_->set_default_shared_memory_cache_kb(0); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| EXPECT_STREQ(Compressed(WriteThrough(Stats("lru_cache", ThreadsafeLRU()), |
| FileCacheWithStats())), |
| server_context->metadata_cache()->Name()); |
| EXPECT_STREQ( |
| HttpCache(WriteThrough( |
| Stats("lru_cache", ThreadsafeLRU()), |
| FileCacheWithStats())), |
| server_context->http_cache()->Name()); |
| EXPECT_TRUE(server_context->filesystem_metadata_cache() == NULL); |
| } |
| |
| TEST_F(SystemCachesTest, BasicShmAndLru) { |
| GoogleString error_msg; |
| EXPECT_TRUE(system_caches_->CreateShmMetadataCache( |
| kCachePath, kUsableMetadataCacheSize, &error_msg)); |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(100); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| // We don't use the LRU when shm cache is on. |
| EXPECT_STREQ(Compressed(Fallback(Stats("shm_cache", "SharedMemCache<64>"), |
| FileCacheWithStats())), |
| server_context->metadata_cache()->Name()); |
| // HTTP cache is unaffected. |
| EXPECT_STREQ( |
| HttpCache(WriteThrough(Stats("lru_cache", ThreadsafeLRU()), |
| FileCacheWithStats())), |
| server_context->http_cache()->Name()); |
| EXPECT_TRUE(server_context->filesystem_metadata_cache() == NULL); |
| } |
| |
| TEST_F(SystemCachesTest, BasicShmAndNoLru) { |
| GoogleString error_msg; |
| EXPECT_TRUE(system_caches_->CreateShmMetadataCache( |
| kCachePath, kUsableMetadataCacheSize, &error_msg)); |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(0); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| // We don't use the LRU when shm cache is on. |
| EXPECT_STREQ(Compressed(Fallback(Stats("shm_cache", "SharedMemCache<64>"), |
| FileCacheWithStats())), |
| server_context->metadata_cache()->Name()); |
| // HTTP cache is unaffected. |
| EXPECT_STREQ(HttpCache(FileCacheWithStats()), |
| server_context->http_cache()->Name()); |
| EXPECT_TRUE(server_context->filesystem_metadata_cache() == NULL); |
| } |
| |
| TEST_F(SystemCachesTest, DoubleShmCreate) { |
| // Proper error message on two creation attempts for the same name. |
| GoogleString error_msg; |
| EXPECT_TRUE( |
| system_caches_->CreateShmMetadataCache(kCachePath, |
| kUsableMetadataCacheSize, |
| &error_msg)); |
| EXPECT_FALSE( |
| system_caches_->CreateShmMetadataCache(kCachePath, |
| kUsableMetadataCacheSize, |
| &error_msg)); |
| EXPECT_STREQ(StrCat("Cache named ", kCachePath, " already exists."), |
| error_msg); |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(100); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| // We don't use the LRU when shm cache is on. |
| EXPECT_STREQ(Compressed(Fallback(Stats("shm_cache", "SharedMemCache<64>"), |
| FileCacheWithStats())), |
| server_context->metadata_cache()->Name()); |
| // HTTP cache is unaffected. |
| EXPECT_STREQ( |
| HttpCache(WriteThrough( |
| Stats("lru_cache", ThreadsafeLRU()), |
| FileCacheWithStats())), |
| server_context->http_cache()->Name()); |
| EXPECT_TRUE(server_context->filesystem_metadata_cache() == NULL); |
| } |
| |
| TEST_F(SystemCachesTest, BasicMemCachedAndLru) { |
| if (MemCachedServerSpec().empty()) { |
| return; |
| } |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(100); |
| options_->set_memcached_servers(MemCachedServerSpec()); |
| options_->set_default_shared_memory_cache_kb(0); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| EXPECT_STREQ(Compressed( |
| WriteThrough(Stats("lru_cache", ThreadsafeLRU()), |
| Fallback(Batcher(AsyncMemCacheWithStats(), 1, 1000), |
| FileCacheWithStats()))), |
| server_context->metadata_cache()->Name()); |
| EXPECT_STREQ( |
| HttpCache(WriteThrough( |
| Stats("lru_cache", ThreadsafeLRU()), |
| Fallback(Batcher(AsyncMemCacheWithStats(), 1, 1000), |
| FileCacheWithStats()))), |
| server_context->http_cache()->Name()); |
| ASSERT_TRUE(server_context->filesystem_metadata_cache() != NULL); |
| EXPECT_TRUE(server_context->filesystem_metadata_cache()->IsBlocking()); |
| EXPECT_STREQ( |
| Fallback(BlockingMemCacheWithStats(), FileCacheWithStats()), |
| server_context->filesystem_metadata_cache()->Name()); |
| } |
| |
| TEST_F(SystemCachesTest, BasicMemCachedAndNoLru_0_Threads) { |
| TestBasicMemCacheAndNoLru(0, 0); |
| } |
| |
| TEST_F(SystemCachesTest, BasicMemCachedAndNoLru_1_Thread) { |
| TestBasicMemCacheAndNoLru(1, 1); |
| } |
| |
| TEST_F(SystemCachesTest, BasicMemCachedAndNoLru_2_Threads) { |
| TestBasicMemCacheAndNoLru(2, 1); // Clamp to 1. |
| } |
| |
| TEST_F(SystemCachesTest, BasicMemCachedLruShm) { |
| if (MemCachedServerSpec().empty()) { |
| return; |
| } |
| |
| GoogleString error_msg; |
| EXPECT_TRUE(system_caches_->CreateShmMetadataCache( |
| kCachePath, kUsableMetadataCacheSize, &error_msg)); |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(100); |
| options_->set_memcached_servers(MemCachedServerSpec()); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| // For metadata, we fallback to memcached behind shmcache. |
| EXPECT_STREQ( |
| Compressed(WriteThrough( |
| Stats("shm_cache", SharedMemCache<64>::FormatName()), |
| Fallback(Batcher(AsyncMemCacheWithStats(), 1, 1000), |
| FileCacheWithStats()))), |
| server_context->metadata_cache()->Name()); |
| EXPECT_STREQ( |
| HttpCache(WriteThrough( |
| Stats("lru_cache", ThreadsafeLRU()), |
| Fallback(Batcher(AsyncMemCacheWithStats(), 1, 1000), |
| FileCacheWithStats()))), |
| server_context->http_cache()->Name()); |
| } |
| |
| TEST_F(SystemCachesTest, BasicMemCachedShmNoLru) { |
| if (MemCachedServerSpec().empty()) { |
| return; |
| } |
| |
| GoogleString error_msg; |
| EXPECT_TRUE(system_caches_->CreateShmMetadataCache( |
| kCachePath, kUsableMetadataCacheSize, &error_msg)); |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(0); |
| options_->set_memcached_servers(MemCachedServerSpec()); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| EXPECT_STREQ( |
| Compressed( |
| WriteThrough( |
| Stats("shm_cache", "SharedMemCache<64>"), |
| Fallback(Batcher(AsyncMemCacheWithStats(), 1, 1000), |
| FileCacheWithStats()))), |
| server_context->metadata_cache()->Name()); |
| EXPECT_STREQ( |
| HttpCache( |
| Fallback(Batcher(AsyncMemCacheWithStats(), 1, 1000), |
| FileCacheWithStats())), |
| server_context->http_cache()->Name()); |
| ASSERT_TRUE(server_context->filesystem_metadata_cache() != NULL); |
| EXPECT_TRUE(server_context->filesystem_metadata_cache()->IsBlocking()); |
| EXPECT_STREQ( |
| Stats("shm_cache", "SharedMemCache<64>"), |
| server_context->filesystem_metadata_cache()->Name()); |
| } |
| |
| TEST_F(SystemCachesTest, BasicFileLockManager) { |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(100); |
| PrepareWithConfig(options_.get()); |
| NamedLockManager* named_locks = system_caches_->GetLockManager( |
| options_.get()); |
| EXPECT_TRUE(named_locks != NULL); |
| EXPECT_TRUE(dynamic_cast<FileSystemLockManager*>(named_locks) != NULL); |
| } |
| |
| TEST_F(SystemCachesTest, BasicShmLockManager) { |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(true); |
| options_->set_lru_cache_kb_per_process(100); |
| PrepareWithConfig(options_.get()); |
| NamedLockManager* named_locks = system_caches_->GetLockManager( |
| options_.get()); |
| EXPECT_TRUE(named_locks != NULL); |
| EXPECT_TRUE(dynamic_cast<SharedMemLockManager*>(named_locks) != NULL); |
| } |
| |
| TEST_F(SystemCachesTest, FileShare) { |
| // [0], [1], share path, [2] doesn't. |
| std::vector<SystemRewriteOptions*> configs; |
| for (int i = 0; i < 3; ++i) { |
| SystemRewriteOptions* config = options_->NewOptions(); |
| config->set_file_cache_path((i == 2) ? kCachePath : kAltCachePath); |
| config->set_default_shared_memory_cache_kb(0); |
| system_caches_->RegisterConfig(config); |
| configs.push_back(config); |
| } |
| system_caches_->RootInit(); |
| // pretend we fork here. |
| system_caches_->ChildInit(); |
| |
| std::vector<ServerContext*> servers; |
| for (int i = 0; i < 3; ++i) { |
| servers.push_back(SetupServerContext(configs[i])); |
| } |
| |
| TestPut(servers[0]->metadata_cache(), "b", "value"); |
| TestGet(servers[0]->metadata_cache(), "b", |
| CacheInterface::kAvailable, "value"); |
| TestGet(servers[1]->metadata_cache(), "b", |
| CacheInterface::kAvailable, "value"); |
| TestGet(servers[2]->metadata_cache(), "b", CacheInterface::kNotFound, ""); |
| |
| TestHttpPut(servers[0]->http_cache(), "http://b.org", "fragment", "value"); |
| TestHttpGet(servers[0]->http_cache(), "http://b.org", "fragment", |
| kFoundResult, "value"); |
| TestHttpGet(servers[1]->http_cache(), "http://b.org", "fragment", |
| kFoundResult, "value"); |
| TestHttpGet(servers[2]->http_cache(), "http://b.org", "fragment", |
| kNotFoundResult, ""); |
| |
| // Lock managers have similar sharing semantics |
| scoped_ptr<NamedLock> lock0( |
| system_caches_->GetLockManager(configs[0])->CreateNamedLock("a")); |
| scoped_ptr<NamedLock> lock1( |
| system_caches_->GetLockManager(configs[1])->CreateNamedLock("a")); |
| scoped_ptr<NamedLock> lock2( |
| system_caches_->GetLockManager(configs[2])->CreateNamedLock("a")); |
| NamedLockTester tester(thread_system_.get()); |
| EXPECT_TRUE(tester.TryLock(lock0.get())); |
| EXPECT_FALSE(tester.TryLock(lock1.get())); |
| EXPECT_TRUE(tester.TryLock(lock2.get())); |
| lock0->Unlock(); |
| EXPECT_TRUE(tester.TryLock(lock1.get())); |
| |
| STLDeleteElements(&servers); |
| } |
| |
| TEST_F(SystemCachesTest, ShmShare) { |
| // For SHM metadata cache, sharing is based on explicit segment names/ |
| // [0], [1], share, [2] doesn't. |
| GoogleString error_msg; |
| EXPECT_TRUE(system_caches_->CreateShmMetadataCache( |
| kCachePath, kUsableMetadataCacheSize, &error_msg)); |
| |
| EXPECT_TRUE(system_caches_->CreateShmMetadataCache( |
| kAltCachePath, kUsableMetadataCacheSize, &error_msg)); |
| |
| std::vector<SystemRewriteOptions*> configs; |
| for (int i = 0; i < 3; ++i) { |
| SystemRewriteOptions* config = options_->NewOptions(); |
| config->set_file_cache_path((i == 2) ? kAltCachePath : kCachePath); |
| system_caches_->RegisterConfig(config); |
| configs.push_back(config); |
| } |
| |
| system_caches_->RootInit(); |
| // pretend we fork here. |
| system_caches_->ChildInit(); |
| |
| std::vector<ServerContext*> servers; |
| for (int i = 0; i < 3; ++i) { |
| servers.push_back(SetupServerContext(configs[i])); |
| EXPECT_STREQ(Compressed(Fallback(Stats("shm_cache", "SharedMemCache<64>"), |
| FileCacheWithStats())), |
| servers[i]->metadata_cache()->Name()); |
| } |
| |
| // This is only about metadata cache. |
| TestPut(servers[0]->metadata_cache(), "b", "value"); |
| TestGet(servers[0]->metadata_cache(), "b", |
| CacheInterface::kAvailable, "value"); |
| TestGet(servers[1]->metadata_cache(), "b", |
| CacheInterface::kAvailable, "value"); |
| TestGet(servers[2]->metadata_cache(), "b", CacheInterface::kNotFound, ""); |
| |
| STLDeleteElements(&servers); |
| } |
| |
| TEST_F(SystemCachesTest, ShmDefault) { |
| // Unless a cache is explicitly defined or the default is disabled with |
| // set_default_shared_memory_cache_kb(0), use the default. Unlike explicitly |
| // configured shared memory caches, default ones write through to an L2 (file |
| // or memcache). |
| // |
| // [0] and [1] share the default, [2] has one separately configured. All |
| // three have different file cache paths. |
| GoogleString error_msg; |
| EXPECT_TRUE(system_caches_->CreateShmMetadataCache( |
| kAltCachePath2, kUsableMetadataCacheSize, &error_msg)); |
| |
| std::vector<SystemRewriteOptions*> configs; |
| const char* kPaths[] = {kCachePath, kAltCachePath, kAltCachePath2}; |
| for (int i = 0; i < 3; ++i) { |
| SystemRewriteOptions* config = options_->NewOptions(); |
| config->set_file_cache_path(kPaths[i]); |
| system_caches_->RegisterConfig(config); |
| configs.push_back(config); |
| } |
| |
| // No shm metadata cache was created for [0]'s kCachePath or [1]'s |
| // kAltCachePath, only [2]'s kAltCachePath2. So [0] and [1] will share the |
| // default. |
| |
| system_caches_->RootInit(); |
| // pretend we fork here. |
| system_caches_->ChildInit(); |
| |
| std::vector<ServerContext*> servers; |
| for (int i = 0; i < 3; ++i) { |
| servers.push_back(SetupServerContext(configs[i])); |
| } |
| EXPECT_STREQ(Compressed(WriteThrough(Stats("shm_cache", "SharedMemCache<64>"), |
| FileCacheWithStats())), |
| servers[0]->metadata_cache()->Name()); |
| EXPECT_STREQ(Compressed(WriteThrough(Stats("shm_cache", "SharedMemCache<64>"), |
| FileCacheWithStats())), |
| servers[1]->metadata_cache()->Name()); |
| EXPECT_STREQ(Compressed(Fallback(Stats("shm_cache", "SharedMemCache<64>"), |
| FileCacheWithStats())), |
| servers[2]->metadata_cache()->Name()); |
| |
| |
| // This is only about metadata cache. |
| TestPut(servers[0]->metadata_cache(), "b", "value"); |
| TestGet(servers[0]->metadata_cache(), "b", |
| CacheInterface::kAvailable, "value"); |
| TestGet(servers[1]->metadata_cache(), "b", |
| CacheInterface::kAvailable, "value"); |
| TestGet(servers[2]->metadata_cache(), "b", CacheInterface::kNotFound, ""); |
| |
| STLDeleteElements(&servers); |
| } |
| |
| TEST_F(SystemCachesTest, MemCachedShare) { |
| if (MemCachedServerSpec().empty()) { |
| return; |
| } |
| |
| // Just share 3 memcached clients for the same server (so we don't |
| // need 2 servers for the test) |
| |
| std::vector<SystemRewriteOptions*> configs; |
| for (int i = 0; i < 3; ++i) { |
| SystemRewriteOptions* config = options_->NewOptions(); |
| config->set_file_cache_path(kCachePath); |
| config->set_memcached_servers(MemCachedServerSpec()); |
| config->set_default_shared_memory_cache_kb(0); |
| system_caches_->RegisterConfig(config); |
| configs.push_back(config); |
| } |
| |
| system_caches_->RootInit(); |
| // pretend we fork here. |
| system_caches_->ChildInit(); |
| |
| std::vector<ServerContext*> servers; |
| for (int i = 0; i < 3; ++i) { |
| servers.push_back(SetupServerContext(configs[i])); |
| EXPECT_STREQ( |
| Compressed(Fallback(Batcher(AsyncMemCacheWithStats(), 1, 1000), |
| FileCacheWithStats())), |
| servers[i]->metadata_cache()->Name()); |
| |
| EXPECT_STREQ(Pcache(Compressed(Fallback(BlockingMemCacheWithStats(), |
| FileCacheWithStats()))), |
| servers[i]->page_property_cache()->property_store()->Name()); |
| } |
| |
| // Metadata + HTTP cache will end up shared |
| TestPut(servers[0]->metadata_cache(), "b", "value"); |
| TestGet(servers[0]->metadata_cache(), "b", |
| CacheInterface::kAvailable, "value"); |
| TestGet(servers[1]->metadata_cache(), "b", |
| CacheInterface::kAvailable, "value"); |
| TestGet(servers[2]->metadata_cache(), "b", |
| CacheInterface::kAvailable, "value"); |
| |
| TestHttpPut(servers[0]->http_cache(), "http://b.org", "fragment", "value"); |
| TestHttpGet(servers[0]->http_cache(), "http://b.org", "fragment", |
| kFoundResult, "value"); |
| TestHttpGet(servers[1]->http_cache(), "http://b.org", "fragment", |
| kFoundResult, "value"); |
| TestHttpGet(servers[2]->http_cache(), "http://b.org", "fragment", |
| kFoundResult, "value"); |
| |
| STLDeleteElements(&servers); |
| } |
| |
| TEST_F(SystemCachesTest, FileCacheSettings) { |
| // Make sure we apply the various file cache settings right. |
| options_->set_file_cache_path(kCachePath); |
| options_->set_file_cache_clean_interval_ms(3 * Timer::kHourMs); |
| options_->set_file_cache_clean_size_kb(1024); |
| options_->set_file_cache_clean_inode_limit(50000); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(0); |
| options_->set_default_shared_memory_cache_kb(0); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| EXPECT_STREQ(Compressed(FileCacheWithStats()), |
| server_context->metadata_cache()->Name()); |
| EXPECT_STREQ(HttpCache(FileCacheWithStats()), |
| server_context->http_cache()->Name()); |
| EXPECT_STREQ(Pcache(Compressed(FileCacheWithStats())), |
| server_context->page_property_cache()->property_store()->Name()); |
| |
| FileCache* file_cache = dynamic_cast<FileCache*>( |
| SkipWrappers(server_context->metadata_cache())); |
| ASSERT_TRUE(file_cache != NULL); |
| EXPECT_EQ(kCachePath, file_cache->path()); |
| EXPECT_EQ(3 * Timer::kHourMs, file_cache->cache_policy()->clean_interval_ms); |
| // Note: this is in bytes, the setting is in kb. |
| EXPECT_EQ(1024*1024, file_cache->cache_policy()->target_size_bytes); |
| EXPECT_EQ(50000, file_cache->cache_policy()->target_inode_count); |
| EXPECT_TRUE(file_cache->worker() != NULL); |
| } |
| |
| TEST_F(SystemCachesTest, LruCacheSettings) { |
| // Test that we apply LRU cache settings right. |
| options_->set_file_cache_path(kCachePath); |
| options_->set_lru_cache_kb_per_process(1024); |
| options_->set_lru_cache_byte_limit(500); |
| options_->set_default_shared_memory_cache_kb(0); |
| PrepareWithConfig(options_.get()); |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| |
| WriteThroughCache* write_through = dynamic_cast<WriteThroughCache*>( |
| SkipWrappers(server_context->metadata_cache())); |
| ASSERT_TRUE(write_through != NULL); |
| EXPECT_EQ(500, write_through->cache1_limit()); |
| |
| LRUCache* lru_cache = dynamic_cast<LRUCache*>( |
| SkipWrappers(write_through->cache1())); |
| ASSERT_TRUE(lru_cache != NULL); |
| EXPECT_EQ(1024*1024, lru_cache->max_bytes_in_cache()); |
| |
| // Also on the HTTP cache |
| WriteThroughCache* http_write_through = |
| dynamic_cast<WriteThroughCache*>(server_context->http_cache()->cache()); |
| ASSERT_TRUE(http_write_through != NULL); |
| EXPECT_EQ(500, http_write_through->cache1_limit()); |
| } |
| |
| TEST_F(SystemCachesTest, HangingMultigetTest) { |
| // Test that we do not hang in the case of corrupted responses from memcached, |
| // as seen in bug report 1048 |
| // https://github.com/pagespeed/mod_pagespeed/issues/1048 |
| set_fake_memcached_port(0); |
| scoped_ptr<FakeMemcacheServerThread> thread( |
| new FakeMemcacheServerThread(this)); |
| ASSERT_TRUE(thread->Start()); |
| thread->WaitForReady(); |
| ASSERT_NE(get_fake_memcached_port(), 0); |
| GoogleString apr_str = |
| StrCat("localhost:", Integer64ToString(get_fake_memcached_port())); |
| AprMemCache* cache = system_caches_->NewAprMemCache(apr_str); |
| static const char k1[] = "hello"; |
| static const char k2[] = "hi"; |
| BlockingCallback cb1(thread_system_.get()); |
| BlockingCallback cb2(thread_system_.get()); |
| CacheInterface::MultiGetRequest* request = |
| new CacheInterface::MultiGetRequest; |
| request->push_back(CacheInterface::KeyCallback(k1, &cb1)); |
| request->push_back(CacheInterface::KeyCallback(k2, &cb2)); |
| cache->Connect(); |
| // Capture stderr, make sure we get the proper string. |
| // This test depends on a custom fprintf in apr_memcache2. |
| // TODO(jcrowell) do this more nicely, don't depend on the print from |
| // multiget, as the real test is that this should not hang. |
| int stderr_backup; |
| char buffer[4096]; |
| fflush(stderr); |
| int err_pipe[2]; |
| stderr_backup = dup(STDERR_FILENO); |
| ASSERT_NE(-1, stderr_backup); |
| ASSERT_EQ(0, pipe(err_pipe)); |
| ASSERT_NE(-1, dup2(err_pipe[1], STDERR_FILENO)); |
| close(err_pipe[1]); |
| // Make the multiget request. |
| cache->MultiGet(request); |
| thread->Join(); |
| fflush(stderr); |
| int bytes_read = read(err_pipe[0], buffer, sizeof(buffer)); |
| ASSERT_NE(-1, bytes_read); |
| // And give back stderr. |
| ASSERT_NE(-1, dup2(stderr_backup, STDERR_FILENO)); |
| // Now check to make sure that we had the proper output. |
| StringPiece output(buffer, bytes_read); |
| EXPECT_TRUE( |
| output.starts_with("Caught potential spin in apr_memcache multiget!")); |
| } |
| |
| TEST_F(SystemCachesTest, StatsStringMinimal) { |
| // The format is rather dependent on the implementation so we don't check it, |
| // but we do care that it at least doesn't crash. |
| GoogleString out; |
| |
| if (MemCachedServerSpec().empty()) { |
| return; |
| } |
| |
| GoogleString error_msg; |
| EXPECT_TRUE(system_caches_->CreateShmMetadataCache( |
| kCachePath, kUsableMetadataCacheSize, &error_msg)); |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(0); |
| options_->set_memcached_servers(MemCachedServerSpec()); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| |
| system_caches_->PrintCacheStats( |
| static_cast<SystemCaches::StatFlags>(SystemCaches::kGlobalView | |
| SystemCaches::kIncludeMemcached), |
| &out); |
| } |
| |
| TEST_F(SystemCachesTest, ShareIdenticalNoPurge) { |
| options_->set_file_cache_path(kCachePath); |
| SystemCachePath* path1 = system_caches_->GetCache(options_.get()); |
| SystemRewriteOptions options2(thread_system_.get()); |
| options2.set_file_cache_path(kCachePath); |
| SystemCachePath* path2 = system_caches_->GetCache(&options2); |
| EXPECT_EQ(path1, path2); |
| } |
| |
| TEST_F(SystemCachesTest, ShareIdenticalPurge) { |
| options_->set_file_cache_path(kCachePath); |
| options_->set_enable_cache_purge(true); |
| SystemCachePath* path1 = system_caches_->GetCache(options_.get()); |
| SystemRewriteOptions options2(thread_system_.get()); |
| options2.set_file_cache_path(kCachePath); |
| options2.set_enable_cache_purge(true); |
| SystemCachePath* path2 = system_caches_->GetCache(&options2); |
| EXPECT_EQ(path1, path2); |
| } |
| |
| TEST_F(SystemCachesTest, NoSharePurgeFlush) { |
| options_->set_file_cache_path(kCachePath); |
| options_->set_enable_cache_purge(true); |
| SystemCachePath* path1 = system_caches_->GetCache(options_.get()); |
| SystemRewriteOptions options2(thread_system_.get()); |
| options2.set_file_cache_path(kCachePath); |
| SystemCachePath* path2 = system_caches_->GetCache(&options2); |
| EXPECT_NE(path1, path2); |
| } |
| |
| TEST_F(SystemCachesTest, ShareIdenticalPurgeCustomPath) { |
| options_->set_file_cache_path(kCachePath); |
| options_->set_cache_flush_filename("f1"); |
| SystemCachePath* path1 = system_caches_->GetCache(options_.get()); |
| SystemRewriteOptions options2(thread_system_.get()); |
| options2.set_file_cache_path(kCachePath); |
| options2.set_cache_flush_filename("f1"); |
| SystemCachePath* path2 = system_caches_->GetCache(&options2); |
| EXPECT_EQ(path1, path2); |
| } |
| |
| TEST_F(SystemCachesTest, NoShareVaryingPurgeCustomPath) { |
| options_->set_file_cache_path(kCachePath); |
| options_->set_cache_flush_filename("f1"); |
| SystemCachePath* path1 = system_caches_->GetCache(options_.get()); |
| SystemRewriteOptions options2(thread_system_.get()); |
| options2.set_file_cache_path(kCachePath); |
| options2.set_cache_flush_filename("f2"); |
| SystemCachePath* path2 = system_caches_->GetCache(&options2); |
| EXPECT_NE(path1, path2); |
| } |
| |
| TEST_F(SystemCachesTest, ShareOnOff) { |
| options_->set_file_cache_path(kCachePath); |
| SystemCachePath* path1 = system_caches_->GetCache(options_.get()); |
| SystemRewriteOptions options2(thread_system_.get()); |
| options2.set_file_cache_path(kCachePath); |
| options2.set_enabled(RewriteOptions::kEnabledOff); |
| SystemCachePath* path2 = system_caches_->GetCache(&options2); |
| EXPECT_EQ(path1, path2); |
| } |
| |
| TEST_F(SystemCachesTest, NoShareOnUnplugged) { |
| options_->set_file_cache_path("/a"); |
| options_->set_enabled(RewriteOptions::kEnabledUnplugged); |
| SystemCachePath* path1 = system_caches_->GetCache(options_.get()); |
| SystemRewriteOptions options2(thread_system_.get()); |
| options2.set_file_cache_path("/b"); |
| options2.set_enabled(RewriteOptions::kEnabledUnplugged); |
| options2.set_cache_flush_filename("f2"); |
| SystemCachePath* path2 = system_caches_->GetCache(&options2); |
| EXPECT_EQ(path1, path2); // All 'unplugged' caches are the same. |
| } |
| |
| TEST_F(SystemCachesTest, ShareUnpluggedWithOtherMismatches) { |
| options_->set_file_cache_path(kCachePath); |
| SystemCachePath* path1 = system_caches_->GetCache(options_.get()); |
| SystemRewriteOptions options2(thread_system_.get()); |
| options2.set_file_cache_path(kCachePath); |
| options2.set_enabled(RewriteOptions::kEnabledUnplugged); |
| SystemCachePath* path2 = system_caches_->GetCache(&options2); |
| EXPECT_NE(path1, path2); |
| } |
| |
| TEST_F(SystemCachesTest, FileCacheNoConflictTwoPaths) { |
| options_->set_file_cache_path(kCachePath); |
| SystemCachePath* path1 = system_caches_->GetCache(options_.get()); |
| SystemRewriteOptions options2(thread_system_.get()); |
| SystemCachePath* path2 = system_caches_->GetCache(&options2); |
| EXPECT_NE(path1, path2); |
| EXPECT_EQ(0, message_handler()->MessagesOfType(kWarning)); |
| } |
| |
| TEST_F(SystemCachesTest, FileCacheFullConflictTwoPaths) { |
| options_->set_file_cache_path(kCachePath); |
| options_->set_file_cache_clean_size_kb(10); |
| options_->set_file_cache_clean_inode_limit(20); |
| options_->set_file_cache_clean_interval_ms(1000); |
| SystemCachePath* path1 = system_caches_->GetCache(options_.get()); |
| SystemRewriteOptions options2(thread_system_.get()); |
| options2.set_file_cache_path(kCachePath); |
| options2.set_file_cache_clean_size_kb(11); // wins |
| options2.set_file_cache_clean_inode_limit(19); // loses |
| options2.set_file_cache_clean_interval_ms(999); // wins |
| SystemCachePath* path2 = system_caches_->GetCache(&options2); |
| ASSERT_EQ(path1, path2); |
| FileCache* file_cache = path1->file_cache_backend(); |
| const FileCache::CachePolicy* policy = file_cache->cache_policy(); |
| EXPECT_EQ(11*1024, policy->target_size_bytes); |
| EXPECT_EQ(20, policy->target_inode_count); |
| EXPECT_EQ(999, policy->clean_interval_ms); |
| EXPECT_EQ(3, message_handler()->MessagesOfType(kWarning)); |
| } |
| |
| TEST_F(SystemCachesTest, FileCacheNoConflictOnDefaults) { |
| options_->set_file_cache_path(kCachePath); |
| options_->set_file_cache_clean_inode_limit(20); |
| options_->set_file_cache_clean_interval_ms(1000); |
| SystemCachePath* path1 = system_caches_->GetCache(options_.get()); |
| SystemRewriteOptions options2(thread_system_.get()); |
| options2.set_file_cache_path(kCachePath); |
| options2.set_file_cache_clean_size_kb(11); // wins |
| SystemCachePath* path2 = system_caches_->GetCache(&options2); |
| ASSERT_EQ(path1, path2); |
| FileCache* file_cache = path1->file_cache_backend(); |
| const FileCache::CachePolicy* policy = file_cache->cache_policy(); |
| EXPECT_EQ(11*1024, policy->target_size_bytes); |
| EXPECT_EQ(20, policy->target_inode_count); |
| EXPECT_EQ(1000, policy->clean_interval_ms); |
| EXPECT_EQ(0, message_handler()->MessagesOfType(kWarning)); |
| } |
| |
| TEST_F(SystemCachesTest, PurgeUrl) { |
| options_->set_enable_cache_purge(true); |
| SystemServerContext* server_context = PopulateCacheForPurgeTest(); |
| server_context->PostInitHook(); |
| SystemRewriteOptions* options = |
| server_context->global_system_rewrite_options(); |
| RequestContextPtr request_context( |
| RequestContext::NewTestRequestContext(thread_system_.get())); |
| StringAsyncFetch fetch(request_context); |
| |
| // Invalidate kUrl1 but leave kUrl2 intact. |
| AdminSite* admin_site = server_context->admin_site(); |
| admin_site->PurgeHandler(kUrl1, server_context->cache_path(), &fetch); |
| ASSERT_TRUE(fetch.done()); |
| ASSERT_TRUE(fetch.success()); |
| server_context->FlushCacheIfNecessary(); |
| |
| // Make sure we can no longer fetch kUrl1, but we can still fetch kUrl2. |
| ResponseHeaders headers; |
| HTTPValue value; |
| EXPECT_EQ(kNotFoundResult, HttpBlockingFindWithOptions( |
| options, kUrl1, server_context->http_cache(), &value, &headers)); |
| EXPECT_EQ(kFoundResult, HttpBlockingFindWithOptions( |
| options, kUrl2, server_context->http_cache(), &value, &headers)); |
| |
| // Now set the global invalidation timestamp, and kUrl2 will now be invalid |
| // as well. |
| AdvanceTimeMs(1); |
| fetch.Reset(); |
| admin_site->PurgeHandler("http://example.com/*", server_context->cache_path(), |
| &fetch); |
| server_context->FlushCacheIfNecessary(); |
| AdvanceTimeMs(1); |
| EXPECT_EQ(kNotFoundResult, HttpBlockingFindWithOptions( |
| options, kUrl2, server_context->http_cache(), &value, &headers)); |
| } |
| |
| TEST_F(SystemCachesTest, InvalidateWithPurgeDisabled) { |
| options_->set_enable_cache_purge(false); |
| SystemServerContext* server_context = PopulateCacheForPurgeTest(); |
| SystemRewriteOptions* options = |
| server_context->global_system_rewrite_options(); |
| |
| // touch cache.flush |
| file_system()->WriteFile(StrCat(kCachePath, "/cache.flush").c_str(), |
| "", message_handler()); |
| AdvanceTimeMs(1000); |
| server_context->FlushCacheIfNecessary(); |
| |
| // Make sure both kUrl1 and kUrl2 are invalidated from touching the file. |
| ResponseHeaders headers; |
| HTTPValue value; |
| EXPECT_EQ(kNotFoundResult, HttpBlockingFindWithOptions( |
| options, kUrl1, server_context->http_cache(), &value, &headers)); |
| EXPECT_EQ(kNotFoundResult, HttpBlockingFindWithOptions( |
| options, kUrl2, server_context->http_cache(), &value, &headers)); |
| } |
| |
| TEST_F(SystemCachesTest, StressTest) { |
| MemCacheStressTestHelper(false); |
| } |
| |
| TEST_F(SystemCachesTest, StressTestWithDeletions) { |
| MemCacheStressTestHelper(true); |
| } |
| |
| // Tests for how we fallback when SHM setup ops fail. |
| class BrokenShmSystemCachesTest : public SystemCachesTest { |
| protected: |
| BrokenShmSystemCachesTest() { |
| shared_mem_.reset(new NullSharedMem()); |
| } |
| }; |
| |
| TEST_F(BrokenShmSystemCachesTest, FallbackShmLockManager) { |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(true); |
| options_->set_lru_cache_kb_per_process(100); |
| PrepareWithConfig(options_.get()); |
| NamedLockManager* named_locks = system_caches_->GetLockManager( |
| options_.get()); |
| EXPECT_TRUE(named_locks != NULL); |
| |
| // Actually file system based here, due to fallback. |
| EXPECT_TRUE(dynamic_cast<FileSystemLockManager*>(named_locks) != NULL); |
| } |
| |
| TEST_F(BrokenShmSystemCachesTest, FallbackShmAndLru) { |
| GoogleString error_msg; |
| EXPECT_TRUE(system_caches_->CreateShmMetadataCache( |
| kCachePath, kUsableMetadataCacheSize, &error_msg)); |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(100); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| // We don't use the LRU when shm cache is on. |
| EXPECT_STREQ(Compressed(WriteThrough(Stats("lru_cache", ThreadsafeLRU()), |
| FileCacheWithStats())), |
| server_context->metadata_cache()->Name()); |
| // HTTP cache is unaffected. |
| EXPECT_STREQ( |
| HttpCache(WriteThrough( |
| Stats("lru_cache", ThreadsafeLRU()), |
| FileCacheWithStats())), |
| server_context->http_cache()->Name()); |
| } |
| |
| TEST_F(BrokenShmSystemCachesTest, FallbackShmAndNoLru) { |
| GoogleString error_msg; |
| EXPECT_TRUE(system_caches_->CreateShmMetadataCache( |
| kCachePath, kUsableMetadataCacheSize, &error_msg)); |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(0); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| // We don't use the LRU when shm cache is on. |
| EXPECT_STREQ(Compressed(FileCacheWithStats()), |
| server_context->metadata_cache()->Name()); |
| // HTTP cache is unaffected. |
| EXPECT_STREQ(HttpCache(FileCacheWithStats()), |
| server_context->http_cache()->Name()); |
| } |
| |
| TEST_F(BrokenShmSystemCachesTest, FallbackMemCachedLruShm) { |
| if (MemCachedServerSpec().empty()) { |
| return; |
| } |
| |
| GoogleString error_msg; |
| EXPECT_TRUE(system_caches_->CreateShmMetadataCache( |
| kCachePath, kUsableMetadataCacheSize, &error_msg)); |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(100); |
| options_->set_memcached_servers(MemCachedServerSpec()); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| // For metadata, we fallback to memcached behind shmcache. |
| EXPECT_STREQ( |
| Compressed( |
| WriteThrough( |
| Stats("lru_cache", ThreadsafeLRU()), |
| Fallback(Batcher(AsyncMemCacheWithStats(), 1, 1000), |
| FileCacheWithStats()))), |
| server_context->metadata_cache()->Name()); |
| EXPECT_STREQ( |
| HttpCache(WriteThrough( |
| Stats("lru_cache", ThreadsafeLRU()), |
| Fallback(Batcher(AsyncMemCacheWithStats(), 1, 1000), |
| FileCacheWithStats()))), |
| server_context->http_cache()->Name()); |
| EXPECT_STREQ(Pcache(Compressed(Fallback(BlockingMemCacheWithStats(), |
| FileCacheWithStats()))), |
| server_context->page_property_cache()->property_store()->Name()); |
| } |
| |
| TEST_F(BrokenShmSystemCachesTest, FallbackMemCachedShmNoLru) { |
| if (MemCachedServerSpec().empty()) { |
| return; |
| } |
| |
| GoogleString error_msg; |
| EXPECT_TRUE(system_caches_->CreateShmMetadataCache( |
| kCachePath, kUsableMetadataCacheSize, &error_msg)); |
| |
| options_->set_file_cache_path(kCachePath); |
| options_->set_use_shared_mem_locking(false); |
| options_->set_lru_cache_kb_per_process(0); |
| options_->set_memcached_servers(MemCachedServerSpec()); |
| PrepareWithConfig(options_.get()); |
| |
| scoped_ptr<ServerContext> server_context( |
| SetupServerContext(options_.release())); |
| EXPECT_STREQ( |
| Compressed(Fallback(Batcher(AsyncMemCacheWithStats(), 1, 1000), |
| FileCacheWithStats())), |
| server_context->metadata_cache()->Name()); |
| EXPECT_STREQ( |
| HttpCache( |
| Fallback(Batcher(AsyncMemCacheWithStats(), 1, 1000), |
| FileCacheWithStats())), |
| server_context->http_cache()->Name()); |
| } |
| |
| } // namespace |
| |
| } // namespace net_instaweb |