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