• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // This is a simple application that stress-tests the crash recovery of the disk
6 // cache. The main application starts a copy of itself on a loop, checking the
7 // exit code of the child process. When the child dies in an unexpected way,
8 // the main application quits.
9 
10 // The child application has two threads: one to exercise the cache in an
11 // infinite loop, and another one to asynchronously kill the process.
12 
13 // A regular build should never crash.
14 // To test that the disk cache doesn't generate critical errors with regular
15 // application level crashes, edit stress_support.h.
16 
17 #include <string>
18 #include <vector>
19 
20 #include "base/at_exit.h"
21 #include "base/command_line.h"
22 #include "base/debug/debugger.h"
23 #include "base/files/file_path.h"
24 #include "base/functional/bind.h"
25 #include "base/functional/callback_helpers.h"
26 #include "base/location.h"
27 #include "base/logging.h"
28 #include "base/message_loop/message_pump_type.h"
29 #include "base/path_service.h"
30 #include "base/process/launch.h"
31 #include "base/process/process.h"
32 #include "base/run_loop.h"
33 #include "base/strings/string_number_conversions.h"
34 #include "base/strings/string_util.h"
35 #include "base/strings/utf_string_conversions.h"
36 #include "base/task/single_thread_task_executor.h"
37 #include "base/task/single_thread_task_runner.h"
38 #include "base/threading/platform_thread.h"
39 #include "base/threading/thread.h"
40 #include "base/time/time.h"
41 #include "build/build_config.h"
42 #include "net/base/io_buffer.h"
43 #include "net/base/net_errors.h"
44 #include "net/base/test_completion_callback.h"
45 #include "net/disk_cache/blockfile/backend_impl.h"
46 #include "net/disk_cache/blockfile/stress_support.h"
47 #include "net/disk_cache/disk_cache.h"
48 #include "net/disk_cache/disk_cache_test_util.h"
49 
50 #if BUILDFLAG(IS_WIN)
51 #include "base/logging_win.h"
52 #endif
53 
54 using base::Time;
55 
56 const int kError = -1;
57 const int kExpectedCrash = 100;
58 
59 // Starts a new process.
RunSlave(int iteration)60 int RunSlave(int iteration) {
61   base::FilePath exe;
62   base::PathService::Get(base::FILE_EXE, &exe);
63 
64   base::CommandLine cmdline(exe);
65   cmdline.AppendArg(base::NumberToString(iteration));
66 
67   base::Process process = base::LaunchProcess(cmdline, base::LaunchOptions());
68   if (!process.IsValid()) {
69     printf("Unable to run test\n");
70     return kError;
71   }
72 
73   int exit_code;
74   if (!process.WaitForExit(&exit_code)) {
75     printf("Unable to get return code\n");
76     return kError;
77   }
78   return exit_code;
79 }
80 
81 // Main loop for the master process.
MasterCode()82 int MasterCode() {
83   for (int i = 0; i < 100000; i++) {
84     int ret = RunSlave(i);
85     if (kExpectedCrash != ret)
86       return ret;
87   }
88 
89   printf("More than enough...\n");
90 
91   return 0;
92 }
93 
94 // -----------------------------------------------------------------------
95 
GenerateStressKey()96 std::string GenerateStressKey() {
97   char key[20 * 1024];
98   size_t size = 50 + rand() % 20000;
99   CacheTestFillBuffer(key, size, true);
100 
101   key[size - 1] = '\0';
102   return std::string(key);
103 }
104 
105 // kNumKeys is meant to be enough to have about 3x or 4x iterations before
106 // the process crashes.
107 #ifdef NDEBUG
108 const int kNumKeys = 4000;
109 #else
110 const int kNumKeys = 1200;
111 #endif
112 const int kNumEntries = 30;
113 const int kBufferSize = 2000;
114 const int kReadSize = 20;
115 
116 // Things that an entry can be doing.
117 enum Operation { NONE, OPEN, CREATE, READ, WRITE, DOOM };
118 
119 // This class encapsulates a cache entry and the operations performed on that
120 // entry. An entry is opened or created as needed, the current content is then
121 // verified and then something is written to the entry. At that point, the
122 // |state_| becomes NONE again, waiting for another write, unless the entry is
123 // closed or deleted.
124 class EntryWrapper {
125  public:
EntryWrapper()126   EntryWrapper() {
127     buffer_ = base::MakeRefCounted<net::IOBufferWithSize>(kBufferSize);
128     memset(buffer_->data(), 'k', kBufferSize);
129   }
130 
state() const131   Operation state() const { return state_; }
132 
133   void DoOpen(int key);
134 
135  private:
136   void OnOpenDone(int key, disk_cache::EntryResult result);
137   void DoRead();
138   void OnReadDone(int result);
139   void DoWrite();
140   void OnWriteDone(int size, int result);
141   void DoDelete(const std::string& key);
142   void OnDeleteDone(int result);
143   void DoIdle();
144 
145   disk_cache::Entry* entry_ = nullptr;
146   Operation state_ = NONE;
147   scoped_refptr<net::IOBuffer> buffer_;
148 };
149 
150 // The data that the main thread is working on.
151 struct Data {
152   Data() = default;
153 
154   int pendig_operations = 0;  // Counter of simultaneous operations.
155   int writes = 0;             // How many writes since this iteration started.
156   int iteration = 0;          // The iteration (number of crashes).
157   disk_cache::BackendImpl* cache = nullptr;
158   std::string keys[kNumKeys];
159   EntryWrapper entries[kNumEntries];
160 };
161 
162 Data* g_data = nullptr;
163 
DoOpen(int key)164 void EntryWrapper::DoOpen(int key) {
165   DCHECK_EQ(state_, NONE);
166   if (entry_)
167     return DoRead();
168 
169   state_ = OPEN;
170   disk_cache::EntryResult result = g_data->cache->OpenEntry(
171       g_data->keys[key], net::HIGHEST,
172       base::BindOnce(&EntryWrapper::OnOpenDone, base::Unretained(this), key));
173   if (result.net_error() != net::ERR_IO_PENDING)
174     OnOpenDone(key, std::move(result));
175 }
176 
OnOpenDone(int key,disk_cache::EntryResult result)177 void EntryWrapper::OnOpenDone(int key, disk_cache::EntryResult result) {
178   if (result.net_error() == net::OK) {
179     entry_ = result.ReleaseEntry();
180     return DoRead();
181   }
182 
183   CHECK_EQ(state_, OPEN);
184   state_ = CREATE;
185   result = g_data->cache->CreateEntry(
186       g_data->keys[key], net::HIGHEST,
187       base::BindOnce(&EntryWrapper::OnOpenDone, base::Unretained(this), key));
188   if (result.net_error() != net::ERR_IO_PENDING)
189     OnOpenDone(key, std::move(result));
190 }
191 
DoRead()192 void EntryWrapper::DoRead() {
193   int current_size = entry_->GetDataSize(0);
194   if (!current_size)
195     return DoWrite();
196 
197   state_ = READ;
198   memset(buffer_->data(), 'k', kReadSize);
199   int rv = entry_->ReadData(
200       0, 0, buffer_.get(), kReadSize,
201       base::BindOnce(&EntryWrapper::OnReadDone, base::Unretained(this)));
202   if (rv != net::ERR_IO_PENDING)
203     OnReadDone(rv);
204 }
205 
OnReadDone(int result)206 void EntryWrapper::OnReadDone(int result) {
207   DCHECK_EQ(state_, READ);
208   CHECK_EQ(result, kReadSize);
209   CHECK_EQ(0, memcmp(buffer_->data(), "Write: ", 7));
210   DoWrite();
211 }
212 
DoWrite()213 void EntryWrapper::DoWrite() {
214   bool truncate = (rand() % 2 == 0);
215   int size = kBufferSize - (rand() % 20) * kBufferSize / 20;
216   state_ = WRITE;
217   base::snprintf(buffer_->data(), kBufferSize,
218                  "Write: %d iter: %d, size: %d, truncate: %d     ",
219                  g_data->writes, g_data->iteration, size, truncate ? 1 : 0);
220   int rv = entry_->WriteData(
221       0, 0, buffer_.get(), size,
222       base::BindOnce(&EntryWrapper::OnWriteDone, base::Unretained(this), size),
223       truncate);
224   if (rv != net::ERR_IO_PENDING)
225     OnWriteDone(size, rv);
226 }
227 
OnWriteDone(int size,int result)228 void EntryWrapper::OnWriteDone(int size, int result) {
229   DCHECK_EQ(state_, WRITE);
230   CHECK_EQ(size, result);
231   if (!(g_data->writes++ % 100))
232     printf("Entries: %d    \r", g_data->writes);
233 
234   int random = rand() % 100;
235   std::string key = entry_->GetKey();
236   if (random > 90)
237     return DoDelete(key);  // 10% delete then close.
238 
239   if (random > 60) {  // 20% close.
240     entry_->Close();
241     entry_ = nullptr;
242   }
243 
244   if (random > 80)
245     return DoDelete(key);  // 10% close then delete.
246 
247   DoIdle();  // 60% do another write later.
248 }
249 
DoDelete(const std::string & key)250 void EntryWrapper::DoDelete(const std::string& key) {
251   state_ = DOOM;
252   int rv = g_data->cache->DoomEntry(
253       key, net::HIGHEST,
254       base::BindOnce(&EntryWrapper::OnDeleteDone, base::Unretained(this)));
255   if (rv != net::ERR_IO_PENDING)
256     OnDeleteDone(rv);
257 }
258 
OnDeleteDone(int result)259 void EntryWrapper::OnDeleteDone(int result) {
260   DCHECK_EQ(state_, DOOM);
261   if (entry_) {
262     entry_->Close();
263     entry_ = nullptr;
264   }
265   DoIdle();
266 }
267 
268 void LoopTask();
269 
DoIdle()270 void EntryWrapper::DoIdle() {
271   state_ = NONE;
272   g_data->pendig_operations--;
273   DCHECK(g_data->pendig_operations);
274   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
275       FROM_HERE, base::BindOnce(&LoopTask));
276 }
277 
278 // The task that keeps the main thread busy. Whenever an entry becomes idle this
279 // task is executed again.
LoopTask()280 void LoopTask() {
281   if (g_data->pendig_operations >= kNumEntries)
282     return;
283 
284   int slot = rand() % kNumEntries;
285   if (g_data->entries[slot].state() == NONE) {
286     // Each slot will have some keys assigned to it so that the same entry will
287     // not be open by two slots, which means that the state is well known at
288     // all times.
289     int keys_per_entry = kNumKeys / kNumEntries;
290     int key = rand() % keys_per_entry + keys_per_entry * slot;
291     g_data->pendig_operations++;
292     g_data->entries[slot].DoOpen(key);
293   }
294 
295   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
296       FROM_HERE, base::BindOnce(&LoopTask));
297 }
298 
299 // This thread will loop forever, adding and removing entries from the cache.
300 // iteration is the current crash cycle, so the entries on the cache are marked
301 // to know which instance of the application wrote them.
StressTheCache(int iteration)302 void StressTheCache(int iteration) {
303   int cache_size = 0x2000000;  // 32MB.
304   uint32_t mask = 0xfff;       // 4096 entries.
305 
306   base::FilePath path;
307   base::PathService::Get(base::DIR_TEMP, &path);
308   path = path.AppendASCII("cache_test_stress");
309 
310   base::Thread cache_thread("CacheThread");
311   if (!cache_thread.StartWithOptions(
312           base::Thread::Options(base::MessagePumpType::IO, 0)))
313     return;
314 
315   g_data = new Data();
316   g_data->iteration = iteration;
317   g_data->cache = new disk_cache::BackendImpl(
318       path, mask, cache_thread.task_runner().get(), net::DISK_CACHE, nullptr);
319   g_data->cache->SetMaxSize(cache_size);
320   g_data->cache->SetFlags(disk_cache::kNoLoadProtection);
321 
322   net::TestCompletionCallback cb;
323   g_data->cache->Init(cb.callback());
324 
325   if (cb.WaitForResult() != net::OK) {
326     printf("Unable to initialize cache.\n");
327     return;
328   }
329   printf("Iteration %d, initial entries: %d\n", iteration,
330          g_data->cache->GetEntryCount());
331 
332   int seed = static_cast<int>(Time::Now().ToInternalValue());
333   srand(seed);
334 
335   for (auto& key : g_data->keys)
336     key = GenerateStressKey();
337 
338   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
339       FROM_HERE, base::BindOnce(&LoopTask));
340   base::RunLoop().Run();
341 }
342 
343 // We want to prevent the timer thread from killing the process while we are
344 // waiting for the debugger to attach.
345 bool g_crashing = false;
346 
347 // RunSoon() and CrashCallback() reference each other, unfortunately.
348 void RunSoon(scoped_refptr<base::SingleThreadTaskRunner> task_runner);
349 
CrashCallback()350 void CrashCallback() {
351   // Keep trying to run.
352   RunSoon(base::SingleThreadTaskRunner::GetCurrentDefault());
353 
354   if (g_crashing)
355     return;
356 
357   if (rand() % 100 > 30) {
358     printf("sweet death...\n");
359 
360     // Terminate the current process without doing normal process-exit cleanup.
361     base::Process::TerminateCurrentProcessImmediately(kExpectedCrash);
362   }
363 }
364 
RunSoon(scoped_refptr<base::SingleThreadTaskRunner> task_runner)365 void RunSoon(scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
366   const base::TimeDelta kTaskDelay = base::Seconds(10);
367   task_runner->PostDelayedTask(FROM_HERE, base::BindOnce(&CrashCallback),
368                                kTaskDelay);
369 }
370 
371 // We leak everything here :)
StartCrashThread()372 bool StartCrashThread() {
373   base::Thread* thread = new base::Thread("party_crasher");
374   if (!thread->Start())
375     return false;
376 
377   RunSoon(thread->task_runner());
378   return true;
379 }
380 
CrashHandler(const char * file,int line,const base::StringPiece str,const base::StringPiece stack_trace)381 void CrashHandler(const char* file,
382                   int line,
383                   const base::StringPiece str,
384                   const base::StringPiece stack_trace) {
385   g_crashing = true;
386   base::debug::BreakDebugger();
387 }
388 
389 // -----------------------------------------------------------------------
390 
391 #if BUILDFLAG(IS_WIN)
392 // {B9A153D4-31C3-48e4-9ABF-D54383F14A0D}
393 const GUID kStressCacheTraceProviderName = {
394     0xb9a153d4, 0x31c3, 0x48e4,
395         { 0x9a, 0xbf, 0xd5, 0x43, 0x83, 0xf1, 0x4a, 0xd } };
396 #endif
397 
main(int argc,const char * argv[])398 int main(int argc, const char* argv[]) {
399   // Setup an AtExitManager so Singleton objects will be destructed.
400   base::AtExitManager at_exit_manager;
401 
402   if (argc < 2)
403     return MasterCode();
404 
405   logging::ScopedLogAssertHandler scoped_assert_handler(
406       base::BindRepeating(CrashHandler));
407 
408 #if BUILDFLAG(IS_WIN)
409   logging::LogEventProvider::Initialize(kStressCacheTraceProviderName);
410 #else
411   base::CommandLine::Init(argc, argv);
412   logging::LoggingSettings settings;
413   settings.logging_dest =
414       logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR;
415   logging::InitLogging(settings);
416 #endif
417 
418   // Some time for the memory manager to flush stuff.
419   base::PlatformThread::Sleep(base::Seconds(3));
420   base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
421 
422   char* end;
423   long int iteration = strtol(argv[1], &end, 0);
424 
425   if (!StartCrashThread()) {
426     printf("failed to start thread\n");
427     return kError;
428   }
429 
430   StressTheCache(iteration);
431   return 0;
432 }
433