blob: f403ae3b488466626b9a128ae4b509eedce1aa6c [file] [log] [blame]
// Copyright 2017 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. See the AUTHORS file for names of contributors.
#include "third_party/leveldatabase/leveldb_chrome.h"
#include <memory>
#include <set>
#include <vector>
#include "base/bind.h"
#include "base/containers/flat_set.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_dump_provider.h"
#include "base/trace_event/process_memory_dump.h"
#include "build/build_config.h"
#include "third_party/leveldatabase/env_chromium.h"
#include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
#include "util/mutexlock.h"
using MemoryPressureLevel = base::MemoryPressureListener::MemoryPressureLevel;
using base::trace_event::MemoryAllocatorDump;
using base::trace_event::MemoryDumpArgs;
using base::trace_event::MemoryDumpProvider;
using base::trace_event::ProcessMemoryDump;
using leveldb::Cache;
using leveldb::NewLRUCache;
namespace leveldb_chrome {
namespace {
size_t DefaultBlockCacheSize() {
if (base::SysInfo::IsLowEndDevice())
return 1 << 20; // 1MB
return 8 << 20; // 8MB
std::string GetDumpNameForMemEnv(const leveldb::Env* memenv) {
return base::StringPrintf("leveldatabase/memenv_0x%" PRIXPTR,
// Singleton owning resources shared by Chrome's leveldb databases.
class Globals {
static Globals* GetInstance() {
static base::NoDestructor<Globals> singleton;
return singleton.get();
: web_block_cache_(base::SysInfo::IsLowEndDevice()
? nullptr
: NewLRUCache(DefaultBlockCacheSize())),
// Using |this| here (when Globals is only partially constructed) is
// safe because base::MemoryPressureListener calls our callback
// asynchronously, so this instance will be fully constructed by the
// time it is called.
base::Unretained(this))) {}
Cache* web_block_cache() const {
if (web_block_cache_)
return web_block_cache_.get();
return browser_block_cache();
Cache* browser_block_cache() const { return browser_block_cache_.get(); }
// Called when the system is under memory pressure.
void OnMemoryPressure(MemoryPressureLevel memory_pressure_level) {
if (memory_pressure_level ==
if (browser_block_cache() == web_block_cache())
void DidCreateChromeMemEnv(leveldb::Env* env) {
leveldb::MutexLock l(&env_mutex_);
DCHECK(in_memory_envs_.find(env) == in_memory_envs_.end());
void WillDestroyChromeMemEnv(leveldb::Env* env) {
leveldb::MutexLock l(&env_mutex_);
DCHECK(in_memory_envs_.find(env) != in_memory_envs_.end());
bool IsInMemoryEnv(const leveldb::Env* env) const {
leveldb::MutexLock l(&env_mutex_);
return in_memory_envs_.find(env) != in_memory_envs_.end();
void DumpAllTrackedEnvs(const MemoryDumpArgs& dump_args,
base::trace_event::ProcessMemoryDump* pmd);
void UpdateHistograms() {
// In-memory caches are hard-coded to be zero bytes so don't log
// LevelDB.SharedCache.BytesUsed.InMemory.
// leveldb limits the read cache size to 1GB, but its default value is 8MB,
// and Chrome uses either 1MB or 8MB.
if (GetSharedWebBlockCache() == GetSharedBrowserBlockCache()) {
browser_block_cache_->TotalCharge() / 1024);
web_block_cache_->TotalCharge() / 1024);
browser_block_cache_->TotalCharge() / 1024);
// Instances are never destroyed.
// If this destructor needs to exist in the future, the callback given to
// base::MemoryPressureListener() must use a WeakPtr.
~Globals() = delete;
std::unique_ptr<Cache> web_block_cache_; // null on low end devices.
std::unique_ptr<Cache> browser_block_cache_; // Never null.
mutable leveldb::port::Mutex env_mutex_;
base::flat_set<leveldb::Env*> in_memory_envs_;
// Listens for the system being under memory pressure.
const base::MemoryPressureListener memory_pressure_listener_;
class ChromeMemEnv : public leveldb::EnvWrapper {
ChromeMemEnv(leveldb::Env* base_env, const std::string& name)
: EnvWrapper(leveldb::NewMemEnv(base_env)),
name_(name) {
~ChromeMemEnv() override {
leveldb::Status NewWritableFile(const std::string& f,
leveldb::WritableFile** r) override {
leveldb::Status s = leveldb::EnvWrapper::NewWritableFile(f, r);
if (s.ok()) {
base::AutoLock lock(files_lock_);
return s;
leveldb::Status NewAppendableFile(const std::string& f,
leveldb::WritableFile** r) override {
leveldb::Status s = leveldb::EnvWrapper::NewAppendableFile(f, r);
if (s.ok()) {
base::AutoLock lock(files_lock_);
return s;
leveldb::Status RemoveFile(const std::string& fname) override {
leveldb::Status s = leveldb::EnvWrapper::RemoveFile(fname);
if (s.ok()) {
base::AutoLock lock(files_lock_);
DCHECK(base::Contains(file_names_, fname));
return s;
leveldb::Status RenameFile(const std::string& src,
const std::string& target) override {
leveldb::Status s = leveldb::EnvWrapper::RenameFile(src, target);
if (s.ok()) {
base::AutoLock lock(files_lock_);
return s;
// Calculate the memory used by this in-memory database. leveldb's in-memory
// databases store their data in a MemEnv which can be shared between
// databases. Chrome rarely (if ever) shares MemEnv's. This calculation is
// also an estimate as leveldb does not expose a way to retrieve or properly
// calculate the amount of RAM used by a MemEnv.
uint64_t size() {
// This is copied from
// //third_party/leveldatabase/src/helpers/memenv/ which cannot be
// included here. Duplicated for size calculations only.
struct FileState {
leveldb::port::Mutex refs_mutex_;
int refs_;
std::vector<char*> blocks_;
uint64_t size_;
enum { kBlockSize = 8 * 1024 };
uint64_t total_size = 0;
base::AutoLock lock(files_lock_);
for (const std::string& fname : file_names_) {
uint64_t file_size;
leveldb::Status s = GetFileSize(fname, &file_size);
if (s.ok())
total_size += file_size + fname.size() + 1 + sizeof(FileState);
return total_size;
const std::string& name() const { return name_; }
bool OnMemoryDump(const MemoryDumpArgs& dump_args, ProcessMemoryDump* pmd) {
auto* env_dump = pmd->CreateAllocatorDump(GetDumpNameForMemEnv(this));
if (dump_args.level_of_detail !=
base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND) {
env_dump->AddString("name", "", name());
const char* system_allocator_name =
if (system_allocator_name) {
pmd->AddSuballocation(env_dump->guid(), system_allocator_name);
return true;
std::unique_ptr<leveldb::Env> base_env_;
const std::string name_;
base::Lock files_lock_;
std::set<std::string> file_names_;
void Globals::DumpAllTrackedEnvs(const MemoryDumpArgs& dump_args,
ProcessMemoryDump* pmd) {
leveldb::MutexLock l(&env_mutex_);
for (auto env : in_memory_envs_)
static_cast<ChromeMemEnv*>(env)->OnMemoryDump(dump_args, pmd);
// Delete all files in a |directory| using using the provided |env|.
// Note, this is not recursive as it is only called to delete files in an
// in-memory Env's filesystem.
leveldb::Status RemoveEnvDirectory(const std::string& directory,
leveldb::Env* env) {
std::vector<std::string> filenames;
leveldb::Status result = env->GetChildren(directory, &filenames);
if (!result.ok()) {
// Ignore error in case directory does not exist
return leveldb::Status::OK();
leveldb::FileLock* lock;
const std::string lockname = leveldb::LockFileName(directory);
result = env->LockFile(lockname, &lock);
if (!result.ok())
return result;
for (const std::string& filename : filenames) {
leveldb::Status del = env->RemoveFile(directory + "/" + filename);
if (result.ok() && !del.ok())
result = del;
env->UnlockFile(lock); // Ignore error since state is already gone
if (result.ok())
result = env->RemoveDir(directory);
return result;
} // namespace
// Returns a separate (from the default) block cache for use by web APIs.
// This must be used when opening the databases accessible to Web-exposed APIs,
// so rogue pages can't mount a denial of service attack by hammering the block
// cache. Without separate caches, such an attack might slow down Chrome's UI to
// the point where the user can't close the offending page's tabs.
Cache* GetSharedWebBlockCache() {
return Globals::GetInstance()->web_block_cache();
Cache* GetSharedBrowserBlockCache() {
return Globals::GetInstance()->browser_block_cache();
Cache* GetSharedInMemoryBlockCache() {
// Zero size cache to prevent cache hits.
static leveldb::Cache* s_empty_cache = leveldb::NewLRUCache(0);
return s_empty_cache;
bool IsMemEnv(const leveldb::Env* env) {
return Globals::GetInstance()->IsInMemoryEnv(env);
std::unique_ptr<leveldb::Env> NewMemEnv(const std::string& name,
leveldb::Env* base_env) {
if (!base_env)
base_env = leveldb::Env::Default();
return std::make_unique<ChromeMemEnv>(base_env, name);
void UpdateHistograms() {
return Globals::GetInstance()->UpdateHistograms();
bool ParseFileName(const std::string& filename,
uint64_t* number,
leveldb::FileType* type) {
return leveldb::ParseFileName(filename, number, type);
bool CorruptClosedDBForTesting(const base::FilePath& db_path) {
base::File current(db_path.Append(FILE_PATH_LITERAL("CURRENT")),
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!current.IsValid()) {
return false;
const char kString[] = "StringWithoutEOL";
if (current.Write(0, kString, sizeof(kString)) != sizeof(kString))
return false;
return true;
bool PossiblyValidDB(const base::FilePath& db_path, leveldb::Env* env) {
const base::FilePath current = db_path.Append(FILE_PATH_LITERAL("CURRENT"));
return env->FileExists(current.AsUTF8Unsafe());
leveldb::Status DeleteDB(const base::FilePath& db_path,
const leveldb::Options& options) {
leveldb::Status status = leveldb::DestroyDB(db_path.AsUTF8Unsafe(), options);
if (!status.ok())
return status;
if (options.env && leveldb_chrome::IsMemEnv(options.env)) {
// RemoveEnvDirectory isn't recursive, but this function assumes that (for
// in-memory env's only) leveldb is the only one writing to the Env so this
// is OK.
return RemoveEnvDirectory(db_path.AsUTF8Unsafe(), options.env);
// TODO(cmumford): To be fully safe this implementation should acquire a lock
// as there is some daylight in between DestroyDB and DeleteFile.
if (!base::DeleteFile(db_path, true)) {
// Only delete the directory when when DestroyDB is successful. This is
// because DestroyDB checks for database locks, and will fail if in use.
return leveldb::Status::IOError(db_path.AsUTF8Unsafe(), "Error deleting");
return leveldb::Status::OK();
MemoryAllocatorDump* GetEnvAllocatorDump(ProcessMemoryDump* pmd,
leveldb::Env* tracked_memenv) {
<< std::hex << tracked_memenv << " is not tracked";
return pmd->GetAllocatorDump(GetDumpNameForMemEnv(tracked_memenv));
void DumpAllTrackedEnvs(ProcessMemoryDump* pmd) {
Globals::GetInstance()->DumpAllTrackedEnvs(pmd->dump_args(), pmd);
} // namespace leveldb_chrome