blob: 9772a63b573aaf2f2c9a8d967660ee6d2233bdd2 [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: 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