1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
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, add the following code and re-compile:
16//
17//     void BackendImpl::CriticalError(int error) {
18//       NOTREACHED();
19//
20//     void BackendImpl::ReportError(int error) {
21//       if (error && error != ERR_PREVIOUS_CRASH) {
22//         NOTREACHED();
23//       }
24
25#include <string>
26#include <vector>
27
28#include "base/at_exit.h"
29#include "base/command_line.h"
30#include "base/debug/debugger.h"
31#include "base/file_path.h"
32#include "base/logging.h"
33#include "base/message_loop.h"
34#include "base/path_service.h"
35#include "base/process_util.h"
36#include "base/string_number_conversions.h"
37#include "base/string_util.h"
38#include "base/threading/platform_thread.h"
39#include "base/threading/thread.h"
40#include "base/utf_string_conversions.h"
41#include "net/base/net_errors.h"
42#include "net/base/test_completion_callback.h"
43#include "net/base/io_buffer.h"
44#include "net/disk_cache/backend_impl.h"
45#include "net/disk_cache/disk_cache.h"
46#include "net/disk_cache/disk_cache_test_util.h"
47
48using base::Time;
49
50const int kError = -1;
51const int kExpectedCrash = 100;
52
53// Starts a new process.
54int RunSlave(int iteration) {
55  FilePath exe;
56  PathService::Get(base::FILE_EXE, &exe);
57
58  CommandLine cmdline(exe);
59  cmdline.AppendArg(base::IntToString(iteration));
60
61  base::ProcessHandle handle;
62  if (!base::LaunchApp(cmdline, false, false, &handle)) {
63    printf("Unable to run test\n");
64    return kError;
65  }
66
67  int exit_code;
68  if (!base::WaitForExitCode(handle, &exit_code)) {
69    printf("Unable to get return code\n");
70    return kError;
71  }
72  return exit_code;
73}
74
75// Main loop for the master process.
76int MasterCode() {
77  for (int i = 0; i < 100000; i++) {
78    int ret = RunSlave(i);
79    if (kExpectedCrash != ret)
80      return ret;
81  }
82
83  printf("More than enough...\n");
84
85  return 0;
86}
87
88// -----------------------------------------------------------------------
89
90// This thread will loop forever, adding and removing entries from the cache.
91// iteration is the current crash cycle, so the entries on the cache are marked
92// to know which instance of the application wrote them.
93void StressTheCache(int iteration) {
94  int cache_size = 0x800000;  // 8MB
95  FilePath path = GetCacheFilePath().InsertBeforeExtensionASCII("_stress");
96
97  base::Thread cache_thread("CacheThread");
98  if (!cache_thread.StartWithOptions(
99          base::Thread::Options(MessageLoop::TYPE_IO, 0)))
100    return;
101
102  TestCompletionCallback cb;
103  disk_cache::Backend* cache;
104  int rv = disk_cache::BackendImpl::CreateBackend(
105               path, false, cache_size, net::DISK_CACHE,
106               disk_cache::kNoLoadProtection | disk_cache::kNoRandom,
107               cache_thread.message_loop_proxy(), NULL, &cache, &cb);
108
109  if (cb.GetResult(rv) != net::OK) {
110    printf("Unable to initialize cache.\n");
111    return;
112  }
113  printf("Iteration %d, initial entries: %d\n", iteration,
114         cache->GetEntryCount());
115
116  int seed = static_cast<int>(Time::Now().ToInternalValue());
117  srand(seed);
118
119#ifdef NDEBUG
120  const int kNumKeys = 5000;
121#else
122  const int kNumKeys = 1700;
123#endif
124  const int kNumEntries = 30;
125  std::string keys[kNumKeys];
126  disk_cache::Entry* entries[kNumEntries] = {0};
127
128  for (int i = 0; i < kNumKeys; i++) {
129    keys[i] = GenerateKey(true);
130  }
131
132  const int kSize = 4000;
133  scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
134  memset(buffer->data(), 'k', kSize);
135
136  for (int i = 0;; i++) {
137    int slot = rand() % kNumEntries;
138    int key = rand() % kNumKeys;
139    bool truncate = rand() % 2 ? false : true;
140    int size = kSize - (rand() % 4) * kSize / 4;
141
142    if (entries[slot])
143      entries[slot]->Close();
144
145    rv = cache->OpenEntry(keys[key], &entries[slot], &cb);
146    if (cb.GetResult(rv) != net::OK) {
147      rv = cache->CreateEntry(keys[key], &entries[slot], &cb);
148      CHECK_EQ(net::OK, cb.GetResult(rv));
149    }
150
151    base::snprintf(buffer->data(), kSize,
152                   "i: %d iter: %d, size: %d, truncate: %d", i, iteration, size,
153                   truncate ? 1 : 0);
154    rv = entries[slot]->WriteData(0, 0, buffer, size, &cb, truncate);
155    CHECK_EQ(size, cb.GetResult(rv));
156
157    if (rand() % 100 > 80) {
158      key = rand() % kNumKeys;
159      rv = cache->DoomEntry(keys[key], &cb);
160      cb.GetResult(rv);
161    }
162
163    if (!(i % 100))
164      printf("Entries: %d    \r", i);
165  }
166}
167
168// We want to prevent the timer thread from killing the process while we are
169// waiting for the debugger to attach.
170bool g_crashing = false;
171
172class CrashTask : public Task {
173 public:
174  CrashTask() {}
175  ~CrashTask() {}
176
177  virtual void Run() {
178    // Keep trying to run.
179    RunSoon(MessageLoop::current());
180
181    if (g_crashing)
182      return;
183
184    if (rand() % 100 > 1) {
185      printf("sweet death...\n");
186#if defined(OS_WIN)
187      // Windows does more work on _exit() that we would like, so we use Kill.
188      base::KillProcessById(base::GetCurrentProcId(), kExpectedCrash, false);
189#elif defined(OS_POSIX)
190      // On POSIX, _exit() will terminate the process with minimal cleanup,
191      // and it is cleaner than killing.
192      _exit(kExpectedCrash);
193#endif
194    }
195  }
196
197  static void RunSoon(MessageLoop* target_loop) {
198    int task_delay = 10000;  // 10 seconds
199    CrashTask* task = new CrashTask();
200    target_loop->PostDelayedTask(FROM_HERE, task, task_delay);
201  }
202};
203
204// We leak everything here :)
205bool StartCrashThread() {
206  base::Thread* thread = new base::Thread("party_crasher");
207  if (!thread->Start())
208    return false;
209
210  CrashTask::RunSoon(thread->message_loop());
211  return true;
212}
213
214void CrashHandler(const std::string& str) {
215  g_crashing = true;
216  base::debug::BreakDebugger();
217}
218
219// -----------------------------------------------------------------------
220
221int main(int argc, const char* argv[]) {
222  // Setup an AtExitManager so Singleton objects will be destructed.
223  base::AtExitManager at_exit_manager;
224
225  if (argc < 2)
226    return MasterCode();
227
228  logging::SetLogAssertHandler(CrashHandler);
229
230  // Some time for the memory manager to flush stuff.
231  base::PlatformThread::Sleep(3000);
232  MessageLoop message_loop(MessageLoop::TYPE_IO);
233
234  char* end;
235  long int iteration = strtol(argv[1], &end, 0);
236
237  if (!StartCrashThread()) {
238    printf("failed to start thread\n");
239    return kError;
240  }
241
242  StressTheCache(iteration);
243  return 0;
244}
245