blob: 875dfe81eeab39656a57deade1589895a6d87cd7 [file] [log] [blame]
Chris Mumfordb234f652014-12-11 07:59:38 -08001// 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
26namespace leveldb {
27
28static const int kValueSize = 1000;
29static const int kMaxNumValues = 2000;
30static const size_t kNumIterations = 3;
31
32class FaultInjectionTestEnv;
33
34namespace {
35
36// Assume a filename, and not a directory name like "/foo/bar/"
37static 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
46Status 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.
52Status Truncate(const std::string& filename, uint64_t length) {
53 leveldb::Env* env = leveldb::Env::Default();
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
84struct FileState {
85 std::string filename_;
86 ssize_t pos_;
87 ssize_t pos_at_last_sync_;
88 ssize_t pos_at_last_flush_;
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.
107class TestWritableFile : public WritableFile {
108 public:
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800109 TestWritableFile(const FileState& state,
Chris Mumfordb234f652014-12-11 07:59:38 -0800110 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_;
120 WritableFile* target_;
121 bool writable_file_opened_;
122 FaultInjectionTestEnv* env_;
123
124 Status SyncParent();
125};
126
127class FaultInjectionTestEnv : public EnvWrapper {
128 public:
129 FaultInjectionTestEnv() : EnvWrapper(Env::Default()), filesystem_active_(true) {}
130 virtual ~FaultInjectionTestEnv() { }
131 virtual Status NewWritableFile(const std::string& fname,
132 WritableFile** result);
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800133 virtual Status NewAppendableFile(const std::string& fname,
134 WritableFile** result);
Chris Mumfordb234f652014-12-11 07:59:38 -0800135 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);
139 Status DropUnsyncedFileData();
140 Status DeleteFilesCreatedAfterLastDirSync();
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
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800159TestWritableFile::TestWritableFile(const FileState& state,
Chris Mumfordb234f652014-12-11 07:59:38 -0800160 WritableFile* f,
161 FaultInjectionTestEnv* env)
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800162 : state_(state),
Chris Mumfordb234f652014-12-11 07:59:38 -0800163 target_(f),
164 writable_file_opened_(true),
165 env_(env) {
166 assert(f != NULL);
Chris Mumfordb234f652014-12-11 07:59:38 -0800167}
168
169TestWritableFile::~TestWritableFile() {
170 if (writable_file_opened_) {
171 Close();
172 }
173 delete target_;
174}
175
176Status TestWritableFile::Append(const Slice& data) {
177 Status s = target_->Append(data);
178 if (s.ok() && env_->IsFilesystemActive()) {
179 state_.pos_ += data.size();
180 }
181 return s;
182}
183
184Status TestWritableFile::Close() {
185 writable_file_opened_ = false;
186 Status s = target_->Close();
187 if (s.ok()) {
188 env_->WritableFileClosed(state_);
189 }
190 return s;
191}
192
193Status TestWritableFile::Flush() {
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
201Status TestWritableFile::SyncParent() {
202 Status s = SyncDir(GetDirName(state_.filename_));
203 if (s.ok()) {
204 env_->DirWasSynced();
205 }
206 return s;
207}
208
209Status TestWritableFile::Sync() {
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
227Status FaultInjectionTestEnv::NewWritableFile(const std::string& fname,
228 WritableFile** result) {
229 WritableFile* actual_writable_file;
230 Status s = target()->NewWritableFile(fname, &actual_writable_file);
231 if (s.ok()) {
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800232 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.
Chris Mumfordb234f652014-12-11 07:59:38 -0800238 UntrackFile(fname);
239 MutexLock l(&mutex_);
240 new_files_since_last_dir_sync_.insert(fname);
241 }
242 return s;
243}
244
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800245Status FaultInjectionTestEnv::NewAppendableFile(const std::string& fname,
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
Chris Mumfordb234f652014-12-11 07:59:38 -0800265Status FaultInjectionTestEnv::DropUnsyncedFileData() {
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
279void FaultInjectionTestEnv::DirWasSynced() {
280 MutexLock l(&mutex_);
281 new_files_since_last_dir_sync_.clear();
282}
283
284bool FaultInjectionTestEnv::IsFileCreatedSinceLastDirSync(
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
291void 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
297Status FaultInjectionTestEnv::DeleteFile(const std::string& f) {
298 Status s = EnvWrapper::DeleteFile(f);
299 ASSERT_OK(s);
300 if (s.ok()) {
301 UntrackFile(f);
302 }
303 return s;
304}
305
306Status FaultInjectionTestEnv::RenameFile(const std::string& s,
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
327void FaultInjectionTestEnv::ResetState() {
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800328 // 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_.
Chris Mumfordb234f652014-12-11 07:59:38 -0800331 MutexLock l(&mutex_);
Chris Mumfordb234f652014-12-11 07:59:38 -0800332 SetFilesystemActive(true);
333}
334
335Status FaultInjectionTestEnv::DeleteFilesCreatedAfterLastDirSync() {
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
349void FaultInjectionTestEnv::WritableFileClosed(const FileState& state) {
350 MutexLock l(&mutex_);
351 db_file_state_[state.filename_] = state;
352}
353
354Status 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
359class FaultInjectionTest {
360 public:
361 enum ExpectedVerifResult { VAL_EXPECT_NO_ERROR, VAL_EXPECT_ERROR };
362 enum ResetMethod { RESET_DROP_UNSYNCED_DATA, RESET_DELETE_UNSYNCED_FILES };
363
364 FaultInjectionTestEnv* env_;
365 std::string dbname_;
366 Cache* tiny_cache_;
367 Options options_;
368 DB* db_;
369
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800370 FaultInjectionTest()
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;
Chris Mumfordb234f652014-12-11 07:59:38 -0800377 options_.env = env_;
378 options_.paranoid_checks = true;
Chris Mumfordb234f652014-12-11 07:59:38 -0800379 options_.block_cache = tiny_cache_;
Chris Mumfordb234f652014-12-11 07:59:38 -0800380 options_.create_if_missing = true;
Chris Mumfordb234f652014-12-11 07:59:38 -0800381 }
382
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800383 ~FaultInjectionTest() {
Chris Mumfordb234f652014-12-11 07:59:38 -0800384 CloseDB();
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800385 DestroyDB(dbname_, Options());
Chris Mumfordb234f652014-12-11 07:59:38 -0800386 delete tiny_cache_;
Chris Mumfordb234f652014-12-11 07:59:38 -0800387 delete env_;
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800388 }
Chris Mumfordb234f652014-12-11 07:59:38 -0800389
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800390 void ReuseLogs(bool reuse) {
391 options_.reuse_logs = reuse;
Chris Mumfordb234f652014-12-11 07:59:38 -0800392 }
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
450 Status OpenDB() {
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:
475 ASSERT_OK(env_->DropUnsyncedFileData());
476 break;
477 case RESET_DELETE_UNSYNCED_FILES:
478 ASSERT_OK(env_->DeleteFilesCreatedAfterLastDirSync());
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
492 void PartialCompactTestReopenWithFault(ResetMethod reset_method,
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
503 void NoWriteTestPreFault() {
504 }
505
506 void NoWriteTestReopenWithFault(ResetMethod reset_method) {
507 CloseDB();
508 ResetDBState(reset_method);
509 ASSERT_OK(OpenDB());
510 }
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800511
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 }
Chris Mumfordb234f652014-12-11 07:59:38 -0800538};
539
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800540TEST(FaultInjectionTest, FaultTestNoLogReuse) {
541 ReuseLogs(false);
542 DoTest();
543}
Chris Mumfordb234f652014-12-11 07:59:38 -0800544
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800545TEST(FaultInjectionTest, FaultTestWithLogReuse) {
546 ReuseLogs(true);
547 DoTest();
Chris Mumfordb234f652014-12-11 07:59:38 -0800548}
549
550} // namespace leveldb
551
552int main(int argc, char** argv) {
553 return leveldb::test::RunAllTests();
554}