xref: /aosp_15_r20/external/leveldb/db/fault_injection_test.cc (revision 9507f98c5f32dee4b5f9e4a38cd499f3ff5c4490)
1 // Copyright 2014 The LevelDB 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. See the AUTHORS file for names of contributors.
4 
5 // This test uses a custom Env to keep track of the state of a filesystem as of
6 // the last "sync". It then checks for data loss errors by purposely dropping
7 // file data (or entire files) not protected by a "sync".
8 
9 #include <map>
10 #include <set>
11 
12 #include "gtest/gtest.h"
13 #include "db/db_impl.h"
14 #include "db/filename.h"
15 #include "db/log_format.h"
16 #include "db/version_set.h"
17 #include "leveldb/cache.h"
18 #include "leveldb/db.h"
19 #include "leveldb/env.h"
20 #include "leveldb/table.h"
21 #include "leveldb/write_batch.h"
22 #include "port/port.h"
23 #include "port/thread_annotations.h"
24 #include "util/logging.h"
25 #include "util/mutexlock.h"
26 #include "util/testutil.h"
27 
28 namespace leveldb {
29 
30 static const int kValueSize = 1000;
31 static const int kMaxNumValues = 2000;
32 static const size_t kNumIterations = 3;
33 
34 class FaultInjectionTestEnv;
35 
36 namespace {
37 
38 // Assume a filename, and not a directory name like "/foo/bar/"
GetDirName(const std::string & filename)39 static std::string GetDirName(const std::string& filename) {
40   size_t found = filename.find_last_of("/\\");
41   if (found == std::string::npos) {
42     return "";
43   } else {
44     return filename.substr(0, found);
45   }
46 }
47 
SyncDir(const std::string & dir)48 Status SyncDir(const std::string& dir) {
49   // As this is a test it isn't required to *actually* sync this directory.
50   return Status::OK();
51 }
52 
53 // A basic file truncation function suitable for this test.
Truncate(const std::string & filename,uint64_t length)54 Status Truncate(const std::string& filename, uint64_t length) {
55   leveldb::Env* env = leveldb::Env::Default();
56 
57   SequentialFile* orig_file;
58   Status s = env->NewSequentialFile(filename, &orig_file);
59   if (!s.ok()) return s;
60 
61   char* scratch = new char[length];
62   leveldb::Slice result;
63   s = orig_file->Read(length, &result, scratch);
64   delete orig_file;
65   if (s.ok()) {
66     std::string tmp_name = GetDirName(filename) + "/truncate.tmp";
67     WritableFile* tmp_file;
68     s = env->NewWritableFile(tmp_name, &tmp_file);
69     if (s.ok()) {
70       s = tmp_file->Append(result);
71       delete tmp_file;
72       if (s.ok()) {
73         s = env->RenameFile(tmp_name, filename);
74       } else {
75         env->RemoveFile(tmp_name);
76       }
77     }
78   }
79 
80   delete[] scratch;
81 
82   return s;
83 }
84 
85 struct FileState {
86   std::string filename_;
87   int64_t pos_;
88   int64_t pos_at_last_sync_;
89   int64_t pos_at_last_flush_;
90 
FileStateleveldb::__anond85a411a0111::FileState91   FileState(const std::string& filename)
92       : filename_(filename),
93         pos_(-1),
94         pos_at_last_sync_(-1),
95         pos_at_last_flush_(-1) {}
96 
FileStateleveldb::__anond85a411a0111::FileState97   FileState() : pos_(-1), pos_at_last_sync_(-1), pos_at_last_flush_(-1) {}
98 
IsFullySyncedleveldb::__anond85a411a0111::FileState99   bool IsFullySynced() const { return pos_ <= 0 || pos_ == pos_at_last_sync_; }
100 
101   Status DropUnsyncedData() const;
102 };
103 
104 }  // anonymous namespace
105 
106 // A wrapper around WritableFile which informs another Env whenever this file
107 // is written to or sync'ed.
108 class TestWritableFile : public WritableFile {
109  public:
110   TestWritableFile(const FileState& state, WritableFile* f,
111                    FaultInjectionTestEnv* env);
112   ~TestWritableFile() override;
113   Status Append(const Slice& data) override;
114   Status Close() override;
115   Status Flush() override;
116   Status Sync() override;
117 
118  private:
119   FileState state_;
120   WritableFile* target_;
121   bool writable_file_opened_;
122   FaultInjectionTestEnv* env_;
123 
124   Status SyncParent();
125 };
126 
127 class FaultInjectionTestEnv : public EnvWrapper {
128  public:
FaultInjectionTestEnv()129   FaultInjectionTestEnv()
130       : EnvWrapper(Env::Default()), filesystem_active_(true) {}
131   ~FaultInjectionTestEnv() override = default;
132   Status NewWritableFile(const std::string& fname,
133                          WritableFile** result) override;
134   Status NewAppendableFile(const std::string& fname,
135                            WritableFile** result) override;
136   Status RemoveFile(const std::string& f) override;
137   Status RenameFile(const std::string& s, const std::string& t) override;
138 
139   void WritableFileClosed(const FileState& state);
140   Status DropUnsyncedFileData();
141   Status RemoveFilesCreatedAfterLastDirSync();
142   void DirWasSynced();
143   bool IsFileCreatedSinceLastDirSync(const std::string& filename);
144   void ResetState();
145   void UntrackFile(const std::string& f);
146   // Setting the filesystem to inactive is the test equivalent to simulating a
147   // system reset. Setting to inactive will freeze our saved filesystem state so
148   // that it will stop being recorded. It can then be reset back to the state at
149   // the time of the reset.
IsFilesystemActive()150   bool IsFilesystemActive() LOCKS_EXCLUDED(mutex_) {
151     MutexLock l(&mutex_);
152     return filesystem_active_;
153   }
SetFilesystemActive(bool active)154   void SetFilesystemActive(bool active) LOCKS_EXCLUDED(mutex_) {
155     MutexLock l(&mutex_);
156     filesystem_active_ = active;
157   }
158 
159  private:
160   port::Mutex mutex_;
161   std::map<std::string, FileState> db_file_state_ GUARDED_BY(mutex_);
162   std::set<std::string> new_files_since_last_dir_sync_ GUARDED_BY(mutex_);
163   bool filesystem_active_ GUARDED_BY(mutex_);  // Record flushes, syncs, writes
164 };
165 
TestWritableFile(const FileState & state,WritableFile * f,FaultInjectionTestEnv * env)166 TestWritableFile::TestWritableFile(const FileState& state, WritableFile* f,
167                                    FaultInjectionTestEnv* env)
168     : state_(state), target_(f), writable_file_opened_(true), env_(env) {
169   assert(f != nullptr);
170 }
171 
~TestWritableFile()172 TestWritableFile::~TestWritableFile() {
173   if (writable_file_opened_) {
174     Close();
175   }
176   delete target_;
177 }
178 
Append(const Slice & data)179 Status TestWritableFile::Append(const Slice& data) {
180   Status s = target_->Append(data);
181   if (s.ok() && env_->IsFilesystemActive()) {
182     state_.pos_ += data.size();
183   }
184   return s;
185 }
186 
Close()187 Status TestWritableFile::Close() {
188   writable_file_opened_ = false;
189   Status s = target_->Close();
190   if (s.ok()) {
191     env_->WritableFileClosed(state_);
192   }
193   return s;
194 }
195 
Flush()196 Status TestWritableFile::Flush() {
197   Status s = target_->Flush();
198   if (s.ok() && env_->IsFilesystemActive()) {
199     state_.pos_at_last_flush_ = state_.pos_;
200   }
201   return s;
202 }
203 
SyncParent()204 Status TestWritableFile::SyncParent() {
205   Status s = SyncDir(GetDirName(state_.filename_));
206   if (s.ok()) {
207     env_->DirWasSynced();
208   }
209   return s;
210 }
211 
Sync()212 Status TestWritableFile::Sync() {
213   if (!env_->IsFilesystemActive()) {
214     return Status::OK();
215   }
216   // Ensure new files referred to by the manifest are in the filesystem.
217   Status s = target_->Sync();
218   if (s.ok()) {
219     state_.pos_at_last_sync_ = state_.pos_;
220   }
221   if (env_->IsFileCreatedSinceLastDirSync(state_.filename_)) {
222     Status ps = SyncParent();
223     if (s.ok() && !ps.ok()) {
224       s = ps;
225     }
226   }
227   return s;
228 }
229 
NewWritableFile(const std::string & fname,WritableFile ** result)230 Status FaultInjectionTestEnv::NewWritableFile(const std::string& fname,
231                                               WritableFile** result) {
232   WritableFile* actual_writable_file;
233   Status s = target()->NewWritableFile(fname, &actual_writable_file);
234   if (s.ok()) {
235     FileState state(fname);
236     state.pos_ = 0;
237     *result = new TestWritableFile(state, actual_writable_file, this);
238     // NewWritableFile doesn't append to files, so if the same file is
239     // opened again then it will be truncated - so forget our saved
240     // state.
241     UntrackFile(fname);
242     MutexLock l(&mutex_);
243     new_files_since_last_dir_sync_.insert(fname);
244   }
245   return s;
246 }
247 
NewAppendableFile(const std::string & fname,WritableFile ** result)248 Status FaultInjectionTestEnv::NewAppendableFile(const std::string& fname,
249                                                 WritableFile** result) {
250   WritableFile* actual_writable_file;
251   Status s = target()->NewAppendableFile(fname, &actual_writable_file);
252   if (s.ok()) {
253     FileState state(fname);
254     state.pos_ = 0;
255     {
256       MutexLock l(&mutex_);
257       if (db_file_state_.count(fname) == 0) {
258         new_files_since_last_dir_sync_.insert(fname);
259       } else {
260         state = db_file_state_[fname];
261       }
262     }
263     *result = new TestWritableFile(state, actual_writable_file, this);
264   }
265   return s;
266 }
267 
DropUnsyncedFileData()268 Status FaultInjectionTestEnv::DropUnsyncedFileData() {
269   Status s;
270   MutexLock l(&mutex_);
271   for (const auto& kvp : db_file_state_) {
272     if (!s.ok()) {
273       break;
274     }
275     const FileState& state = kvp.second;
276     if (!state.IsFullySynced()) {
277       s = state.DropUnsyncedData();
278     }
279   }
280   return s;
281 }
282 
DirWasSynced()283 void FaultInjectionTestEnv::DirWasSynced() {
284   MutexLock l(&mutex_);
285   new_files_since_last_dir_sync_.clear();
286 }
287 
IsFileCreatedSinceLastDirSync(const std::string & filename)288 bool FaultInjectionTestEnv::IsFileCreatedSinceLastDirSync(
289     const std::string& filename) {
290   MutexLock l(&mutex_);
291   return new_files_since_last_dir_sync_.find(filename) !=
292          new_files_since_last_dir_sync_.end();
293 }
294 
UntrackFile(const std::string & f)295 void FaultInjectionTestEnv::UntrackFile(const std::string& f) {
296   MutexLock l(&mutex_);
297   db_file_state_.erase(f);
298   new_files_since_last_dir_sync_.erase(f);
299 }
300 
RemoveFile(const std::string & f)301 Status FaultInjectionTestEnv::RemoveFile(const std::string& f) {
302   Status s = EnvWrapper::RemoveFile(f);
303   EXPECT_LEVELDB_OK(s);
304   if (s.ok()) {
305     UntrackFile(f);
306   }
307   return s;
308 }
309 
RenameFile(const std::string & s,const std::string & t)310 Status FaultInjectionTestEnv::RenameFile(const std::string& s,
311                                          const std::string& t) {
312   Status ret = EnvWrapper::RenameFile(s, t);
313 
314   if (ret.ok()) {
315     MutexLock l(&mutex_);
316     if (db_file_state_.find(s) != db_file_state_.end()) {
317       db_file_state_[t] = db_file_state_[s];
318       db_file_state_.erase(s);
319     }
320 
321     if (new_files_since_last_dir_sync_.erase(s) != 0) {
322       assert(new_files_since_last_dir_sync_.find(t) ==
323              new_files_since_last_dir_sync_.end());
324       new_files_since_last_dir_sync_.insert(t);
325     }
326   }
327 
328   return ret;
329 }
330 
ResetState()331 void FaultInjectionTestEnv::ResetState() {
332   // Since we are not destroying the database, the existing files
333   // should keep their recorded synced/flushed state. Therefore
334   // we do not reset db_file_state_ and new_files_since_last_dir_sync_.
335   SetFilesystemActive(true);
336 }
337 
RemoveFilesCreatedAfterLastDirSync()338 Status FaultInjectionTestEnv::RemoveFilesCreatedAfterLastDirSync() {
339   // Because RemoveFile access this container make a copy to avoid deadlock
340   mutex_.Lock();
341   std::set<std::string> new_files(new_files_since_last_dir_sync_.begin(),
342                                   new_files_since_last_dir_sync_.end());
343   mutex_.Unlock();
344   Status status;
345   for (const auto& new_file : new_files) {
346     Status remove_status = RemoveFile(new_file);
347     if (!remove_status.ok() && status.ok()) {
348       status = std::move(remove_status);
349     }
350   }
351   return status;
352 }
353 
WritableFileClosed(const FileState & state)354 void FaultInjectionTestEnv::WritableFileClosed(const FileState& state) {
355   MutexLock l(&mutex_);
356   db_file_state_[state.filename_] = state;
357 }
358 
DropUnsyncedData() const359 Status FileState::DropUnsyncedData() const {
360   int64_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_;
361   return Truncate(filename_, sync_pos);
362 }
363 
364 class FaultInjectionTest : public testing::Test {
365  public:
366   enum ExpectedVerifResult { VAL_EXPECT_NO_ERROR, VAL_EXPECT_ERROR };
367   enum ResetMethod { RESET_DROP_UNSYNCED_DATA, RESET_DELETE_UNSYNCED_FILES };
368 
369   FaultInjectionTestEnv* env_;
370   std::string dbname_;
371   Cache* tiny_cache_;
372   Options options_;
373   DB* db_;
374 
FaultInjectionTest()375   FaultInjectionTest()
376       : env_(new FaultInjectionTestEnv),
377         tiny_cache_(NewLRUCache(100)),
378         db_(nullptr) {
379     dbname_ = testing::TempDir() + "fault_test";
380     DestroyDB(dbname_, Options());  // Destroy any db from earlier run
381     options_.reuse_logs = true;
382     options_.env = env_;
383     options_.paranoid_checks = true;
384     options_.block_cache = tiny_cache_;
385     options_.create_if_missing = true;
386   }
387 
~FaultInjectionTest()388   ~FaultInjectionTest() {
389     CloseDB();
390     DestroyDB(dbname_, Options());
391     delete tiny_cache_;
392     delete env_;
393   }
394 
ReuseLogs(bool reuse)395   void ReuseLogs(bool reuse) { options_.reuse_logs = reuse; }
396 
Build(int start_idx,int num_vals)397   void Build(int start_idx, int num_vals) {
398     std::string key_space, value_space;
399     WriteBatch batch;
400     for (int i = start_idx; i < start_idx + num_vals; i++) {
401       Slice key = Key(i, &key_space);
402       batch.Clear();
403       batch.Put(key, Value(i, &value_space));
404       WriteOptions options;
405       ASSERT_LEVELDB_OK(db_->Write(options, &batch));
406     }
407   }
408 
ReadValue(int i,std::string * val) const409   Status ReadValue(int i, std::string* val) const {
410     std::string key_space, value_space;
411     Slice key = Key(i, &key_space);
412     Value(i, &value_space);
413     ReadOptions options;
414     return db_->Get(options, key, val);
415   }
416 
Verify(int start_idx,int num_vals,ExpectedVerifResult expected) const417   Status Verify(int start_idx, int num_vals,
418                 ExpectedVerifResult expected) const {
419     std::string val;
420     std::string value_space;
421     Status s;
422     for (int i = start_idx; i < start_idx + num_vals && s.ok(); i++) {
423       Value(i, &value_space);
424       s = ReadValue(i, &val);
425       if (expected == VAL_EXPECT_NO_ERROR) {
426         if (s.ok()) {
427           EXPECT_EQ(value_space, val);
428         }
429       } else if (s.ok()) {
430         std::fprintf(stderr, "Expected an error at %d, but was OK\n", i);
431         s = Status::IOError(dbname_, "Expected value error:");
432       } else {
433         s = Status::OK();  // An expected error
434       }
435     }
436     return s;
437   }
438 
439   // Return the ith key
Key(int i,std::string * storage) const440   Slice Key(int i, std::string* storage) const {
441     char buf[100];
442     std::snprintf(buf, sizeof(buf), "%016d", i);
443     storage->assign(buf, strlen(buf));
444     return Slice(*storage);
445   }
446 
447   // Return the value to associate with the specified key
Value(int k,std::string * storage) const448   Slice Value(int k, std::string* storage) const {
449     Random r(k);
450     return test::RandomString(&r, kValueSize, storage);
451   }
452 
OpenDB()453   Status OpenDB() {
454     delete db_;
455     db_ = nullptr;
456     env_->ResetState();
457     return DB::Open(options_, dbname_, &db_);
458   }
459 
CloseDB()460   void CloseDB() {
461     delete db_;
462     db_ = nullptr;
463   }
464 
DeleteAllData()465   void DeleteAllData() {
466     Iterator* iter = db_->NewIterator(ReadOptions());
467     for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
468       ASSERT_LEVELDB_OK(db_->Delete(WriteOptions(), iter->key()));
469     }
470 
471     delete iter;
472   }
473 
ResetDBState(ResetMethod reset_method)474   void ResetDBState(ResetMethod reset_method) {
475     switch (reset_method) {
476       case RESET_DROP_UNSYNCED_DATA:
477         ASSERT_LEVELDB_OK(env_->DropUnsyncedFileData());
478         break;
479       case RESET_DELETE_UNSYNCED_FILES:
480         ASSERT_LEVELDB_OK(env_->RemoveFilesCreatedAfterLastDirSync());
481         break;
482       default:
483         assert(false);
484     }
485   }
486 
PartialCompactTestPreFault(int num_pre_sync,int num_post_sync)487   void PartialCompactTestPreFault(int num_pre_sync, int num_post_sync) {
488     DeleteAllData();
489     Build(0, num_pre_sync);
490     db_->CompactRange(nullptr, nullptr);
491     Build(num_pre_sync, num_post_sync);
492   }
493 
PartialCompactTestReopenWithFault(ResetMethod reset_method,int num_pre_sync,int num_post_sync)494   void PartialCompactTestReopenWithFault(ResetMethod reset_method,
495                                          int num_pre_sync, int num_post_sync) {
496     env_->SetFilesystemActive(false);
497     CloseDB();
498     ResetDBState(reset_method);
499     ASSERT_LEVELDB_OK(OpenDB());
500     ASSERT_LEVELDB_OK(
501         Verify(0, num_pre_sync, FaultInjectionTest::VAL_EXPECT_NO_ERROR));
502     ASSERT_LEVELDB_OK(Verify(num_pre_sync, num_post_sync,
503                              FaultInjectionTest::VAL_EXPECT_ERROR));
504   }
505 
NoWriteTestPreFault()506   void NoWriteTestPreFault() {}
507 
NoWriteTestReopenWithFault(ResetMethod reset_method)508   void NoWriteTestReopenWithFault(ResetMethod reset_method) {
509     CloseDB();
510     ResetDBState(reset_method);
511     ASSERT_LEVELDB_OK(OpenDB());
512   }
513 
DoTest()514   void DoTest() {
515     Random rnd(0);
516     ASSERT_LEVELDB_OK(OpenDB());
517     for (size_t idx = 0; idx < kNumIterations; idx++) {
518       int num_pre_sync = rnd.Uniform(kMaxNumValues);
519       int num_post_sync = rnd.Uniform(kMaxNumValues);
520 
521       PartialCompactTestPreFault(num_pre_sync, num_post_sync);
522       PartialCompactTestReopenWithFault(RESET_DROP_UNSYNCED_DATA, num_pre_sync,
523                                         num_post_sync);
524 
525       NoWriteTestPreFault();
526       NoWriteTestReopenWithFault(RESET_DROP_UNSYNCED_DATA);
527 
528       PartialCompactTestPreFault(num_pre_sync, num_post_sync);
529       // No new files created so we expect all values since no files will be
530       // dropped.
531       PartialCompactTestReopenWithFault(RESET_DELETE_UNSYNCED_FILES,
532                                         num_pre_sync + num_post_sync, 0);
533 
534       NoWriteTestPreFault();
535       NoWriteTestReopenWithFault(RESET_DELETE_UNSYNCED_FILES);
536     }
537   }
538 };
539 
TEST_F(FaultInjectionTest,FaultTestNoLogReuse)540 TEST_F(FaultInjectionTest, FaultTestNoLogReuse) {
541   ReuseLogs(false);
542   DoTest();
543 }
544 
TEST_F(FaultInjectionTest,FaultTestWithLogReuse)545 TEST_F(FaultInjectionTest, FaultTestWithLogReuse) {
546   ReuseLogs(true);
547   DoTest();
548 }
549 
550 }  // namespace leveldb
551 
552