// 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 #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 web_block_cache_; // null on low end devices. std::unique_ptr browser_block_cache_; // Never null. // Listens for the system being under memory pressure. std::unique_ptr memory_pressure_listener_; mutable leveldb::port::Mutex env_mutex_; base::flat_set 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 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