diff options
Diffstat (limited to 'content/browser/indexed_db/leveldb/leveldb_database.cc')
-rw-r--r-- | content/browser/indexed_db/leveldb/leveldb_database.cc | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/content/browser/indexed_db/leveldb/leveldb_database.cc b/content/browser/indexed_db/leveldb/leveldb_database.cc new file mode 100644 index 0000000..0a8307c --- /dev/null +++ b/content/browser/indexed_db/leveldb/leveldb_database.cc @@ -0,0 +1,361 @@ +// Copyright (c) 2013 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 "content/browser/indexed_db/leveldb/leveldb_database.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" +#include "base/string16.h" +#include "base/sys_info.h" +#include "base/utf_string_conversions.h" +#include "content/browser/indexed_db/leveldb/leveldb_comparator.h" +#include "content/browser/indexed_db/leveldb/leveldb_iterator.h" +#include "content/browser/indexed_db/leveldb/leveldb_slice.h" +#include "content/browser/indexed_db/leveldb/leveldb_write_batch.h" +#include "third_party/leveldatabase/env_idb.h" +#include "third_party/leveldatabase/src/helpers/memenv/memenv.h" +#include "third_party/leveldatabase/src/include/leveldb/comparator.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "third_party/leveldatabase/src/include/leveldb/env.h" +#include "third_party/leveldatabase/src/include/leveldb/slice.h" + +namespace content { + +static leveldb::Slice MakeSlice(const std::vector<char>& value) { + return leveldb::Slice(&*value.begin(), value.size()); +} + +static leveldb::Slice MakeSlice(const LevelDBSlice& s) { + return leveldb::Slice(s.begin(), s.end() - s.begin()); +} + +static LevelDBSlice MakeLevelDBSlice(const leveldb::Slice& s) { + return LevelDBSlice(s.data(), s.data() + s.size()); +} + +class ComparatorAdapter : public leveldb::Comparator { + public: + explicit ComparatorAdapter(const LevelDBComparator* comparator) + : comparator_(comparator) {} + + virtual int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const + OVERRIDE { + return comparator_->Compare(MakeLevelDBSlice(a), MakeLevelDBSlice(b)); + } + + virtual const char* Name() const OVERRIDE { return comparator_->Name(); } + + // TODO(jsbell): Support the methods below in the future. + virtual void FindShortestSeparator(std::string* start, + const leveldb::Slice& limit) const + OVERRIDE {} + virtual void FindShortSuccessor(std::string* key) const OVERRIDE {} + + private: + const LevelDBComparator* comparator_; +}; + +LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase* db) + : db_(db->db_.get()), snapshot_(db_->GetSnapshot()) {} + +LevelDBSnapshot::~LevelDBSnapshot() { db_->ReleaseSnapshot(snapshot_); } + +LevelDBDatabase::LevelDBDatabase() {} + +LevelDBDatabase::~LevelDBDatabase() { + // db_'s destructor uses comparator_adapter_; order of deletion is important. + db_.reset(); + comparator_adapter_.reset(); + env_.reset(); +} + +static leveldb::Status OpenDB(leveldb::Comparator* comparator, + leveldb::Env* env, + const base::FilePath& path, + leveldb::DB** db) { + leveldb::Options options; + options.comparator = comparator; + options.create_if_missing = true; + options.paranoid_checks = true; + + // Marking compression as explicitly off so snappy support can be + // compiled in for other leveldb clients without implicitly enabling + // it for IndexedDB. http://crbug.com/81384 + options.compression = leveldb::kNoCompression; + + // 20 max_open_files is the minimum LevelDB allows but its cache behaves + // poorly with less than 4 files per shard. As of this writing the latest + // leveldb (1.9) hardcodes 16 shards. See + // https://code.google.com/p/chromium/issues/detail?id=227313#c11 + options.max_open_files = 80; + options.env = env; + + // ChromiumEnv assumes UTF8, converts back to FilePath before using. + return leveldb::DB::Open(options, path.AsUTF8Unsafe(), db); +} + +bool LevelDBDatabase::Destroy(const base::FilePath& file_name) { + leveldb::Options options; + options.env = leveldb::IDBEnv(); + // ChromiumEnv assumes UTF8, converts back to FilePath before using. + const leveldb::Status s = + leveldb::DestroyDB(file_name.AsUTF8Unsafe(), options); + return s.ok(); +} + +static void HistogramFreeSpace(const char* type, + const base::FilePath& file_name) { + string16 name = ASCIIToUTF16("WebCore.IndexedDB.LevelDB.Open") + + ASCIIToUTF16(type) + ASCIIToUTF16("FreeDiskSpace"); + int64 free_disk_space_in_k_bytes = + base::SysInfo::AmountOfFreeDiskSpace(file_name) / 1024; + if (free_disk_space_in_k_bytes < 0) { + base::Histogram::FactoryGet( + "WebCore.IndexedDB.LevelDB.FreeDiskSpaceFailure", + 1, + 2 /*boundary*/, + 2 /*boundary*/ + 1, + base::HistogramBase::kUmaTargetedHistogramFlag)->Add(1 /*sample*/); + return; + } + int clamped_disk_space_k_bytes = + free_disk_space_in_k_bytes > INT_MAX ? INT_MAX + : free_disk_space_in_k_bytes; + const uint64 histogram_max = static_cast<uint64>(1e9); + COMPILE_ASSERT(histogram_max <= INT_MAX, histogram_max_too_big); + base::Histogram::FactoryGet(UTF16ToUTF8(name), + 1, + histogram_max, + 11 /*buckets*/, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(clamped_disk_space_k_bytes); +} + +static void HistogramLevelDBError(const char* histogram_name, + const leveldb::Status& s) { + DCHECK(!s.ok()); + enum { + LEVEL_DB_NOT_FOUND, + LEVEL_DB_CORRUPTION, + LEVEL_DB_IO_ERROR, + LEVEL_DB_OTHER, + LEVEL_DB_MAX_ERROR + }; + int leveldb_error = LEVEL_DB_OTHER; + if (s.IsNotFound()) + leveldb_error = LEVEL_DB_NOT_FOUND; + else if (s.IsCorruption()) + leveldb_error = LEVEL_DB_CORRUPTION; + else if (s.IsIOError()) + leveldb_error = LEVEL_DB_IO_ERROR; + base::Histogram::FactoryGet(histogram_name, + 1, + LEVEL_DB_MAX_ERROR, + LEVEL_DB_MAX_ERROR + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(leveldb_error); +} + +scoped_ptr<LevelDBDatabase> LevelDBDatabase::Open( + const base::FilePath& file_name, + const LevelDBComparator* comparator) { + scoped_ptr<ComparatorAdapter> comparator_adapter( + new ComparatorAdapter(comparator)); + + leveldb::DB* db; + const leveldb::Status s = + OpenDB(comparator_adapter.get(), leveldb::IDBEnv(), file_name, &db); + + if (!s.ok()) { + HistogramLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", s); + HistogramFreeSpace("Failure", file_name); + + LOG(ERROR) << "Failed to open LevelDB database from " + << file_name.AsUTF8Unsafe() << "," << s.ToString(); + return scoped_ptr<LevelDBDatabase>(); + } + + HistogramFreeSpace("Success", file_name); + + scoped_ptr<LevelDBDatabase> result(new LevelDBDatabase); + result->db_ = make_scoped_ptr(db); + result->comparator_adapter_ = comparator_adapter.Pass(); + result->comparator_ = comparator; + + return result.Pass(); +} + +scoped_ptr<LevelDBDatabase> LevelDBDatabase::OpenInMemory( + const LevelDBComparator* comparator) { + scoped_ptr<ComparatorAdapter> comparator_adapter( + new ComparatorAdapter(comparator)); + scoped_ptr<leveldb::Env> in_memory_env(leveldb::NewMemEnv(leveldb::IDBEnv())); + + leveldb::DB* db; + const leveldb::Status s = OpenDB( + comparator_adapter.get(), in_memory_env.get(), base::FilePath(), &db); + + if (!s.ok()) { + LOG(ERROR) << "Failed to open in-memory LevelDB database: " << s.ToString(); + return scoped_ptr<LevelDBDatabase>(); + } + + scoped_ptr<LevelDBDatabase> result(new LevelDBDatabase); + result->env_ = in_memory_env.Pass(); + result->db_ = make_scoped_ptr(db); + result->comparator_adapter_ = comparator_adapter.Pass(); + result->comparator_ = comparator; + + return result.Pass(); +} + +bool LevelDBDatabase::Put(const LevelDBSlice& key, + const std::vector<char>& value) { + leveldb::WriteOptions write_options; + write_options.sync = true; + + const leveldb::Status s = + db_->Put(write_options, MakeSlice(key), MakeSlice(value)); + if (s.ok()) + return true; + LOG(ERROR) << "LevelDB put failed: " << s.ToString(); + return false; +} + +bool LevelDBDatabase::Remove(const LevelDBSlice& key) { + leveldb::WriteOptions write_options; + write_options.sync = true; + + const leveldb::Status s = db_->Delete(write_options, MakeSlice(key)); + if (s.ok()) + return true; + if (s.IsNotFound()) + return false; + LOG(ERROR) << "LevelDB remove failed: " << s.ToString(); + return false; +} + +bool LevelDBDatabase::Get(const LevelDBSlice& key, + std::vector<char>& value, + bool& found, + const LevelDBSnapshot* snapshot) { + found = false; + std::string result; + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; // TODO(jsbell): Disable this if the + // performance impact is too great. + read_options.snapshot = snapshot ? snapshot->snapshot_ : 0; + + const leveldb::Status s = db_->Get(read_options, MakeSlice(key), &result); + if (s.ok()) { + found = true; + value.clear(); + value.insert(value.end(), result.begin(), result.end()); + return true; + } + if (s.IsNotFound()) + return true; + LOG(ERROR) << "LevelDB get failed: " << s.ToString(); + return false; +} + +bool LevelDBDatabase::Write(LevelDBWriteBatch& write_batch) { + leveldb::WriteOptions write_options; + write_options.sync = true; + + const leveldb::Status s = + db_->Write(write_options, write_batch.write_batch_.get()); + if (s.ok()) + return true; + HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s); + LOG(ERROR) << "LevelDB write failed: " << s.ToString(); + return false; +} + +namespace { +class IteratorImpl : public LevelDBIterator { + public: + virtual ~IteratorImpl() {} + + virtual bool IsValid() const OVERRIDE; + virtual void SeekToLast() OVERRIDE; + virtual void Seek(const LevelDBSlice& target) OVERRIDE; + virtual void Next() OVERRIDE; + virtual void Prev() OVERRIDE; + virtual LevelDBSlice Key() const OVERRIDE; + virtual LevelDBSlice Value() const OVERRIDE; + + private: + friend class content::LevelDBDatabase; + IteratorImpl(scoped_ptr<leveldb::Iterator> iterator); + void CheckStatus(); + + scoped_ptr<leveldb::Iterator> iterator_; +}; +} + +IteratorImpl::IteratorImpl(scoped_ptr<leveldb::Iterator> it) + : iterator_(it.Pass()) {} + +void IteratorImpl::CheckStatus() { + const leveldb::Status s = iterator_->status(); + if (!s.ok()) + LOG(ERROR) << "LevelDB iterator error: " << s.ToString(); +} + +bool IteratorImpl::IsValid() const { return iterator_->Valid(); } + +void IteratorImpl::SeekToLast() { + iterator_->SeekToLast(); + CheckStatus(); +} + +void IteratorImpl::Seek(const LevelDBSlice& target) { + iterator_->Seek(MakeSlice(target)); + CheckStatus(); +} + +void IteratorImpl::Next() { + DCHECK(IsValid()); + iterator_->Next(); + CheckStatus(); +} + +void IteratorImpl::Prev() { + DCHECK(IsValid()); + iterator_->Prev(); + CheckStatus(); +} + +LevelDBSlice IteratorImpl::Key() const { + DCHECK(IsValid()); + return MakeLevelDBSlice(iterator_->key()); +} + +LevelDBSlice IteratorImpl::Value() const { + DCHECK(IsValid()); + return MakeLevelDBSlice(iterator_->value()); +} + +scoped_ptr<LevelDBIterator> LevelDBDatabase::CreateIterator( + const LevelDBSnapshot* snapshot) { + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; // TODO(jsbell): Disable this if the + // performance impact is too great. + read_options.snapshot = snapshot ? snapshot->snapshot_ : 0; + scoped_ptr<leveldb::Iterator> i(db_->NewIterator(read_options)); + if (!i) // TODO(jsbell): Double check if we actually need to check this. + return scoped_ptr<LevelDBIterator>(); + return scoped_ptr<LevelDBIterator>(new IteratorImpl(i.Pass())); +} + +const LevelDBComparator* LevelDBDatabase::Comparator() const { + return comparator_; +} + +} // namespace content |