blob: 65e8dd551d24c983f21b40660cea1ca45d14f228 [file] [log] [blame]
// 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 <inttypes.h>
#include <stdint.h>
#include <algorithm>
#include <cerrno>
#include <memory>
#include <utility>
#include "base/files/file.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/time/default_clock.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/process_memory_dump.h"
#include "build/build_config.h"
#include "content/browser/indexed_db/indexed_db_class_factory.h"
#include "content/browser/indexed_db/indexed_db_tracing.h"
#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
#include "content/browser/indexed_db/leveldb/leveldb_env.h"
#include "content/browser/indexed_db/leveldb/leveldb_iterator_impl.h"
#include "content/browser/indexed_db/leveldb/leveldb_write_batch.h"
#include "third_party/leveldatabase/env_chromium.h"
#include "third_party/leveldatabase/leveldb_chrome.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
#include "third_party/leveldatabase/src/include/leveldb/filter_policy.h"
#include "third_party/leveldatabase/src/include/leveldb/slice.h"
using base::StringPiece;
using leveldb_env::DBTracker;
namespace content {
namespace {
// Forcing flushes to disk at the end of a transaction guarantees that the
// data hit disk, but drastically impacts throughput when the filesystem is
// busy with background compactions. Not syncing trades off reliability for
// performance. Note that background compactions which move data from the
// log to SSTs are always done with reliable writes.
//
// Sync writes are necessary on Windows for quota calculations; POSIX
// calculates file sizes correctly even when not synced to disk.
#if defined(OS_WIN)
const bool kSyncWrites = true;
#else
// TODO(dgrogan): Either remove the #if block or change this back to false.
// See http://crbug.com/338385.
const bool kSyncWrites = true;
#endif
class LockImpl : public LevelDBLock {
public:
explicit LockImpl(leveldb::Env* env, leveldb::FileLock* lock)
: env_(env), lock_(lock) {}
~LockImpl() override { env_->UnlockFile(lock_); }
private:
leveldb::Env* env_;
leveldb::FileLock* lock_;
DISALLOW_COPY_AND_ASSIGN(LockImpl);
};
class ComparatorAdapter : public leveldb::Comparator {
public:
explicit ComparatorAdapter(const LevelDBComparator* comparator)
: comparator_(comparator) {}
int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const override {
return comparator_->Compare(leveldb_env::MakeStringPiece(a),
leveldb_env::MakeStringPiece(b));
}
const char* Name() const override { return comparator_->Name(); }
// TODO(jsbell): Support the methods below in the future.
void FindShortestSeparator(std::string* start,
const leveldb::Slice& limit) const override {}
void FindShortSuccessor(std::string* key) const override {}
private:
const LevelDBComparator* comparator_;
};
leveldb::Status OpenDB(
leveldb::Comparator* comparator,
leveldb::Env* env,
const base::FilePath& path,
std::unique_ptr<leveldb::DB>* db,
std::unique_ptr<const leveldb::FilterPolicy>* filter_policy) {
filter_policy->reset(leveldb::NewBloomFilterPolicy(10));
leveldb_env::Options options;
options.comparator = comparator;
options.create_if_missing = true;
options.paranoid_checks = true;
options.filter_policy = filter_policy->get();
options.compression = leveldb::kSnappyCompression;
options.write_buffer_size =
leveldb_env::WriteBufferSize(base::SysInfo::AmountOfTotalDiskSpace(path));
// For info about the troubles we've run into with this parameter, see:
// https://code.google.com/p/chromium/issues/detail?id=227313#c11
options.max_open_files = 80;
options.env = env;
options.block_cache = leveldb_chrome::GetSharedWebBlockCache();
// ChromiumEnv assumes UTF8, converts back to FilePath before using.
return leveldb_env::OpenDB(options, path.AsUTF8Unsafe(), db);
}
int CheckFreeSpace(const char* const type, const base::FilePath& file_name) {
std::string name =
std::string("WebCore.IndexedDB.LevelDB.Open") + type + "FreeDiskSpace";
int64_t 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 -1;
}
int clamped_disk_space_k_bytes = free_disk_space_in_k_bytes > INT_MAX
? INT_MAX
: free_disk_space_in_k_bytes;
const uint64_t histogram_max = static_cast<uint64_t>(1e9);
static_assert(histogram_max <= INT_MAX, "histogram_max too big");
base::Histogram::FactoryGet(name,
1,
histogram_max,
11 /*buckets*/,
base::HistogramBase::kUmaTargetedHistogramFlag)
->Add(clamped_disk_space_k_bytes);
return clamped_disk_space_k_bytes;
}
void ParseAndHistogramIOErrorDetails(const std::string& histogram_name,
const leveldb::Status& s) {
leveldb_env::MethodID method;
base::File::Error error = base::File::FILE_OK;
leveldb_env::ErrorParsingResult result =
leveldb_env::ParseMethodAndError(s, &method, &error);
if (result == leveldb_env::NONE)
return;
std::string method_histogram_name(histogram_name);
method_histogram_name.append(".EnvMethod");
base::LinearHistogram::FactoryGet(
method_histogram_name,
1,
leveldb_env::kNumEntries,
leveldb_env::kNumEntries + 1,
base::HistogramBase::kUmaTargetedHistogramFlag)->Add(method);
std::string error_histogram_name(histogram_name);
if (result == leveldb_env::METHOD_AND_BFE) {
DCHECK_LT(error, 0);
error_histogram_name.append(std::string(".BFE.") +
leveldb_env::MethodIDToString(method));
base::LinearHistogram::FactoryGet(
error_histogram_name,
1,
-base::File::FILE_ERROR_MAX,
-base::File::FILE_ERROR_MAX + 1,
base::HistogramBase::kUmaTargetedHistogramFlag)->Add(-error);
}
}
void ParseAndHistogramCorruptionDetails(const std::string& histogram_name,
const leveldb::Status& status) {
int error = leveldb_env::GetCorruptionCode(status);
DCHECK_GE(error, 0);
std::string corruption_histogram_name(histogram_name);
corruption_histogram_name.append(".Corruption");
const int kNumPatterns = leveldb_env::GetNumCorruptionCodes();
base::LinearHistogram::FactoryGet(
corruption_histogram_name,
1,
kNumPatterns,
kNumPatterns + 1,
base::HistogramBase::kUmaTargetedHistogramFlag)->Add(error);
}
void HistogramLevelDBError(const std::string& histogram_name,
const leveldb::Status& s) {
if (s.ok()) {
NOTREACHED();
return;
}
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);
if (s.IsIOError())
ParseAndHistogramIOErrorDetails(histogram_name, s);
else
ParseAndHistogramCorruptionDetails(histogram_name, s);
}
} // namespace
LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase* db)
: db_(db->db_.get()), snapshot_(db_->GetSnapshot()) {}
LevelDBSnapshot::~LevelDBSnapshot() {
db_->ReleaseSnapshot(snapshot_);
}
LevelDBDatabase::LevelDBDatabase(size_t max_open_iterators)
: clock_(new base::DefaultClock()), iterator_lru_(max_open_iterators) {
DCHECK(max_open_iterators);
}
LevelDBDatabase::~LevelDBDatabase() {
LOCAL_HISTOGRAM_COUNTS_10000("Storage.IndexedDB.LevelDB.MaxIterators",
max_iterators_);
base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
this);
// db_'s destructor uses comparator_adapter_; order of deletion is important.
CloseDatabase();
comparator_adapter_.reset();
env_.reset();
}
void LevelDBDatabase::CloseDatabase() {
if (db_) {
base::TimeTicks begin_time = base::TimeTicks::Now();
db_.reset();
UMA_HISTOGRAM_MEDIUM_TIMES("WebCore.IndexedDB.LevelDB.CloseTime",
base::TimeTicks::Now() - begin_time);
}
}
// static
leveldb::Status LevelDBDatabase::Destroy(const base::FilePath& file_name) {
leveldb_env::Options options;
options.env = LevelDBEnv::Get();
// ChromiumEnv assumes UTF8, converts back to FilePath before using.
return leveldb::DestroyDB(file_name.AsUTF8Unsafe(), options);
}
// static
std::unique_ptr<LevelDBLock> LevelDBDatabase::LockForTesting(
const base::FilePath& file_name) {
leveldb::Env* env = LevelDBEnv::Get();
base::FilePath lock_path = file_name.AppendASCII("LOCK");
leveldb::FileLock* lock = nullptr;
leveldb::Status status = env->LockFile(lock_path.AsUTF8Unsafe(), &lock);
if (!status.ok())
return std::unique_ptr<LevelDBLock>();
DCHECK(lock);
return std::make_unique<LockImpl>(env, lock);
}
// static
leveldb::Status LevelDBDatabase::Open(const base::FilePath& file_name,
const LevelDBComparator* comparator,
size_t max_open_cursors,
std::unique_ptr<LevelDBDatabase>* result,
bool* is_disk_full) {
IDB_TRACE("LevelDBDatabase::Open");
base::TimeTicks begin_time = base::TimeTicks::Now();
std::unique_ptr<ComparatorAdapter> comparator_adapter(
std::make_unique<ComparatorAdapter>(comparator));
std::unique_ptr<leveldb::DB> db;
std::unique_ptr<const leveldb::FilterPolicy> filter_policy;
const leveldb::Status s = OpenDB(comparator_adapter.get(), LevelDBEnv::Get(),
file_name, &db, &filter_policy);
if (!s.ok()) {
HistogramLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", s);
int free_space_k_bytes = CheckFreeSpace("Failure", file_name);
// Disks with <100k of free space almost never succeed in opening a
// leveldb database.
if (is_disk_full)
*is_disk_full = free_space_k_bytes >= 0 && free_space_k_bytes < 100;
LOG(ERROR) << "Failed to open LevelDB database from "
<< file_name.AsUTF8Unsafe() << "," << s.ToString();
return s;
}
UMA_HISTOGRAM_MEDIUM_TIMES("WebCore.IndexedDB.LevelDB.OpenTime",
base::TimeTicks::Now() - begin_time);
CheckFreeSpace("Success", file_name);
(*result) = base::WrapUnique(new LevelDBDatabase(max_open_cursors));
(*result)->db_ = std::move(db);
(*result)->comparator_adapter_ = std::move(comparator_adapter);
(*result)->comparator_ = comparator;
(*result)->filter_policy_ = std::move(filter_policy);
(*result)->file_name_for_tracing = file_name.BaseName().AsUTF8Unsafe();
return s;
}
// static
std::unique_ptr<LevelDBDatabase> LevelDBDatabase::OpenInMemory(
const LevelDBComparator* comparator) {
std::unique_ptr<ComparatorAdapter> comparator_adapter(
std::make_unique<ComparatorAdapter>(comparator));
std::unique_ptr<leveldb::Env> in_memory_env(
leveldb_chrome::NewMemEnv("indexed-db", LevelDBEnv::Get()));
std::unique_ptr<leveldb::DB> db;
std::unique_ptr<const leveldb::FilterPolicy> filter_policy;
const leveldb::Status s = OpenDB(comparator_adapter.get(),
in_memory_env.get(),
base::FilePath(),
&db,
&filter_policy);
if (!s.ok()) {
LOG(ERROR) << "Failed to open in-memory LevelDB database: " << s.ToString();
return std::unique_ptr<LevelDBDatabase>();
}
std::unique_ptr<LevelDBDatabase> result = base::WrapUnique(
new LevelDBDatabase(kDefaultMaxOpenIteratorsPerDatabase));
result->env_ = std::move(in_memory_env);
result->db_ = std::move(db);
result->comparator_adapter_ = std::move(comparator_adapter);
result->comparator_ = comparator;
result->filter_policy_ = std::move(filter_policy);
result->file_name_for_tracing = "in-memory-database";
return result;
}
leveldb::Status LevelDBDatabase::Put(const StringPiece& key,
std::string* value) {
base::TimeTicks begin_time = base::TimeTicks::Now();
leveldb::WriteOptions write_options;
write_options.sync = kSyncWrites;
const leveldb::Status s = db_->Put(write_options, leveldb_env::MakeSlice(key),
leveldb_env::MakeSlice(*value));
if (!s.ok())
LOG(ERROR) << "LevelDB put failed: " << s.ToString();
else
UMA_HISTOGRAM_TIMES("WebCore.IndexedDB.LevelDB.PutTime",
base::TimeTicks::Now() - begin_time);
last_modified_ = clock_->Now();
return s;
}
leveldb::Status LevelDBDatabase::Remove(const StringPiece& key) {
leveldb::WriteOptions write_options;
write_options.sync = kSyncWrites;
const leveldb::Status s =
db_->Delete(write_options, leveldb_env::MakeSlice(key));
if (!s.ok() && !s.IsNotFound())
LOG(ERROR) << "LevelDB remove failed: " << s.ToString();
last_modified_ = clock_->Now();
return s;
}
leveldb::Status LevelDBDatabase::Get(const StringPiece& key,
std::string* value,
bool* found,
const LevelDBSnapshot* snapshot) {
*found = false;
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_ : nullptr;
const leveldb::Status s =
db_->Get(read_options, leveldb_env::MakeSlice(key), value);
if (s.ok()) {
*found = true;
return s;
}
if (s.IsNotFound())
return leveldb::Status::OK();
HistogramLevelDBError("WebCore.IndexedDB.LevelDBReadErrors", s);
LOG(ERROR) << "LevelDB get failed: " << s.ToString();
return s;
}
leveldb::Status LevelDBDatabase::Write(const LevelDBWriteBatch& write_batch) {
base::TimeTicks begin_time = base::TimeTicks::Now();
leveldb::WriteOptions write_options;
write_options.sync = kSyncWrites;
const leveldb::Status s =
db_->Write(write_options, write_batch.write_batch_.get());
if (!s.ok()) {
HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s);
LOG(ERROR) << "LevelDB write failed: " << s.ToString();
} else {
UMA_HISTOGRAM_TIMES("WebCore.IndexedDB.LevelDB.WriteTime",
base::TimeTicks::Now() - begin_time);
}
last_modified_ = clock_->Now();
return s;
}
std::unique_ptr<LevelDBIterator> LevelDBDatabase::CreateIterator(
const leveldb::ReadOptions& options) {
num_iterators_++;
max_iterators_ = std::max(max_iterators_, num_iterators_);
// Iterator isn't added to lru cache until it is used, as memory isn't loaded
// for the iterator until it's first Seek call.
std::unique_ptr<leveldb::Iterator> i(db_->NewIterator(options));
return std::unique_ptr<LevelDBIterator>(
IndexedDBClassFactory::Get()->CreateIteratorImpl(std::move(i), this,
options.snapshot));
}
const LevelDBComparator* LevelDBDatabase::Comparator() const {
return comparator_;
}
void LevelDBDatabase::Compact(const base::StringPiece& start,
const base::StringPiece& stop) {
IDB_TRACE("LevelDBDatabase::Compact");
const leveldb::Slice start_slice = leveldb_env::MakeSlice(start);
const leveldb::Slice stop_slice = leveldb_env::MakeSlice(stop);
// NULL batch means just wait for earlier writes to be done
db_->Write(leveldb::WriteOptions(), nullptr);
db_->CompactRange(&start_slice, &stop_slice);
}
void LevelDBDatabase::CompactAll() {
db_->CompactRange(nullptr, nullptr);
}
leveldb::ReadOptions LevelDBDatabase::DefaultReadOptions() {
return DefaultReadOptions(nullptr);
}
leveldb::ReadOptions LevelDBDatabase::DefaultReadOptions(
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_ : nullptr;
return read_options;
}
bool LevelDBDatabase::OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) {
if (!db_)
return false;
// All leveldb databases are already dumped by leveldb_env::DBTracker. Add
// an edge to the existing database.
auto* db_tracker_dump =
leveldb_env::DBTracker::GetOrCreateAllocatorDump(pmd, db_.get());
if (!db_tracker_dump)
return true;
auto* db_dump = pmd->CreateAllocatorDump(
base::StringPrintf("site_storage/index_db/db_0x%" PRIXPTR,
reinterpret_cast<uintptr_t>(db_.get())));
db_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
base::trace_event::MemoryAllocatorDump::kUnitsBytes,
db_tracker_dump->GetSizeInternal());
pmd->AddOwnershipEdge(db_dump->guid(), db_tracker_dump->guid());
if (env_ && leveldb_chrome::IsMemEnv(env_.get())) {
// All leveldb env's are already dumped by leveldb_env::DBTracker. Add
// an edge to the existing env.
auto* env_tracker_dump =
DBTracker::GetOrCreateAllocatorDump(pmd, env_.get());
auto* env_dump = pmd->CreateAllocatorDump(
base::StringPrintf("site_storage/index_db/memenv_0x%" PRIXPTR,
reinterpret_cast<uintptr_t>(env_.get())));
env_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
base::trace_event::MemoryAllocatorDump::kUnitsBytes,
env_tracker_dump->GetSizeInternal());
pmd->AddOwnershipEdge(env_dump->guid(), env_tracker_dump->guid());
}
// Dumps in BACKGROUND mode can only have whitelisted strings (and there are
// currently none) so return early.
if (args.level_of_detail ==
base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND) {
return true;
}
db_dump->AddString("file_name", "", file_name_for_tracing);
return true;
}
void LevelDBDatabase::SetClockForTesting(std::unique_ptr<base::Clock> clock) {
clock_ = std::move(clock);
}
std::unique_ptr<leveldb::Iterator> LevelDBDatabase::CreateLevelDBIterator(
const leveldb::Snapshot* snapshot) {
leveldb::ReadOptions read_options;
read_options.verify_checksums = true;
read_options.snapshot = snapshot;
return std::unique_ptr<leveldb::Iterator>(db_->NewIterator(read_options));
}
LevelDBDatabase::DetachIteratorOnDestruct::~DetachIteratorOnDestruct() {
if (it_)
it_->Detach();
}
void LevelDBDatabase::OnIteratorUsed(LevelDBIterator* iter) {
// This line updates the LRU if the item exists.
if (iterator_lru_.Get(iter) != iterator_lru_.end())
return;
DetachIteratorOnDestruct purger(iter);
iterator_lru_.Put(iter, std::move(purger));
}
void LevelDBDatabase::OnIteratorDestroyed(LevelDBIterator* iter) {
DCHECK_GT(num_iterators_, 0u);
--num_iterators_;
auto it = iterator_lru_.Peek(iter);
if (it == iterator_lru_.end())
return;
iterator_lru_.Erase(it);
}
} // namespace content