blob: c6ea9b5d14753298dd0daf1e4f16aa1234eae510 [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 "base/bind.h"
#include "base/containers/flat_set.h"
#include "base/files/file.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/metrics/histogram_macros.h"
#include "base/sys_info.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 leveldb::Cache;
using leveldb::NewLRUCache;
namespace leveldb_chrome {
namespace {
size_t DefaultBlockCacheSize() {
if (base::SysInfo::IsLowEndDevice())
return 1 << 20; // 1MB
else
return 8 << 20; // 8MB
}
// Singleton owning resources shared by Chrome's leveldb databases.
class Globals {
public:
static Globals* GetInstance() {
static Globals* globals = new Globals();
return globals;
}
Globals() : browser_block_cache_(NewLRUCache(DefaultBlockCacheSize())) {
if (!base::SysInfo::IsLowEndDevice())
web_block_cache_.reset(NewLRUCache(DefaultBlockCacheSize()));
memory_pressure_listener_.reset(new base::MemoryPressureListener(
base::Bind(&Globals::OnMemoryPressure, 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 ==
MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE)
return;
browser_block_cache()->Prune();
if (browser_block_cache() == web_block_cache())
return;
web_block_cache()->Prune();
}
void DidCreateChromeMemEnv(leveldb::Env* env) {
leveldb::MutexLock l(&env_mutex_);
DCHECK(in_memory_envs_.find(env) == in_memory_envs_.end());
in_memory_envs_.insert(env);
}
void WillDestroyChromeMemEnv(leveldb::Env* env) {
leveldb::MutexLock l(&env_mutex_);
DCHECK(in_memory_envs_.find(env) != in_memory_envs_.end());
in_memory_envs_.erase(env);
}
bool IsInMemoryEnv(const leveldb::Env* env) const {
leveldb::MutexLock l(&env_mutex_);
return in_memory_envs_.find(env) != in_memory_envs_.end();
}
void UpdateHistograms() {
leveldb_env::DBTracker::GetInstance()->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()) {
UMA_HISTOGRAM_COUNTS_100000("LevelDB.SharedCache.KBUsed.Unified",
browser_block_cache_->TotalCharge() / 1024);
return;
}
UMA_HISTOGRAM_COUNTS_100000("LevelDB.SharedCache.KBUsed.Web",
web_block_cache_->TotalCharge() / 1024);
UMA_HISTOGRAM_COUNTS_100000("LevelDB.SharedCache.KBUsed.Browser",
browser_block_cache_->TotalCharge() / 1024);
}
private:
~Globals() {}
std::unique_ptr<Cache> web_block_cache_; // null on low end devices.
std::unique_ptr<Cache> browser_block_cache_; // Never null.
// Listens for the system being under memory pressure.
std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
mutable leveldb::port::Mutex env_mutex_;
base::flat_set<leveldb::Env*> in_memory_envs_;
DISALLOW_COPY_AND_ASSIGN(Globals);
};
class ChromeMemEnv : public leveldb::EnvWrapper {
public:
ChromeMemEnv(leveldb::Env* base_env)
: EnvWrapper(leveldb::NewMemEnv(base_env)), base_env_(target()) {
Globals::GetInstance()->DidCreateChromeMemEnv(this);
}
~ChromeMemEnv() override {
Globals::GetInstance()->WillDestroyChromeMemEnv(this);
}
private:
std::unique_ptr<leveldb::Env> base_env_;
DISALLOW_COPY_AND_ASSIGN(ChromeMemEnv);
};
} // 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) {
DCHECK(env);
return Globals::GetInstance()->IsInMemoryEnv(env);
}
leveldb::Env* NewMemEnv(leveldb::Env* base_env) {
return new ChromeMemEnv(base_env);
}
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;
current.Close();
return true;
}
} // namespace leveldb_chrome