From 215d116d00a9f349bb9cae4d7f16671c8f67e5f8 Mon Sep 17 00:00:00 2001 From: cmumford Date: Tue, 15 Mar 2016 10:18:23 -0700 Subject: Extensions: Extract lazy leveldb operations into LazyLevelDb. Extracted (the bulk of) the leveldb operations from LeveldbValueStore into a new base class: LazyLevelDb. The purpose of this change is to support an upcoming shared database class that also needs to read/write from/to a leveldb, but partitioned for different key spaces. This is part 2 from crrev.com/1658833003 BUG=453946 Review URL: https://codereview.chromium.org/1788893002 Cr-Commit-Position: refs/heads/master@{#381239} --- extensions/browser/value_store/lazy_leveldb.cc | 271 +++++++++++++++++ extensions/browser/value_store/lazy_leveldb.h | 89 ++++++ .../browser/value_store/leveldb_value_store.cc | 319 ++------------------- .../browser/value_store/leveldb_value_store.h | 49 +--- 4 files changed, 384 insertions(+), 344 deletions(-) create mode 100644 extensions/browser/value_store/lazy_leveldb.cc create mode 100644 extensions/browser/value_store/lazy_leveldb.h (limited to 'extensions/browser') diff --git a/extensions/browser/value_store/lazy_leveldb.cc b/extensions/browser/value_store/lazy_leveldb.cc new file mode 100644 index 0000000..da37f08 --- /dev/null +++ b/extensions/browser/value_store/lazy_leveldb.cc @@ -0,0 +1,271 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/value_store/lazy_leveldb.h" + +#include "base/files/file_util.h" +#include "base/json/json_reader.h" +#include "base/strings/string_util.h" +#include "content/public/browser/browser_thread.h" +#include "third_party/leveldatabase/env_chromium.h" +#include "third_party/leveldatabase/src/include/leveldb/iterator.h" +#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" + +using base::StringPiece; +using content::BrowserThread; + +namespace { + +const char kInvalidJson[] = "Invalid JSON"; +const char kRestoredDuringOpen[] = "Database corruption repaired during open"; + +// UMA values used when recovering from a corrupted leveldb. +// Do not change/delete these values as you will break reporting for older +// copies of Chrome. Only add new values to the end. +enum LevelDBDatabaseCorruptionRecoveryValue { + LEVELDB_DB_RESTORE_DELETE_SUCCESS = 0, + LEVELDB_DB_RESTORE_DELETE_FAILURE, + LEVELDB_DB_RESTORE_REPAIR_SUCCESS, + LEVELDB_DB_RESTORE_MAX +}; + +// UMA values used when recovering from a corrupted leveldb. +// Do not change/delete these values as you will break reporting for older +// copies of Chrome. Only add new values to the end. +enum LevelDBValueCorruptionRecoveryValue { + LEVELDB_VALUE_RESTORE_DELETE_SUCCESS, + LEVELDB_VALUE_RESTORE_DELETE_FAILURE, + LEVELDB_VALUE_RESTORE_MAX +}; + +ValueStore::StatusCode LevelDbToValueStoreStatusCode( + const leveldb::Status& status) { + if (status.ok()) + return ValueStore::OK; + if (status.IsCorruption()) + return ValueStore::CORRUPTION; + return ValueStore::OTHER_ERROR; +} + +} // namespace + +LazyLevelDb::LazyLevelDb(const std::string& uma_client_name, + const base::FilePath& path) + : db_path_(path) { + open_options_.create_if_missing = true; + open_options_.paranoid_checks = true; + open_options_.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue; + + read_options_.verify_checksums = true; + + // Used in lieu of UMA_HISTOGRAM_ENUMERATION because the histogram name is + // not a constant. + open_histogram_ = base::LinearHistogram::FactoryGet( + "Extensions.Database.Open." + uma_client_name, 1, + leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1, + base::Histogram::kUmaTargetedHistogramFlag); + db_restore_histogram_ = base::LinearHistogram::FactoryGet( + "Extensions.Database.Database.Restore." + uma_client_name, 1, + LEVELDB_DB_RESTORE_MAX, LEVELDB_DB_RESTORE_MAX + 1, + base::Histogram::kUmaTargetedHistogramFlag); + value_restore_histogram_ = base::LinearHistogram::FactoryGet( + "Extensions.Database.Value.Restore." + uma_client_name, 1, + LEVELDB_VALUE_RESTORE_MAX, LEVELDB_VALUE_RESTORE_MAX + 1, + base::Histogram::kUmaTargetedHistogramFlag); +} + +LazyLevelDb::~LazyLevelDb() { + if (db_ && !BrowserThread::CurrentlyOn(BrowserThread::FILE)) + BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, db_.release()); +} + +ValueStore::Status LazyLevelDb::Read(const std::string& key, + scoped_ptr* value) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + DCHECK(value); + + std::string value_as_json; + leveldb::Status s = db_->Get(read_options_, key, &value_as_json); + + if (s.IsNotFound()) { + // Despite there being no value, it was still a success. Check this first + // because ok() is false on IsNotFound. + return ValueStore::Status(); + } + + if (!s.ok()) + return ToValueStoreError(s); + + scoped_ptr val = base::JSONReader().ReadToValue(value_as_json); + if (!val) + return ValueStore::Status(ValueStore::CORRUPTION, FixCorruption(&key), + kInvalidJson); + + *value = std::move(val); + return ValueStore::Status(); +} + +leveldb::Status LazyLevelDb::Delete(const std::string& key) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + DCHECK(db_.get()); + + leveldb::WriteBatch batch; + batch.Delete(key); + + return db_->Write(leveldb::WriteOptions(), &batch); +} + +ValueStore::BackingStoreRestoreStatus LazyLevelDb::LogRestoreStatus( + ValueStore::BackingStoreRestoreStatus restore_status) const { + switch (restore_status) { + case ValueStore::RESTORE_NONE: + NOTREACHED(); + break; + case ValueStore::DB_RESTORE_DELETE_SUCCESS: + db_restore_histogram_->Add(LEVELDB_DB_RESTORE_DELETE_SUCCESS); + break; + case ValueStore::DB_RESTORE_DELETE_FAILURE: + db_restore_histogram_->Add(LEVELDB_DB_RESTORE_DELETE_FAILURE); + break; + case ValueStore::DB_RESTORE_REPAIR_SUCCESS: + db_restore_histogram_->Add(LEVELDB_DB_RESTORE_REPAIR_SUCCESS); + break; + case ValueStore::VALUE_RESTORE_DELETE_SUCCESS: + value_restore_histogram_->Add(LEVELDB_VALUE_RESTORE_DELETE_SUCCESS); + break; + case ValueStore::VALUE_RESTORE_DELETE_FAILURE: + value_restore_histogram_->Add(LEVELDB_VALUE_RESTORE_DELETE_FAILURE); + break; + } + return restore_status; +} + +ValueStore::BackingStoreRestoreStatus LazyLevelDb::FixCorruption( + const std::string* key) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + leveldb::Status s; + if (key && db_) { + s = Delete(*key); + // Deleting involves writing to the log, so it's possible to have a + // perfectly OK database but still have a delete fail. + if (s.ok()) + return LogRestoreStatus(ValueStore::VALUE_RESTORE_DELETE_SUCCESS); + else if (s.IsIOError()) + return LogRestoreStatus(ValueStore::VALUE_RESTORE_DELETE_FAILURE); + // Any other kind of failure triggers a db repair. + } + + // Make sure database is closed. + db_.reset(); + + // First try the less lossy repair. + ValueStore::BackingStoreRestoreStatus restore_status = + ValueStore::RESTORE_NONE; + + leveldb::Options repair_options; + repair_options.create_if_missing = true; + repair_options.paranoid_checks = true; + + // RepairDB can drop an unbounded number of leveldb tables (key/value sets). + s = leveldb::RepairDB(db_path_.AsUTF8Unsafe(), repair_options); + + leveldb::DB* db = nullptr; + if (s.ok()) { + restore_status = ValueStore::DB_RESTORE_REPAIR_SUCCESS; + s = leveldb::DB::Open(open_options_, db_path_.AsUTF8Unsafe(), &db); + } + + if (!s.ok()) { + if (DeleteDbFile()) { + restore_status = ValueStore::DB_RESTORE_DELETE_SUCCESS; + s = leveldb::DB::Open(open_options_, db_path_.AsUTF8Unsafe(), &db); + } else { + restore_status = ValueStore::DB_RESTORE_DELETE_FAILURE; + } + } + + if (s.ok()) + db_.reset(db); + else + db_unrecoverable_ = true; + + if (s.ok() && key) { + s = Delete(*key); + if (s.ok()) { + restore_status = ValueStore::VALUE_RESTORE_DELETE_SUCCESS; + } else if (s.IsIOError()) { + restore_status = ValueStore::VALUE_RESTORE_DELETE_FAILURE; + } else { + db_.reset(db); + if (!DeleteDbFile()) + db_unrecoverable_ = true; + restore_status = ValueStore::DB_RESTORE_DELETE_FAILURE; + } + } + + // Only log for the final and most extreme form of database restoration. + LogRestoreStatus(restore_status); + + return restore_status; +} + +ValueStore::Status LazyLevelDb::EnsureDbIsOpen() { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + + if (db_) + return ValueStore::Status(); + + if (db_unrecoverable_) { + return ValueStore::Status(ValueStore::CORRUPTION, + ValueStore::DB_RESTORE_DELETE_FAILURE, + "Database corrupted"); + } + + leveldb::DB* db = nullptr; + leveldb::Status ldb_status = + leveldb::DB::Open(open_options_, db_path_.AsUTF8Unsafe(), &db); + open_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(ldb_status)); + ValueStore::Status status = ToValueStoreError(ldb_status); + if (ldb_status.ok()) { + db_.reset(db); + } else if (ldb_status.IsCorruption()) { + status.restore_status = FixCorruption(nullptr); + if (status.restore_status != ValueStore::DB_RESTORE_DELETE_FAILURE) { + status.code = ValueStore::OK; + status.message = kRestoredDuringOpen; + } + } + + return status; +} + +ValueStore::Status LazyLevelDb::ToValueStoreError( + const leveldb::Status& status) { + CHECK(!status.IsNotFound()); // not an error + + std::string message = status.ToString(); + // The message may contain |db_path_|, which may be considered sensitive + // data, and those strings are passed to the extension, so strip it out. + base::ReplaceSubstringsAfterOffset(&message, 0u, db_path_.AsUTF8Unsafe(), + "..."); + + return ValueStore::Status(LevelDbToValueStoreStatusCode(status), message); +} + +bool LazyLevelDb::DeleteDbFile() { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + db_.reset(); // release any lock on the directory + if (!base::DeleteFile(db_path_, true /* recursive */)) { + LOG(WARNING) << "Failed to delete leveldb database at " << db_path_.value(); + return false; + } + return true; +} + +scoped_ptr LazyLevelDb::CreateIterator( + const leveldb::ReadOptions& read_options) { + if (!EnsureDbIsOpen().ok()) + return nullptr; + return make_scoped_ptr(db_->NewIterator(read_options)); +} diff --git a/extensions/browser/value_store/lazy_leveldb.h b/extensions/browser/value_store/lazy_leveldb.h new file mode 100644 index 0000000..f435faf --- /dev/null +++ b/extensions/browser/value_store/lazy_leveldb.h @@ -0,0 +1,89 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_VALUE_STORE_LAZY_LEVELDB_H_ +#define EXTENSIONS_BROWSER_VALUE_STORE_LAZY_LEVELDB_H_ + +#include + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_base.h" +#include "extensions/browser/value_store/value_store.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" + +namespace leveldb { +class Iterator; +} // namespace leveldb + +class LazyLevelDb { + public: + // Create a new database iterator. This iterator *must* be deleted before + // this database is closed. + scoped_ptr CreateIterator( + const leveldb::ReadOptions& read_options); + + // Convert a leveldb::Status to a ValueStore::Status. Will also sanitize path + // to eliminate user data path. + ValueStore::Status ToValueStoreError(const leveldb::Status& status); + + protected: + LazyLevelDb(const std::string& uma_client_name, const base::FilePath& path); + ~LazyLevelDb(); + + // Close, if necessary, and delete the database directory. + bool DeleteDbFile(); + + // Fix the |key| or database. If |key| is not null and the database is open + // then the key will be deleted. Otherwise the database will be repaired, and + // failing that will be deleted. + ValueStore::BackingStoreRestoreStatus FixCorruption(const std::string* key); + + // Delete a value (identified by |key|) from the database. + leveldb::Status Delete(const std::string& key); + + // Read a value from the database. + ValueStore::Status Read(const std::string& key, + scoped_ptr* value); + + // Ensure that the database is open. + ValueStore::Status EnsureDbIsOpen(); + + const std::string& open_histogram_name() const { + return open_histogram_->histogram_name(); + } + + leveldb::DB* db() { return db_.get(); } + + const leveldb::ReadOptions& read_options() const { return read_options_; } + + const leveldb::WriteOptions& write_options() const { return write_options_; } + + private: + ValueStore::BackingStoreRestoreStatus LogRestoreStatus( + ValueStore::BackingStoreRestoreStatus restore_status) const; + + // The leveldb to which this class reads/writes. + scoped_ptr db_; + // The path to the underlying leveldb. + const base::FilePath db_path_; + // The options to be used when this database is lazily opened. + leveldb::Options open_options_; + // The options to be used for all database read operations. + leveldb::ReadOptions read_options_; + // The options to be used for all database write operations. + leveldb::WriteOptions write_options_; + // Set when this database has tried to repair (and failed) to prevent + // unbounded attempts to open a bad/unrecoverable database. + bool db_unrecoverable_ = false; + // Used for UMA logging. + base::HistogramBase* open_histogram_ = nullptr; + base::HistogramBase* db_restore_histogram_ = nullptr; + base::HistogramBase* value_restore_histogram_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(LazyLevelDb); +}; + +#endif // EXTENSIONS_BROWSER_VALUE_STORE_LAZY_LEVELDB_H_ diff --git a/extensions/browser/value_store/leveldb_value_store.cc b/extensions/browser/value_store/leveldb_value_store.cc index e2529ce..c5a989a 100644 --- a/extensions/browser/value_store/leveldb_value_store.cc +++ b/extensions/browser/value_store/leveldb_value_store.cc @@ -31,89 +31,12 @@ namespace { const char kInvalidJson[] = "Invalid JSON"; const char kCannotSerialize[] = "Cannot serialize value to JSON"; -const char kRestoredDuringOpen[] = "Database corruption repaired during open"; - -// UMA values used when recovering from a corrupted leveldb. -// Do not change/delete these values as you will break reporting for older -// copies of Chrome. Only add new values to the end. -enum LevelDBDatabaseCorruptionRecoveryValue { - LEVELDB_DB_RESTORE_DELETE_SUCCESS = 0, - LEVELDB_DB_RESTORE_DELETE_FAILURE, - LEVELDB_DB_RESTORE_REPAIR_SUCCESS, - LEVELDB_DB_RESTORE_MAX -}; - -// UMA values used when recovering from a corrupted leveldb. -// Do not change/delete these values as you will break reporting for older -// copies of Chrome. Only add new values to the end. -enum LevelDBValueCorruptionRecoveryValue { - LEVELDB_VALUE_RESTORE_DELETE_SUCCESS, - LEVELDB_VALUE_RESTORE_DELETE_FAILURE, - LEVELDB_VALUE_RESTORE_MAX -}; - -// Scoped leveldb snapshot which releases the snapshot on destruction. -class ScopedSnapshot { - public: - explicit ScopedSnapshot(leveldb::DB* db) - : db_(db), snapshot_(db->GetSnapshot()) {} - - ~ScopedSnapshot() { - db_->ReleaseSnapshot(snapshot_); - } - - const leveldb::Snapshot* get() { - return snapshot_; - } - - private: - leveldb::DB* db_; - const leveldb::Snapshot* snapshot_; - - DISALLOW_COPY_AND_ASSIGN(ScopedSnapshot); -}; - -ValueStore::StatusCode LevelDbToValueStoreStatus( - const leveldb::Status& status) { - if (status.ok()) - return ValueStore::OK; - if (status.IsCorruption()) - return ValueStore::CORRUPTION; - return ValueStore::OTHER_ERROR; -} } // namespace LeveldbValueStore::LeveldbValueStore(const std::string& uma_client_name, const base::FilePath& db_path) - : db_path_(db_path), - db_unrecoverable_(false), - open_histogram_(nullptr), - db_restore_histogram_(nullptr), - value_restore_histogram_(nullptr) { - DCHECK_CURRENTLY_ON(BrowserThread::FILE); - - open_options_.max_open_files = 0; // Use minimum. - open_options_.create_if_missing = true; - open_options_.paranoid_checks = true; - open_options_.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue; - - read_options_.verify_checksums = true; - - // Used in lieu of UMA_HISTOGRAM_ENUMERATION because the histogram name is - // not a constant. - open_histogram_ = base::LinearHistogram::FactoryGet( - "Extensions.Database.Open." + uma_client_name, 1, - leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1, - base::Histogram::kUmaTargetedHistogramFlag); - db_restore_histogram_ = base::LinearHistogram::FactoryGet( - "Extensions.Database.Database.Restore." + uma_client_name, 1, - LEVELDB_DB_RESTORE_MAX, LEVELDB_DB_RESTORE_MAX + 1, - base::Histogram::kUmaTargetedHistogramFlag); - value_restore_histogram_ = base::LinearHistogram::FactoryGet( - "Extensions.Database.Value.Restore." + uma_client_name, 1, - LEVELDB_VALUE_RESTORE_MAX, LEVELDB_VALUE_RESTORE_MAX + 1, - base::Histogram::kUmaTargetedHistogramFlag); + : LazyLevelDb(uma_client_name, db_path) { base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( this, "LeveldbValueStore", base::ThreadTaskRunnerHandle::Get()); } @@ -122,12 +45,6 @@ LeveldbValueStore::~LeveldbValueStore() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( this); - - // Delete the database from disk if it's empty (but only if we managed to - // open it!). This is safe on destruction, assuming that we have exclusive - // access to the database. - if (db_ && IsEmpty()) - DeleteDbFile(); } size_t LeveldbValueStore::GetBytesInUse(const std::string& key) { @@ -157,7 +74,7 @@ ValueStore::ReadResult LeveldbValueStore::Get(const std::string& key) { return MakeReadResult(status); scoped_ptr setting; - status.Merge(ReadFromDb(key, &setting)); + status.Merge(Read(key, &setting)); if (!status.ok()) return MakeReadResult(status); @@ -179,7 +96,7 @@ ValueStore::ReadResult LeveldbValueStore::Get( for (const std::string& key : keys) { scoped_ptr setting; - status.Merge(ReadFromDb(key, &setting)); + status.Merge(Read(key, &setting)); if (!status.ok()) return MakeReadResult(status); if (setting) @@ -197,14 +114,9 @@ ValueStore::ReadResult LeveldbValueStore::Get() { return MakeReadResult(status); base::JSONReader json_reader; - leveldb::ReadOptions options = leveldb::ReadOptions(); - // All interaction with the db is done on the same thread, so snapshotting - // isn't strictly necessary. This is just defensive. scoped_ptr settings(new base::DictionaryValue()); - ScopedSnapshot snapshot(db_.get()); - options.snapshot = snapshot.get(); - scoped_ptr it(db_->NewIterator(options)); + scoped_ptr it(db()->NewIterator(read_options())); for (it->SeekToFirst(); it->Valid(); it->Next()) { std::string key = it->key().ToString(); scoped_ptr value = @@ -231,8 +143,9 @@ ValueStore::ReadResult LeveldbValueStore::Get() { return MakeReadResult(std::move(settings), status); } -ValueStore::WriteResult LeveldbValueStore::Set( - WriteOptions options, const std::string& key, const base::Value& value) { +ValueStore::WriteResult LeveldbValueStore::Set(WriteOptions options, + const std::string& key, + const base::Value& value) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); Status status = EnsureDbIsOpen(); @@ -251,7 +164,8 @@ ValueStore::WriteResult LeveldbValueStore::Set( } ValueStore::WriteResult LeveldbValueStore::Set( - WriteOptions options, const base::DictionaryValue& settings) { + WriteOptions options, + const base::DictionaryValue& settings) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); Status status = EnsureDbIsOpen(); @@ -292,7 +206,7 @@ ValueStore::WriteResult LeveldbValueStore::Remove( for (const std::string& key : keys) { scoped_ptr old_value; - status.Merge(ReadFromDb(key, &old_value)); + status.Merge(Read(key, &old_value)); if (!status.ok()) return MakeWriteResult(status); @@ -302,7 +216,7 @@ ValueStore::WriteResult LeveldbValueStore::Remove( } } - leveldb::Status ldb_status = db_->Write(leveldb::WriteOptions(), &batch); + leveldb::Status ldb_status = db()->Write(leveldb::WriteOptions(), &batch); if (!ldb_status.ok() && !ldb_status.IsNotFound()) { status.Merge(ToValueStoreError(ldb_status)); return MakeWriteResult(status); @@ -332,6 +246,9 @@ ValueStore::WriteResult LeveldbValueStore::Clear() { } bool LeveldbValueStore::WriteToDbForTest(leveldb::WriteBatch* batch) { + Status status = EnsureDbIsOpen(); + if (!status.ok()) + return false; return WriteToDb(batch).ok(); } @@ -341,19 +258,18 @@ bool LeveldbValueStore::OnMemoryDump( DCHECK_CURRENTLY_ON(BrowserThread::FILE); // Return true so that the provider is not disabled. - if (!db_) + if (!db()) return true; std::string value; uint64_t size; - bool res = db_->GetProperty("leveldb.approximate-memory-usage", &value); + bool res = db()->GetProperty("leveldb.approximate-memory-usage", &value); DCHECK(res); res = base::StringToUint64(value, &size); DCHECK(res); - auto dump = pmd->CreateAllocatorDump( - base::StringPrintf("leveldb/value_store/%s/%p", - open_histogram_->histogram_name().c_str(), this)); + auto dump = pmd->CreateAllocatorDump(base::StringPrintf( + "leveldb/value_store/%s/%p", open_histogram_name().c_str(), this)); dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, base::trace_event::MemoryAllocatorDump::kUnitsBytes, size); @@ -367,154 +283,6 @@ bool LeveldbValueStore::OnMemoryDump( return true; } -ValueStore::BackingStoreRestoreStatus LeveldbValueStore::LogRestoreStatus( - BackingStoreRestoreStatus restore_status) { - switch (restore_status) { - case RESTORE_NONE: - NOTREACHED(); - break; - case DB_RESTORE_DELETE_SUCCESS: - db_restore_histogram_->Add(LEVELDB_DB_RESTORE_DELETE_SUCCESS); - break; - case DB_RESTORE_DELETE_FAILURE: - db_restore_histogram_->Add(LEVELDB_DB_RESTORE_DELETE_FAILURE); - break; - case DB_RESTORE_REPAIR_SUCCESS: - db_restore_histogram_->Add(LEVELDB_DB_RESTORE_REPAIR_SUCCESS); - break; - case VALUE_RESTORE_DELETE_SUCCESS: - value_restore_histogram_->Add(LEVELDB_VALUE_RESTORE_DELETE_SUCCESS); - break; - case VALUE_RESTORE_DELETE_FAILURE: - value_restore_histogram_->Add(LEVELDB_VALUE_RESTORE_DELETE_FAILURE); - break; - } - return restore_status; -} - -ValueStore::BackingStoreRestoreStatus LeveldbValueStore::FixCorruption( - const std::string* key) { - leveldb::Status s; - if (key && db_) { - s = Delete(*key); - // Deleting involves writing to the log, so it's possible to have a - // perfectly OK database but still have a delete fail. - if (s.ok()) - return LogRestoreStatus(VALUE_RESTORE_DELETE_SUCCESS); - else if (s.IsIOError()) - return LogRestoreStatus(VALUE_RESTORE_DELETE_FAILURE); - // Any other kind of failure triggers a db repair. - } - - // Make sure database is closed. - db_.reset(); - - // First try the less lossy repair. - BackingStoreRestoreStatus restore_status = RESTORE_NONE; - - leveldb::Options repair_options; - repair_options.create_if_missing = true; - repair_options.paranoid_checks = true; - - // RepairDB can drop an unbounded number of leveldb tables (key/value sets). - s = leveldb::RepairDB(db_path_.AsUTF8Unsafe(), repair_options); - - leveldb::DB* db = nullptr; - if (s.ok()) { - restore_status = DB_RESTORE_REPAIR_SUCCESS; - s = leveldb::DB::Open(open_options_, db_path_.AsUTF8Unsafe(), &db); - } - - if (!s.ok()) { - if (DeleteDbFile()) { - restore_status = DB_RESTORE_DELETE_SUCCESS; - s = leveldb::DB::Open(open_options_, db_path_.AsUTF8Unsafe(), &db); - } else { - restore_status = DB_RESTORE_DELETE_FAILURE; - } - } - - if (s.ok()) - db_.reset(db); - else - db_unrecoverable_ = true; - - if (s.ok() && key) { - s = Delete(*key); - if (s.ok()) { - restore_status = VALUE_RESTORE_DELETE_SUCCESS; - } else if (s.IsIOError()) { - restore_status = VALUE_RESTORE_DELETE_FAILURE; - } else { - db_.reset(db); - if (!DeleteDbFile()) - db_unrecoverable_ = true; - restore_status = DB_RESTORE_DELETE_FAILURE; - } - } - - // Only log for the final and most extreme form of database restoration. - LogRestoreStatus(restore_status); - - return restore_status; -} - -ValueStore::Status LeveldbValueStore::EnsureDbIsOpen() { - DCHECK_CURRENTLY_ON(BrowserThread::FILE); - - if (db_) - return Status(); - - if (db_unrecoverable_) { - return ValueStore::Status(ValueStore::CORRUPTION, - ValueStore::DB_RESTORE_DELETE_FAILURE, - "Database corrupted"); - } - - leveldb::DB* db = NULL; - leveldb::Status ldb_status = - leveldb::DB::Open(open_options_, db_path_.AsUTF8Unsafe(), &db); - open_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(ldb_status)); - Status status = ToValueStoreError(ldb_status); - if (ldb_status.ok()) { - db_.reset(db); - } else if (ldb_status.IsCorruption()) { - status.restore_status = FixCorruption(nullptr); - if (status.restore_status != DB_RESTORE_DELETE_FAILURE) { - status.code = OK; - status.message = kRestoredDuringOpen; - } - } - - return status; -} - -ValueStore::Status LeveldbValueStore::ReadFromDb( - const std::string& key, - scoped_ptr* setting) { - DCHECK_CURRENTLY_ON(BrowserThread::FILE); - DCHECK(setting); - - std::string value_as_json; - leveldb::Status s = db_->Get(read_options_, key, &value_as_json); - - if (s.IsNotFound()) { - // Despite there being no value, it was still a success. Check this first - // because ok() is false on IsNotFound. - return Status(); - } - - if (!s.ok()) - return ToValueStoreError(s); - - scoped_ptr value = base::JSONReader().ReadToValue(value_as_json); - if (!value) - return Status(CORRUPTION, FixCorruption(&key), kInvalidJson); - - *setting = std::move(value); - return Status(); -} - ValueStore::Status LeveldbValueStore::AddToBatch( ValueStore::WriteOptions options, const std::string& key, @@ -525,7 +293,7 @@ ValueStore::Status LeveldbValueStore::AddToBatch( if (!(options & NO_GENERATE_CHANGES)) { scoped_ptr old_value; - Status status = ReadFromDb(key, &old_value); + Status status = Read(key, &old_value); if (!status.ok()) return status; if (!old_value || !old_value->Equals(&value)) { @@ -547,52 +315,5 @@ ValueStore::Status LeveldbValueStore::AddToBatch( } ValueStore::Status LeveldbValueStore::WriteToDb(leveldb::WriteBatch* batch) { - leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch); - return ToValueStoreError(status); -} - -bool LeveldbValueStore::IsEmpty() { - DCHECK_CURRENTLY_ON(BrowserThread::FILE); - scoped_ptr it(db_->NewIterator(leveldb::ReadOptions())); - - it->SeekToFirst(); - bool is_empty = !it->Valid(); - if (!it->status().ok()) { - LOG(ERROR) << "Checking DB emptiness failed: " << it->status().ToString(); - return false; - } - return is_empty; -} - -leveldb::Status LeveldbValueStore::Delete(const std::string& key) { - DCHECK_CURRENTLY_ON(BrowserThread::FILE); - DCHECK(db_.get()); - - leveldb::WriteBatch batch; - batch.Delete(key); - - return db_->Write(leveldb::WriteOptions(), &batch); -} - -bool LeveldbValueStore::DeleteDbFile() { - db_.reset(); // release any lock on the directory - if (!base::DeleteFile(db_path_, true /* recursive */)) { - LOG(WARNING) << "Failed to delete LeveldbValueStore database at " << - db_path_.value(); - return false; - } - return true; -} - -ValueStore::Status LeveldbValueStore::ToValueStoreError( - const leveldb::Status& status) { - CHECK(!status.IsNotFound()); // not an error - - std::string message = status.ToString(); - // The message may contain |db_path_|, which may be considered sensitive - // data, and those strings are passed to the extension, so strip it out. - base::ReplaceSubstringsAfterOffset( - &message, 0u, db_path_.AsUTF8Unsafe(), "..."); - - return Status(LevelDbToValueStoreStatus(status), message); + return ToValueStoreError(db()->Write(write_options(), batch)); } diff --git a/extensions/browser/value_store/leveldb_value_store.h b/extensions/browser/value_store/leveldb_value_store.h index 058e282..41e91a3 100644 --- a/extensions/browser/value_store/leveldb_value_store.h +++ b/extensions/browser/value_store/leveldb_value_store.h @@ -15,6 +15,7 @@ #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/trace_event/memory_dump_provider.h" +#include "extensions/browser/value_store/lazy_leveldb.h" #include "extensions/browser/value_store/value_store.h" #include "third_party/leveldatabase/src/include/leveldb/db.h" @@ -25,6 +26,7 @@ class HistogramBase; // Value store area, backed by a leveldb database. // All methods must be run on the FILE thread. class LeveldbValueStore : public ValueStore, + public LazyLevelDb, public base::trace_event::MemoryDumpProvider { public: // Creates a database bound to |path|. The underlying database won't be @@ -46,10 +48,10 @@ class LeveldbValueStore : public ValueStore, ReadResult Get(const std::string& key) override; ReadResult Get(const std::vector& keys) override; ReadResult Get() override; - WriteResult Set(WriteOptions options, + WriteResult Set(ValueStore::WriteOptions options, const std::string& key, const base::Value& value) override; - WriteResult Set(WriteOptions options, + WriteResult Set(ValueStore::WriteOptions options, const base::DictionaryValue& values) override; WriteResult Remove(const std::string& key) override; WriteResult Remove(const std::vector& keys) override; @@ -64,23 +66,6 @@ class LeveldbValueStore : public ValueStore, base::trace_event::ProcessMemoryDump* pmd) override; private: - // Fix the |key| or database. If |key| is not null and the database is open - // then the key will be deleted. Otherwise the database will be repaired, and - // failing that will be deleted. - BackingStoreRestoreStatus FixCorruption(const std::string* key); - - // Log, to UMA, the status of an attempt to restore a database. - BackingStoreRestoreStatus LogRestoreStatus( - BackingStoreRestoreStatus restore_status); - - // Tries to open the database if it hasn't been opened already. - ValueStore::Status EnsureDbIsOpen(); - - // Reads a setting from the database. - ValueStore::Status ReadFromDb(const std::string& key, - // Will be reset() with the result, if any. - scoped_ptr* setting); - // Adds a setting to a WriteBatch, and logs the change in |changes|. For use // with WriteToDb. ValueStore::Status AddToBatch(ValueStore::WriteOptions options, @@ -92,32 +77,6 @@ class LeveldbValueStore : public ValueStore, // Commits the changes in |batch| to the database. ValueStore::Status WriteToDb(leveldb::WriteBatch* batch); - // Converts an error leveldb::Status to a ValueStore::Error. - ValueStore::Status ToValueStoreError(const leveldb::Status& status); - - // Delete a value (identified by |key|) from the value store. - leveldb::Status Delete(const std::string& key); - - // Removes the on-disk database at |db_path_|. Any file system locks should - // be released before calling this method. - bool DeleteDbFile(); - - // Returns whether the database is empty. - bool IsEmpty(); - - // The location of the leveldb backend. - const base::FilePath db_path_; - leveldb::Options open_options_; - leveldb::ReadOptions read_options_; - - // leveldb backend. - scoped_ptr db_; - // Database is corrupt - restoration failed. - bool db_unrecoverable_; - base::HistogramBase* open_histogram_; - base::HistogramBase* db_restore_histogram_; - base::HistogramBase* value_restore_histogram_; - DISALLOW_COPY_AND_ASSIGN(LeveldbValueStore); }; -- cgit v1.1