Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(25)

Side by Side Diff: extensions/browser/value_store/leveldb_value_store.cc

Issue 1428843002: LeveldbValueStore: Deleting db when open fails due to corruption. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: EnsureDbIsOpen calling Restore when corrupted, also Recover -> Restore Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "extensions/browser/value_store/leveldb_value_store.h" 5 #include "extensions/browser/value_store/leveldb_value_store.h"
6 6
7 #include "base/files/file_util.h" 7 #include "base/files/file_util.h"
8 #include "base/json/json_reader.h" 8 #include "base/json/json_reader.h"
9 #include "base/json/json_writer.h" 9 #include "base/json/json_writer.h"
10 #include "base/logging.h" 10 #include "base/logging.h"
(...skipping 11 matching lines...) Expand all
22 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" 22 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
23 23
24 namespace util = value_store_util; 24 namespace util = value_store_util;
25 using content::BrowserThread; 25 using content::BrowserThread;
26 26
27 namespace { 27 namespace {
28 28
29 const char kInvalidJson[] = "Invalid JSON"; 29 const char kInvalidJson[] = "Invalid JSON";
30 const char kCannotSerialize[] = "Cannot serialize value to JSON"; 30 const char kCannotSerialize[] = "Cannot serialize value to JSON";
31 31
32 // UMA values used when recovering from a corrupted leveldb.
33 // Do not change/delete these values as you will break reporting for older
34 // copies of Chrome. Only add new values to the end.
35 enum LevelDBCorruptionRecoveryValue {
36 LEVELDB_RESTORE_DELETE_SUCCESS = 0,
37 LEVELDB_RESTORE_DELETE_FAILURE,
38 LEVELDB_RESTORE_REPAIR_SUCCESS,
39 LEVELDB_RESTORE_MAX
40 };
41
32 // Scoped leveldb snapshot which releases the snapshot on destruction. 42 // Scoped leveldb snapshot which releases the snapshot on destruction.
33 class ScopedSnapshot { 43 class ScopedSnapshot {
34 public: 44 public:
35 explicit ScopedSnapshot(leveldb::DB* db) 45 explicit ScopedSnapshot(leveldb::DB* db)
36 : db_(db), snapshot_(db->GetSnapshot()) {} 46 : db_(db), snapshot_(db->GetSnapshot()) {}
37 47
38 ~ScopedSnapshot() { 48 ~ScopedSnapshot() {
39 db_->ReleaseSnapshot(snapshot_); 49 db_->ReleaseSnapshot(snapshot_);
40 } 50 }
41 51
42 const leveldb::Snapshot* get() { 52 const leveldb::Snapshot* get() {
43 return snapshot_; 53 return snapshot_;
44 } 54 }
45 55
46 private: 56 private:
47 leveldb::DB* db_; 57 leveldb::DB* db_;
48 const leveldb::Snapshot* snapshot_; 58 const leveldb::Snapshot* snapshot_;
49 59
50 DISALLOW_COPY_AND_ASSIGN(ScopedSnapshot); 60 DISALLOW_COPY_AND_ASSIGN(ScopedSnapshot);
51 }; 61 };
52 62
53 } // namespace 63 } // namespace
54 64
55 LeveldbValueStore::LeveldbValueStore(const std::string& uma_client_name, 65 LeveldbValueStore::LeveldbValueStore(const std::string& uma_client_name,
56 const base::FilePath& db_path) 66 const base::FilePath& db_path)
57 : db_path_(db_path), open_histogram_(nullptr) { 67 : db_path_(db_path), open_histogram_(nullptr), restore_histogram_(nullptr) {
58 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 68 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
59 69
60 // Used in lieu of UMA_HISTOGRAM_ENUMERATION because the histogram name is 70 // Used in lieu of UMA_HISTOGRAM_ENUMERATION because the histogram name is
61 // not a constant. 71 // not a constant.
62 open_histogram_ = base::LinearHistogram::FactoryGet( 72 open_histogram_ = base::LinearHistogram::FactoryGet(
63 "Extensions.Database.Open." + uma_client_name, 1, 73 "Extensions.Database.Open." + uma_client_name, 1,
64 leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1, 74 leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1,
65 base::Histogram::kUmaTargetedHistogramFlag); 75 base::Histogram::kUmaTargetedHistogramFlag);
76 restore_histogram_ = base::LinearHistogram::FactoryGet(
Devlin 2015/11/06 19:54:46 This is histogram black magic to me, so I'll trust
cmumford 2015/11/09 23:04:21 I manually hack-ed in a call to "restore_histogram
77 "Extensions.Database.Restore." + uma_client_name, 1, LEVELDB_RESTORE_MAX,
78 LEVELDB_RESTORE_MAX + 1, base::Histogram::kUmaTargetedHistogramFlag);
66 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( 79 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
67 this, "LeveldbValueStore", base::ThreadTaskRunnerHandle::Get()); 80 this, "LeveldbValueStore", base::ThreadTaskRunnerHandle::Get());
68 } 81 }
69 82
70 LeveldbValueStore::~LeveldbValueStore() { 83 LeveldbValueStore::~LeveldbValueStore() {
71 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 84 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
72 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( 85 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
73 this); 86 this);
74 87
75 // Delete the database from disk if it's empty (but only if we managed to 88 // Delete the database from disk if it's empty (but only if we managed to
(...skipping 204 matching lines...) Expand 10 before | Expand all | Expand 10 after
280 ValueStoreChange(next_key, next_value.release(), NULL)); 293 ValueStoreChange(next_key, next_value.release(), NULL));
281 } 294 }
282 295
283 DeleteDbFile(); 296 DeleteDbFile();
284 return MakeWriteResult(changes.Pass()); 297 return MakeWriteResult(changes.Pass());
285 } 298 }
286 299
287 bool LeveldbValueStore::Restore() { 300 bool LeveldbValueStore::Restore() {
288 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 301 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
289 302
290 ReadResult result = Get(); 303 // Possible to have a corrupted open database, so first close it.
291 std::string previous_key; 304 db_.reset();
292 while (result->IsCorrupted()) {
293 // If we don't have a specific corrupted key, or we've tried and failed to
294 // clear this specific key, or we fail to restore the key, then wipe the
295 // whole database.
296 if (!result->error().key.get() || *result->error().key == previous_key ||
297 !RestoreKey(*result->error().key)) {
298 DeleteDbFile();
299 result = Get();
300 break;
301 }
302 305
303 // Otherwise, re-Get() the database to check if there is still any 306 leveldb::Options options;
304 // corruption. 307 options.create_if_missing = true;
305 previous_key = *result->error().key; 308
306 result = Get(); 309 // Repair can drop an unbounded number of leveldb tables (key/value sets)
310 leveldb::Status status = leveldb::RepairDB(db_path_.AsUTF8Unsafe(), options);
311 if (status.ok()) {
312 restore_histogram_->Add(LEVELDB_RESTORE_REPAIR_SUCCESS);
313 return true;
307 } 314 }
308 315
309 // If we still have an error, it means we've tried deleting the database file, 316 if (DeleteDbFile()) {
310 // and failed. There's nothing more we can do. 317 restore_histogram_->Add(LEVELDB_RESTORE_DELETE_SUCCESS);
311 return !result->IsCorrupted(); 318 return true;
319 }
320
321 restore_histogram_->Add(LEVELDB_RESTORE_DELETE_FAILURE);
322 return false;
312 } 323 }
313 324
314 bool LeveldbValueStore::RestoreKey(const std::string& key) { 325 bool LeveldbValueStore::RestoreKey(const std::string& key) {
315 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 326 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
316 327
317 ReadResult result = Get(key); 328 ReadResult result = Get(key);
318 if (result->IsCorrupted()) { 329 if (result->IsCorrupted()) {
319 leveldb::WriteBatch batch; 330 leveldb::WriteBatch batch;
320 batch.Delete(key); 331 batch.Delete(key);
321 scoped_ptr<ValueStore::Error> error = WriteToDb(&batch); 332 scoped_ptr<ValueStore::Error> error = WriteToDb(&batch);
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
374 leveldb::Options options; 385 leveldb::Options options;
375 options.max_open_files = 0; // Use minimum. 386 options.max_open_files = 0; // Use minimum.
376 options.create_if_missing = true; 387 options.create_if_missing = true;
377 options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue; 388 options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
378 389
379 leveldb::DB* db = NULL; 390 leveldb::DB* db = NULL;
380 leveldb::Status status = 391 leveldb::Status status =
381 leveldb::DB::Open(options, db_path_.AsUTF8Unsafe(), &db); 392 leveldb::DB::Open(options, db_path_.AsUTF8Unsafe(), &db);
382 if (open_histogram_) 393 if (open_histogram_)
383 open_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(status)); 394 open_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(status));
395 if (status.IsCorruption()) {
396 // Returning a corruption error should result in Restore() being called.
397 // However, since once a leveldb becomes corrupt it's unusable without
398 // some kind of repair or delete, so do that right now.
399 Restore();
400 }
384 if (!status.ok()) 401 if (!status.ok())
385 return ToValueStoreError(status, util::NoKey()); 402 return ToValueStoreError(status, util::NoKey());
386 403
387 CHECK(db); 404 CHECK(db);
388 db_.reset(db); 405 db_.reset(db);
389 return util::NoError(); 406 return util::NoError();
390 } 407 }
391 408
392 scoped_ptr<ValueStore::Error> LeveldbValueStore::ReadFromDb( 409 scoped_ptr<ValueStore::Error> LeveldbValueStore::ReadFromDb(
393 leveldb::ReadOptions options, 410 leveldb::ReadOptions options,
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
461 478
462 it->SeekToFirst(); 479 it->SeekToFirst();
463 bool is_empty = !it->Valid(); 480 bool is_empty = !it->Valid();
464 if (!it->status().ok()) { 481 if (!it->status().ok()) {
465 LOG(ERROR) << "Checking DB emptiness failed: " << it->status().ToString(); 482 LOG(ERROR) << "Checking DB emptiness failed: " << it->status().ToString();
466 return false; 483 return false;
467 } 484 }
468 return is_empty; 485 return is_empty;
469 } 486 }
470 487
471 void LeveldbValueStore::DeleteDbFile() { 488 bool LeveldbValueStore::DeleteDbFile() {
472 db_.reset(); // release any lock on the directory 489 db_.reset(); // release any lock on the directory
473 if (!base::DeleteFile(db_path_, true /* recursive */)) { 490 if (!base::DeleteFile(db_path_, true /* recursive */)) {
474 LOG(WARNING) << "Failed to delete LeveldbValueStore database at " << 491 LOG(WARNING) << "Failed to delete LeveldbValueStore database at " <<
475 db_path_.value(); 492 db_path_.value();
493 return false;
476 } 494 }
495 return true;
477 } 496 }
478 497
479 scoped_ptr<ValueStore::Error> LeveldbValueStore::ToValueStoreError( 498 scoped_ptr<ValueStore::Error> LeveldbValueStore::ToValueStoreError(
480 const leveldb::Status& status, 499 const leveldb::Status& status,
481 scoped_ptr<std::string> key) { 500 scoped_ptr<std::string> key) {
482 CHECK(!status.ok()); 501 CHECK(!status.ok());
483 CHECK(!status.IsNotFound()); // not an error 502 CHECK(!status.IsNotFound()); // not an error
484 503
485 std::string message = status.ToString(); 504 std::string message = status.ToString();
486 // The message may contain |db_path_|, which may be considered sensitive 505 // The message may contain |db_path_|, which may be considered sensitive
487 // data, and those strings are passed to the extension, so strip it out. 506 // data, and those strings are passed to the extension, so strip it out.
488 base::ReplaceSubstringsAfterOffset( 507 base::ReplaceSubstringsAfterOffset(
489 &message, 0u, db_path_.AsUTF8Unsafe(), "..."); 508 &message, 0u, db_path_.AsUTF8Unsafe(), "...");
490 509
491 return Error::Create(CORRUPTION, message, key.Pass()); 510 return Error::Create(CORRUPTION, message, key.Pass());
492 } 511 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698