From 67df23f076705daa68a80db863a3c26986dc32ca Mon Sep 17 00:00:00 2001 From: rvargas Date: Tue, 22 Sep 2015 14:28:02 -0700 Subject: Disk cache: switch stress_cache to perform fully async operations to get a more realistic environmenrt. BUG=none Review URL: https://codereview.chromium.org/1351223002 Cr-Commit-Position: refs/heads/master@{#350237} --- net/tools/stress_cache/stress_cache.cc | 261 ++++++++++++++++++++++++++------- 1 file changed, 205 insertions(+), 56 deletions(-) diff --git a/net/tools/stress_cache/stress_cache.cc b/net/tools/stress_cache/stress_cache.cc index 0706d4c..29ddff2 100644 --- a/net/tools/stress_cache/stress_cache.cc +++ b/net/tools/stress_cache/stress_cache.cc @@ -19,6 +19,7 @@ #include "base/at_exit.h" #include "base/bind.h" +#include "base/bind_helpers.h" #include "base/command_line.h" #include "base/debug/debugger.h" #include "base/files/file_path.h" @@ -28,6 +29,7 @@ #include "base/path_service.h" #include "base/process/launch.h" #include "base/process/process.h" +#include "base/run_loop.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" @@ -98,6 +100,197 @@ std::string GenerateStressKey() { return std::string(key); } +// kNumKeys is meant to be enough to have about 3x or 4x iterations before +// the process crashes. +#ifdef NDEBUG +const int kNumKeys = 4000; +#else +const int kNumKeys = 1200; +#endif +const int kNumEntries = 30; +const int kBufferSize = 2000; +const int kReadSize = 20; + +// Things that an entry can be doing. +enum Operation { NONE, OPEN, CREATE, READ, WRITE, DOOM }; + +// This class encapsulates a cache entry and the operations performed on that +// entry. An entry is opened or created as needed, the current content is then +// verified and then something is written to the entry. At that point, the +// |state_| becomes NONE again, waiting for another write, unless the entry is +// closed or deleted. +class EntryWrapper { + public: + EntryWrapper() : entry_(nullptr), state_(NONE) { + buffer_ = new net::IOBuffer(kBufferSize); + memset(buffer_->data(), 'k', kBufferSize); + } + + Operation state() const { return state_; } + + void DoOpen(int key); + + private: + void OnOpenDone(int key, int result); + void DoRead(); + void OnReadDone(int result); + void DoWrite(); + void OnWriteDone(int size, int result); + void DoDelete(const std::string& key); + void OnDeleteDone(int result); + void DoIdle(); + + disk_cache::Entry* entry_; + Operation state_; + scoped_refptr buffer_; +}; + +// The data that the main thread is working on. +struct Data { + Data() : pendig_operations(0), writes(0), iteration(0), cache(nullptr) {} + + int pendig_operations; // Counter of simultaneous operations. + int writes; // How many writes since this iteration started. + int iteration; // The iteration (number of crashes). + disk_cache::BackendImpl* cache; + std::string keys[kNumKeys]; + EntryWrapper entries[kNumEntries]; +}; + +Data* g_data = nullptr; + +void EntryWrapper::DoOpen(int key) { + DCHECK_EQ(state_, NONE); + if (entry_) + return DoRead(); + + state_ = OPEN; + int rv = g_data->cache->OpenEntry( + g_data->keys[key], &entry_, + base::Bind(&EntryWrapper::OnOpenDone, base::Unretained(this), key)); + if (rv != net::ERR_IO_PENDING) + OnOpenDone(key, rv); +} + +void EntryWrapper::OnOpenDone(int key, int result) { + if (result == net::OK) + return DoRead(); + + CHECK_EQ(state_, OPEN); + state_ = CREATE; + result = g_data->cache->CreateEntry( + g_data->keys[key], &entry_, + base::Bind(&EntryWrapper::OnOpenDone, base::Unretained(this), key)); + if (result != net::ERR_IO_PENDING) + OnOpenDone(key, result); +} + +void EntryWrapper::DoRead() { + int current_size = entry_->GetDataSize(0); + if (!current_size) + return DoWrite(); + + state_ = READ; + memset(buffer_->data(), 'k', kReadSize); + int rv = entry_->ReadData( + 0, 0, buffer_.get(), kReadSize, + base::Bind(&EntryWrapper::OnReadDone, base::Unretained(this))); + if (rv != net::ERR_IO_PENDING) + OnReadDone(rv); +} + +void EntryWrapper::OnReadDone(int result) { + DCHECK_EQ(state_, READ); + CHECK_EQ(result, kReadSize); + CHECK_EQ(0, memcmp(buffer_->data(), "Write: ", 7)); + DoWrite(); +} + +void EntryWrapper::DoWrite() { + bool truncate = (rand() % 2 == 0); + int size = kBufferSize - (rand() % 20) * kBufferSize / 20; + state_ = WRITE; + base::snprintf(buffer_->data(), kBufferSize, + "Write: %d iter: %d, size: %d, truncate: %d ", + g_data->writes, g_data->iteration, size, truncate ? 1 : 0); + int rv = entry_->WriteData( + 0, 0, buffer_.get(), size, + base::Bind(&EntryWrapper::OnWriteDone, base::Unretained(this), size), + truncate); + if (rv != net::ERR_IO_PENDING) + OnWriteDone(size, rv); +} + +void EntryWrapper::OnWriteDone(int size, int result) { + DCHECK_EQ(state_, WRITE); + CHECK_EQ(size, result); + if (!(g_data->writes++ % 100)) + printf("Entries: %d \r", g_data->writes); + + int random = rand() % 100; + std::string key = entry_->GetKey(); + if (random > 90) + return DoDelete(key); // 10% delete then close. + + if (random > 60) { // 20% close. + entry_->Close(); + entry_ = nullptr; + } + + if (random > 80) + return DoDelete(key); // 10% close then delete. + + DoIdle(); // 60% do another write later. +} + +void EntryWrapper::DoDelete(const std::string& key) { + state_ = DOOM; + int rv = g_data->cache->DoomEntry( + key, base::Bind(&EntryWrapper::OnDeleteDone, base::Unretained(this))); + if (rv != net::ERR_IO_PENDING) + OnDeleteDone(rv); +} + +void EntryWrapper::OnDeleteDone(int result) { + DCHECK_EQ(state_, DOOM); + if (entry_) { + entry_->Close(); + entry_ = nullptr; + } + DoIdle(); +} + +void LoopTask(); + +void EntryWrapper::DoIdle() { + state_ = NONE; + g_data->pendig_operations--; + DCHECK(g_data->pendig_operations); + base::MessageLoop::current()->task_runner()->PostTask(FROM_HERE, + base::Bind(&LoopTask)); +} + +// The task that keeps the main thread busy. Whenever an entry becomes idle this +// task is executed again. +void LoopTask() { + if (g_data->pendig_operations >= kNumEntries) + return; + + int slot = rand() % kNumEntries; + if (g_data->entries[slot].state() == NONE) { + // Each slot will have some keys assigned to it so that the same entry will + // not be open by two slots, which means that the state is well known at + // all times. + int keys_per_entry = kNumKeys / kNumEntries; + int key = rand() % keys_per_entry + keys_per_entry * slot; + g_data->pendig_operations++; + g_data->entries[slot].DoOpen(key); + } + + base::MessageLoop::current()->task_runner()->PostTask(FROM_HERE, + base::Bind(&LoopTask)); +} + // This thread will loop forever, adding and removing entries from the cache. // iteration is the current crash cycle, so the entries on the cache are marked // to know which instance of the application wrote them. @@ -114,76 +307,32 @@ void StressTheCache(int iteration) { base::Thread::Options(base::MessageLoop::TYPE_IO, 0))) return; - disk_cache::BackendImpl* cache = new disk_cache::BackendImpl( + g_data = new Data(); + g_data->iteration = iteration; + g_data->cache = new disk_cache::BackendImpl( path, mask, cache_thread.task_runner().get(), NULL); - cache->SetMaxSize(cache_size); - cache->SetFlags(disk_cache::kNoLoadProtection); + g_data->cache->SetMaxSize(cache_size); + g_data->cache->SetFlags(disk_cache::kNoLoadProtection); net::TestCompletionCallback cb; - int rv = cache->Init(cb.callback()); + int rv = g_data->cache->Init(cb.callback()); if (cb.GetResult(rv) != net::OK) { printf("Unable to initialize cache.\n"); return; } printf("Iteration %d, initial entries: %d\n", iteration, - cache->GetEntryCount()); + g_data->cache->GetEntryCount()); int seed = static_cast(Time::Now().ToInternalValue()); srand(seed); - // kNumKeys is meant to be enough to have about 3x or 4x iterations before - // the process crashes. -#ifdef NDEBUG - const int kNumKeys = 4000; -#else - const int kNumKeys = 1200; -#endif - const int kNumEntries = 30; - std::string keys[kNumKeys]; - disk_cache::Entry* entries[kNumEntries] = {0}; + for (int i = 0; i < kNumKeys; i++) + g_data->keys[i] = GenerateStressKey(); - for (int i = 0; i < kNumKeys; i++) { - keys[i] = GenerateStressKey(); - } - - const int kSize = 20000; - scoped_refptr buffer(new net::IOBuffer(kSize)); - memset(buffer->data(), 'k', kSize); - - for (int i = 0;; i++) { - int slot = rand() % kNumEntries; - int key = rand() % kNumKeys; - bool truncate = (rand() % 2 == 0); - int size = kSize - (rand() % 20) * kSize / 20; - - if (entries[slot]) - entries[slot]->Close(); - - net::TestCompletionCallback cb; - rv = cache->OpenEntry(keys[key], &entries[slot], cb.callback()); - if (cb.GetResult(rv) != net::OK) { - rv = cache->CreateEntry(keys[key], &entries[slot], cb.callback()); - CHECK_EQ(net::OK, cb.GetResult(rv)); - } - - base::snprintf(buffer->data(), kSize, - "i: %d iter: %d, size: %d, truncate: %d ", i, iteration, - size, truncate ? 1 : 0); - rv = entries[slot]->WriteData(0, 0, buffer.get(), size, cb.callback(), - truncate); - CHECK_EQ(size, cb.GetResult(rv)); - - if (rand() % 100 > 80) { - key = rand() % kNumKeys; - net::TestCompletionCallback cb2; - rv = cache->DoomEntry(keys[key], cb2.callback()); - cb2.GetResult(rv); - } - - if (!(i % 100)) - printf("Entries: %d \r", i); - } + base::MessageLoop::current()->task_runner()->PostTask(FROM_HERE, + base::Bind(&LoopTask)); + base::RunLoop().Run(); } // We want to prevent the timer thread from killing the process while we are -- cgit v1.1