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