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