blob: 1f72984b70e4d9c725c23c18f0ccbb620dfb9225 [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
Chris Mumfordb234f652014-12-11 07:59:38 -08009#include <map>
10#include <set>
costan0db30412018-03-23 12:50:14 -070011
12#include "leveldb/db.h"
Chris Mumfordb234f652014-12-11 07:59:38 -080013#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"
costan0db30412018-03-23 12:50:14 -070021#include "port/port.h"
22#include "port/thread_annotations.h"
Chris Mumfordb234f652014-12-11 07:59:38 -080023#include "util/logging.h"
24#include "util/mutexlock.h"
25#include "util/testharness.h"
26#include "util/testutil.h"
27
28namespace leveldb {
29
30static const int kValueSize = 1000;
31static const int kMaxNumValues = 2000;
32static const size_t kNumIterations = 3;
33
34class FaultInjectionTestEnv;
35
36namespace {
37
38// Assume a filename, and not a directory name like "/foo/bar/"
costan23162ca2017-10-10 11:29:00 -070039static std::string GetDirName(const std::string& filename) {
Chris Mumfordb234f652014-12-11 07:59:38 -080040 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
48Status 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.
54Status 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())
60 return s;
61
62 char* scratch = new char[length];
63 leveldb::Slice result;
64 s = orig_file->Read(length, &result, scratch);
65 delete orig_file;
66 if (s.ok()) {
67 std::string tmp_name = GetDirName(filename) + "/truncate.tmp";
68 WritableFile* tmp_file;
69 s = env->NewWritableFile(tmp_name, &tmp_file);
70 if (s.ok()) {
71 s = tmp_file->Append(result);
72 delete tmp_file;
73 if (s.ok()) {
74 s = env->RenameFile(tmp_name, filename);
75 } else {
76 env->DeleteFile(tmp_name);
77 }
78 }
79 }
80
81 delete[] scratch;
82
83 return s;
84}
85
86struct FileState {
87 std::string filename_;
costan89af27b2018-09-04 09:44:56 -070088 int64_t pos_;
89 int64_t pos_at_last_sync_;
90 int64_t pos_at_last_flush_;
Chris Mumfordb234f652014-12-11 07:59:38 -080091
92 FileState(const std::string& filename)
93 : filename_(filename),
94 pos_(-1),
95 pos_at_last_sync_(-1),
96 pos_at_last_flush_(-1) { }
97
98 FileState() : pos_(-1), pos_at_last_sync_(-1), pos_at_last_flush_(-1) {}
99
100 bool IsFullySynced() const { return pos_ <= 0 || pos_ == pos_at_last_sync_; }
101
102 Status DropUnsyncedData() const;
103};
104
105} // anonymous namespace
106
107// A wrapper around WritableFile which informs another Env whenever this file
108// is written to or sync'ed.
109class TestWritableFile : public WritableFile {
110 public:
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800111 TestWritableFile(const FileState& state,
Chris Mumfordb234f652014-12-11 07:59:38 -0800112 WritableFile* f,
113 FaultInjectionTestEnv* env);
114 virtual ~TestWritableFile();
115 virtual Status Append(const Slice& data);
116 virtual Status Close();
117 virtual Status Flush();
118 virtual Status Sync();
119
120 private:
121 FileState state_;
122 WritableFile* target_;
123 bool writable_file_opened_;
124 FaultInjectionTestEnv* env_;
125
126 Status SyncParent();
127};
128
129class FaultInjectionTestEnv : public EnvWrapper {
130 public:
costan0db30412018-03-23 12:50:14 -0700131 FaultInjectionTestEnv()
132 : EnvWrapper(Env::Default()), filesystem_active_(true) {}
Chris Mumfordb234f652014-12-11 07:59:38 -0800133 virtual ~FaultInjectionTestEnv() { }
134 virtual Status NewWritableFile(const std::string& fname,
135 WritableFile** result);
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800136 virtual Status NewAppendableFile(const std::string& fname,
137 WritableFile** result);
Chris Mumfordb234f652014-12-11 07:59:38 -0800138 virtual Status DeleteFile(const std::string& f);
139 virtual Status RenameFile(const std::string& s, const std::string& t);
140
141 void WritableFileClosed(const FileState& state);
142 Status DropUnsyncedFileData();
143 Status DeleteFilesCreatedAfterLastDirSync();
144 void DirWasSynced();
145 bool IsFileCreatedSinceLastDirSync(const std::string& filename);
146 void ResetState();
147 void UntrackFile(const std::string& f);
148 // Setting the filesystem to inactive is the test equivalent to simulating a
149 // system reset. Setting to inactive will freeze our saved filesystem state so
150 // that it will stop being recorded. It can then be reset back to the state at
151 // the time of the reset.
costan0db30412018-03-23 12:50:14 -0700152 bool IsFilesystemActive() LOCKS_EXCLUDED(mutex_) {
153 MutexLock l(&mutex_);
154 return filesystem_active_;
155 }
156 void SetFilesystemActive(bool active) LOCKS_EXCLUDED(mutex_) {
157 MutexLock l(&mutex_);
158 filesystem_active_ = active;
159 }
Chris Mumfordb234f652014-12-11 07:59:38 -0800160
161 private:
162 port::Mutex mutex_;
costan0db30412018-03-23 12:50:14 -0700163 std::map<std::string, FileState> db_file_state_ GUARDED_BY(mutex_);
164 std::set<std::string> new_files_since_last_dir_sync_ GUARDED_BY(mutex_);
165 bool filesystem_active_ GUARDED_BY(mutex_); // Record flushes, syncs, writes
Chris Mumfordb234f652014-12-11 07:59:38 -0800166};
167
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800168TestWritableFile::TestWritableFile(const FileState& state,
Chris Mumfordb234f652014-12-11 07:59:38 -0800169 WritableFile* f,
170 FaultInjectionTestEnv* env)
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800171 : state_(state),
Chris Mumfordb234f652014-12-11 07:59:38 -0800172 target_(f),
173 writable_file_opened_(true),
174 env_(env) {
costan09217fd2018-04-10 16:18:06 -0700175 assert(f != nullptr);
Chris Mumfordb234f652014-12-11 07:59:38 -0800176}
177
178TestWritableFile::~TestWritableFile() {
179 if (writable_file_opened_) {
180 Close();
181 }
182 delete target_;
183}
184
185Status TestWritableFile::Append(const Slice& data) {
186 Status s = target_->Append(data);
187 if (s.ok() && env_->IsFilesystemActive()) {
188 state_.pos_ += data.size();
189 }
190 return s;
191}
192
193Status TestWritableFile::Close() {
194 writable_file_opened_ = false;
195 Status s = target_->Close();
196 if (s.ok()) {
197 env_->WritableFileClosed(state_);
198 }
199 return s;
200}
201
202Status TestWritableFile::Flush() {
203 Status s = target_->Flush();
204 if (s.ok() && env_->IsFilesystemActive()) {
205 state_.pos_at_last_flush_ = state_.pos_;
206 }
207 return s;
208}
209
210Status TestWritableFile::SyncParent() {
211 Status s = SyncDir(GetDirName(state_.filename_));
212 if (s.ok()) {
213 env_->DirWasSynced();
214 }
215 return s;
216}
217
218Status TestWritableFile::Sync() {
219 if (!env_->IsFilesystemActive()) {
220 return Status::OK();
221 }
222 // Ensure new files referred to by the manifest are in the filesystem.
223 Status s = target_->Sync();
224 if (s.ok()) {
225 state_.pos_at_last_sync_ = state_.pos_;
226 }
227 if (env_->IsFileCreatedSinceLastDirSync(state_.filename_)) {
228 Status ps = SyncParent();
229 if (s.ok() && !ps.ok()) {
230 s = ps;
231 }
232 }
233 return s;
234}
235
236Status FaultInjectionTestEnv::NewWritableFile(const std::string& fname,
237 WritableFile** result) {
238 WritableFile* actual_writable_file;
239 Status s = target()->NewWritableFile(fname, &actual_writable_file);
240 if (s.ok()) {
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800241 FileState state(fname);
242 state.pos_ = 0;
243 *result = new TestWritableFile(state, actual_writable_file, this);
244 // NewWritableFile doesn't append to files, so if the same file is
245 // opened again then it will be truncated - so forget our saved
246 // state.
Chris Mumfordb234f652014-12-11 07:59:38 -0800247 UntrackFile(fname);
248 MutexLock l(&mutex_);
249 new_files_since_last_dir_sync_.insert(fname);
250 }
251 return s;
252}
253
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800254Status FaultInjectionTestEnv::NewAppendableFile(const std::string& fname,
255 WritableFile** result) {
256 WritableFile* actual_writable_file;
257 Status s = target()->NewAppendableFile(fname, &actual_writable_file);
258 if (s.ok()) {
259 FileState state(fname);
260 state.pos_ = 0;
261 {
262 MutexLock l(&mutex_);
263 if (db_file_state_.count(fname) == 0) {
264 new_files_since_last_dir_sync_.insert(fname);
265 } else {
266 state = db_file_state_[fname];
267 }
268 }
269 *result = new TestWritableFile(state, actual_writable_file, this);
270 }
271 return s;
272}
273
Chris Mumfordb234f652014-12-11 07:59:38 -0800274Status FaultInjectionTestEnv::DropUnsyncedFileData() {
275 Status s;
276 MutexLock l(&mutex_);
277 for (std::map<std::string, FileState>::const_iterator it =
278 db_file_state_.begin();
279 s.ok() && it != db_file_state_.end(); ++it) {
280 const FileState& state = it->second;
281 if (!state.IsFullySynced()) {
282 s = state.DropUnsyncedData();
283 }
284 }
285 return s;
286}
287
288void FaultInjectionTestEnv::DirWasSynced() {
289 MutexLock l(&mutex_);
290 new_files_since_last_dir_sync_.clear();
291}
292
293bool FaultInjectionTestEnv::IsFileCreatedSinceLastDirSync(
294 const std::string& filename) {
295 MutexLock l(&mutex_);
296 return new_files_since_last_dir_sync_.find(filename) !=
297 new_files_since_last_dir_sync_.end();
298}
299
300void FaultInjectionTestEnv::UntrackFile(const std::string& f) {
301 MutexLock l(&mutex_);
302 db_file_state_.erase(f);
303 new_files_since_last_dir_sync_.erase(f);
304}
305
306Status FaultInjectionTestEnv::DeleteFile(const std::string& f) {
307 Status s = EnvWrapper::DeleteFile(f);
308 ASSERT_OK(s);
309 if (s.ok()) {
310 UntrackFile(f);
311 }
312 return s;
313}
314
315Status FaultInjectionTestEnv::RenameFile(const std::string& s,
316 const std::string& t) {
317 Status ret = EnvWrapper::RenameFile(s, t);
318
319 if (ret.ok()) {
320 MutexLock l(&mutex_);
321 if (db_file_state_.find(s) != db_file_state_.end()) {
322 db_file_state_[t] = db_file_state_[s];
323 db_file_state_.erase(s);
324 }
325
326 if (new_files_since_last_dir_sync_.erase(s) != 0) {
327 assert(new_files_since_last_dir_sync_.find(t) ==
328 new_files_since_last_dir_sync_.end());
329 new_files_since_last_dir_sync_.insert(t);
330 }
331 }
332
333 return ret;
334}
335
336void FaultInjectionTestEnv::ResetState() {
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800337 // Since we are not destroying the database, the existing files
338 // should keep their recorded synced/flushed state. Therefore
339 // we do not reset db_file_state_ and new_files_since_last_dir_sync_.
Chris Mumfordb234f652014-12-11 07:59:38 -0800340 SetFilesystemActive(true);
341}
342
343Status FaultInjectionTestEnv::DeleteFilesCreatedAfterLastDirSync() {
344 // Because DeleteFile access this container make a copy to avoid deadlock
345 mutex_.Lock();
346 std::set<std::string> new_files(new_files_since_last_dir_sync_.begin(),
347 new_files_since_last_dir_sync_.end());
348 mutex_.Unlock();
349 Status s;
350 std::set<std::string>::const_iterator it;
351 for (it = new_files.begin(); s.ok() && it != new_files.end(); ++it) {
352 s = DeleteFile(*it);
353 }
354 return s;
355}
356
357void FaultInjectionTestEnv::WritableFileClosed(const FileState& state) {
358 MutexLock l(&mutex_);
359 db_file_state_[state.filename_] = state;
360}
361
362Status FileState::DropUnsyncedData() const {
costan89af27b2018-09-04 09:44:56 -0700363 int64_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_;
Chris Mumfordb234f652014-12-11 07:59:38 -0800364 return Truncate(filename_, sync_pos);
365}
366
367class FaultInjectionTest {
368 public:
369 enum ExpectedVerifResult { VAL_EXPECT_NO_ERROR, VAL_EXPECT_ERROR };
370 enum ResetMethod { RESET_DROP_UNSYNCED_DATA, RESET_DELETE_UNSYNCED_FILES };
371
372 FaultInjectionTestEnv* env_;
373 std::string dbname_;
374 Cache* tiny_cache_;
375 Options options_;
376 DB* db_;
377
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800378 FaultInjectionTest()
379 : env_(new FaultInjectionTestEnv),
380 tiny_cache_(NewLRUCache(100)),
costan09217fd2018-04-10 16:18:06 -0700381 db_(nullptr) {
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800382 dbname_ = test::TmpDir() + "/fault_test";
383 DestroyDB(dbname_, Options()); // Destroy any db from earlier run
384 options_.reuse_logs = true;
Chris Mumfordb234f652014-12-11 07:59:38 -0800385 options_.env = env_;
386 options_.paranoid_checks = true;
Chris Mumfordb234f652014-12-11 07:59:38 -0800387 options_.block_cache = tiny_cache_;
Chris Mumfordb234f652014-12-11 07:59:38 -0800388 options_.create_if_missing = true;
Chris Mumfordb234f652014-12-11 07:59:38 -0800389 }
390
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800391 ~FaultInjectionTest() {
Chris Mumfordb234f652014-12-11 07:59:38 -0800392 CloseDB();
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800393 DestroyDB(dbname_, Options());
Chris Mumfordb234f652014-12-11 07:59:38 -0800394 delete tiny_cache_;
Chris Mumfordb234f652014-12-11 07:59:38 -0800395 delete env_;
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800396 }
Chris Mumfordb234f652014-12-11 07:59:38 -0800397
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800398 void ReuseLogs(bool reuse) {
399 options_.reuse_logs = reuse;
Chris Mumfordb234f652014-12-11 07:59:38 -0800400 }
401
402 void Build(int start_idx, int num_vals) {
403 std::string key_space, value_space;
404 WriteBatch batch;
405 for (int i = start_idx; i < start_idx + num_vals; i++) {
406 Slice key = Key(i, &key_space);
407 batch.Clear();
408 batch.Put(key, Value(i, &value_space));
409 WriteOptions options;
410 ASSERT_OK(db_->Write(options, &batch));
411 }
412 }
413
414 Status ReadValue(int i, std::string* val) const {
415 std::string key_space, value_space;
416 Slice key = Key(i, &key_space);
417 Value(i, &value_space);
418 ReadOptions options;
419 return db_->Get(options, key, val);
420 }
421
422 Status Verify(int start_idx, int num_vals,
423 ExpectedVerifResult expected) const {
424 std::string val;
425 std::string value_space;
426 Status s;
427 for (int i = start_idx; i < start_idx + num_vals && s.ok(); i++) {
428 Value(i, &value_space);
429 s = ReadValue(i, &val);
430 if (expected == VAL_EXPECT_NO_ERROR) {
431 if (s.ok()) {
432 ASSERT_EQ(value_space, val);
433 }
434 } else if (s.ok()) {
435 fprintf(stderr, "Expected an error at %d, but was OK\n", i);
436 s = Status::IOError(dbname_, "Expected value error:");
437 } else {
438 s = Status::OK(); // An expected error
439 }
440 }
441 return s;
442 }
443
444 // Return the ith key
445 Slice Key(int i, std::string* storage) const {
446 char buf[100];
447 snprintf(buf, sizeof(buf), "%016d", i);
448 storage->assign(buf, strlen(buf));
449 return Slice(*storage);
450 }
451
452 // Return the value to associate with the specified key
453 Slice Value(int k, std::string* storage) const {
454 Random r(k);
455 return test::RandomString(&r, kValueSize, storage);
456 }
457
458 Status OpenDB() {
459 delete db_;
costan09217fd2018-04-10 16:18:06 -0700460 db_ = nullptr;
Chris Mumfordb234f652014-12-11 07:59:38 -0800461 env_->ResetState();
462 return DB::Open(options_, dbname_, &db_);
463 }
464
465 void CloseDB() {
466 delete db_;
costan09217fd2018-04-10 16:18:06 -0700467 db_ = nullptr;
Chris Mumfordb234f652014-12-11 07:59:38 -0800468 }
469
470 void DeleteAllData() {
471 Iterator* iter = db_->NewIterator(ReadOptions());
Chris Mumfordb234f652014-12-11 07:59:38 -0800472 for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
473 ASSERT_OK(db_->Delete(WriteOptions(), iter->key()));
474 }
475
476 delete iter;
477 }
478
479 void ResetDBState(ResetMethod reset_method) {
480 switch (reset_method) {
481 case RESET_DROP_UNSYNCED_DATA:
482 ASSERT_OK(env_->DropUnsyncedFileData());
483 break;
484 case RESET_DELETE_UNSYNCED_FILES:
485 ASSERT_OK(env_->DeleteFilesCreatedAfterLastDirSync());
486 break;
487 default:
488 assert(false);
489 }
490 }
491
492 void PartialCompactTestPreFault(int num_pre_sync, int num_post_sync) {
493 DeleteAllData();
494 Build(0, num_pre_sync);
costan09217fd2018-04-10 16:18:06 -0700495 db_->CompactRange(nullptr, nullptr);
Chris Mumfordb234f652014-12-11 07:59:38 -0800496 Build(num_pre_sync, num_post_sync);
497 }
498
499 void PartialCompactTestReopenWithFault(ResetMethod reset_method,
500 int num_pre_sync,
501 int num_post_sync) {
502 env_->SetFilesystemActive(false);
503 CloseDB();
504 ResetDBState(reset_method);
505 ASSERT_OK(OpenDB());
506 ASSERT_OK(Verify(0, num_pre_sync, FaultInjectionTest::VAL_EXPECT_NO_ERROR));
507 ASSERT_OK(Verify(num_pre_sync, num_post_sync, FaultInjectionTest::VAL_EXPECT_ERROR));
508 }
509
510 void NoWriteTestPreFault() {
511 }
512
513 void NoWriteTestReopenWithFault(ResetMethod reset_method) {
514 CloseDB();
515 ResetDBState(reset_method);
516 ASSERT_OK(OpenDB());
517 }
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800518
519 void DoTest() {
520 Random rnd(0);
521 ASSERT_OK(OpenDB());
522 for (size_t idx = 0; idx < kNumIterations; idx++) {
523 int num_pre_sync = rnd.Uniform(kMaxNumValues);
524 int num_post_sync = rnd.Uniform(kMaxNumValues);
525
526 PartialCompactTestPreFault(num_pre_sync, num_post_sync);
527 PartialCompactTestReopenWithFault(RESET_DROP_UNSYNCED_DATA,
528 num_pre_sync,
529 num_post_sync);
530
531 NoWriteTestPreFault();
532 NoWriteTestReopenWithFault(RESET_DROP_UNSYNCED_DATA);
533
534 PartialCompactTestPreFault(num_pre_sync, num_post_sync);
535 // No new files created so we expect all values since no files will be
536 // dropped.
537 PartialCompactTestReopenWithFault(RESET_DELETE_UNSYNCED_FILES,
538 num_pre_sync + num_post_sync,
539 0);
540
541 NoWriteTestPreFault();
542 NoWriteTestReopenWithFault(RESET_DELETE_UNSYNCED_FILES);
543 }
544 }
Chris Mumfordb234f652014-12-11 07:59:38 -0800545};
546
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800547TEST(FaultInjectionTest, FaultTestNoLogReuse) {
548 ReuseLogs(false);
549 DoTest();
550}
Chris Mumfordb234f652014-12-11 07:59:38 -0800551
Sanjay Ghemawatac1d69d2014-12-11 08:13:18 -0800552TEST(FaultInjectionTest, FaultTestWithLogReuse) {
553 ReuseLogs(true);
554 DoTest();
Chris Mumfordb234f652014-12-11 07:59:38 -0800555}
556
557} // namespace leveldb
558
559int main(int argc, char** argv) {
560 return leveldb::test::RunAllTests();
561}