| // Copyright (c) 2011 The LevelDB 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/env_chromium.h" |
| |
| #include <atomic> |
| #include <limits> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/check_op.h" |
| #include "base/files/file_util.h" |
| #include "base/format_macros.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/no_destructor.h" |
| #include "base/notreached.h" |
| #include "base/process/process_metrics.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/system/sys_info.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "base/time/time_override.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 "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "components/services/storage/public/cpp/filesystem/file_error_or.h" |
| #include "components/services/storage/public/cpp/filesystem/filesystem_proxy.h" |
| #include "third_party/leveldatabase/chromium_logger.h" |
| #include "third_party/leveldatabase/leveldb_chrome.h" |
| #include "third_party/leveldatabase/leveldb_features.h" |
| #include "third_party/leveldatabase/src/include/leveldb/options.h" |
| #include "third_party/re2/src/re2/re2.h" |
| |
| using base::FilePath; |
| using base::trace_event::MemoryAllocatorDump; |
| using base::trace_event::MemoryDumpArgs; |
| using base::trace_event::ProcessMemoryDump; |
| using leveldb::FileLock; |
| using leveldb::Slice; |
| using leveldb::Status; |
| |
| namespace leveldb_env { |
| namespace { |
| |
| template <typename ValueType> |
| using FileErrorOr = storage::FileErrorOr<ValueType>; |
| |
| // After this limit we don't bother doing file eviction for leveldb for speed, |
| // memory usage, and simplicity. |
| const constexpr size_t kFileLimitToDisableEviction = 10'000; |
| |
| // The maximum time for the |Retrier| to indicate that an operation should |
| // be retried. |
| constexpr auto kMaxRetryDuration = base::TimeDelta::FromMilliseconds(1000); |
| |
| const FilePath::CharType table_extension[] = FILE_PATH_LITERAL(".ldb"); |
| |
| static const FilePath::CharType kLevelDBTestDirectoryPrefix[] = |
| FILE_PATH_LITERAL("leveldb-test-"); |
| |
| // This name should not be changed or users involved in a crash might not be |
| // able to recover data. |
| static const char kDatabaseNameSuffixForRebuildDB[] = "__tmp_for_rebuild"; |
| |
| class ChromiumFileLock : public FileLock { |
| public: |
| ChromiumFileLock(std::unique_ptr<storage::FilesystemProxy::FileLock> lock, |
| const std::string& name) |
| : lock(std::move(lock)), name(name) {} |
| ChromiumFileLock(const ChromiumFileLock&) = delete; |
| ChromiumFileLock& operator=(const ChromiumFileLock&) = delete; |
| |
| const std::unique_ptr<storage::FilesystemProxy::FileLock> lock; |
| const std::string name; |
| }; |
| |
| class Retrier { |
| public: |
| Retrier() |
| // TODO(crbug.com/1059965): figure out a better way to handle time for |
| // tests. |
| : start_(base::subtle::TimeTicksNowIgnoringOverride()), |
| limit_(start_ + kMaxRetryDuration), |
| last_(start_), |
| time_to_sleep_(base::TimeDelta::FromMilliseconds(10)) {} |
| ~Retrier() = default; |
| bool ShouldKeepTrying() { |
| if (last_ < limit_) { |
| base::PlatformThread::Sleep(time_to_sleep_); |
| // TODO(crbug.com/1059965): figure out a better way to handle time for |
| // tests. |
| last_ = base::subtle::TimeTicksNowIgnoringOverride(); |
| return true; |
| } |
| return false; |
| } |
| |
| private: |
| const base::TimeTicks start_; |
| base::TimeTicks limit_; |
| base::TimeTicks last_; |
| base::TimeDelta time_to_sleep_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Retrier); |
| }; |
| |
| class ChromiumSequentialFile : public leveldb::SequentialFile { |
| public: |
| ChromiumSequentialFile(const std::string& fname, base::File f) |
| : filename_(fname), file_(std::move(f)) {} |
| ~ChromiumSequentialFile() override = default; |
| |
| // Note: This method is relatively hot during leveldb database |
| // compaction. Please avoid making them slower. |
| Status Read(size_t n, Slice* result, char* scratch) override { |
| int bytes_read = file_.ReadAtCurrentPosNoBestEffort(scratch, n); |
| if (bytes_read == -1) { |
| base::File::Error error = base::File::GetLastFileError(); |
| return MakeIOError(filename_, base::File::ErrorToString(error), |
| kSequentialFileRead, error); |
| } |
| *result = Slice(scratch, bytes_read); |
| return Status::OK(); |
| } |
| |
| Status Skip(uint64_t n) override { |
| if (file_.Seek(base::File::FROM_CURRENT, n) == -1) { |
| base::File::Error error = base::File::GetLastFileError(); |
| return MakeIOError(filename_, base::File::ErrorToString(error), |
| kSequentialFileSkip, error); |
| } else { |
| return Status::OK(); |
| } |
| } |
| |
| private: |
| std::string filename_; |
| base::File file_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ChromiumSequentialFile); |
| }; |
| |
| void RemoveFile(const Slice& key, void* value) { |
| delete static_cast<base::File*>(value); |
| } |
| |
| Status ReadFromFileToScratch(uint64_t offset, |
| size_t n, |
| Slice* result, |
| char* scratch, |
| base::File* file, |
| const base::FilePath& file_path) { |
| int bytes_read = file->Read(offset, scratch, n); |
| if (bytes_read < 0) { |
| return MakeIOError(file_path.AsUTF8Unsafe(), "Could not perform read", |
| kRandomAccessFileRead); |
| } |
| *result = Slice(scratch, (bytes_read < 0) ? 0 : bytes_read); |
| |
| return Status::OK(); |
| } |
| |
| // The cache mechanism uses leveldb's LRU cache, which is a threadsafe sharded |
| // LRU cache. The keys use the pointer value of |this|, and the values are file |
| // objects. |
| // Each object will only use its own cache entry, so the |Erase| call in the |
| // object destructor should synchronously delete the file from the cache. This |
| // ensures that pointer location re-use won't re-use an entry in the cache as |
| // the entry at |this| will always have been deleted. |
| // Files are always cleaned up with |RemoveFile|, which will be called when the |
| // ChromiumEvictableRandomAccessFile is deleted, the cache is deleted, or the |
| // file is evicted. |
| class ChromiumEvictableRandomAccessFile : public leveldb::RandomAccessFile { |
| public: |
| ChromiumEvictableRandomAccessFile(base::FilePath file_path, |
| base::File file, |
| storage::FilesystemProxy* filesystem, |
| leveldb::Cache* file_cache) |
| : filepath_(std::move(file_path)), |
| filesystem_(filesystem), |
| file_cache_(file_cache), |
| cache_key_data_(this), |
| cache_key_( |
| leveldb::Slice(reinterpret_cast<const char*>(&cache_key_data_), |
| sizeof(cache_key_data_))) { |
| DCHECK(file_cache_); |
| base::File* heap_file = new base::File(std::move(file)); |
| // A |charge| of '1' is used because the capacity is the file handle limit, |
| // and each entry is one file. |
| file_cache_->Release(file_cache_->Insert(cache_key_, heap_file, |
| 1 /* charge */, &RemoveFile)); |
| } |
| |
| virtual ~ChromiumEvictableRandomAccessFile() { |
| file_cache_->Erase(cache_key_); |
| } |
| |
| // Note: This method is relatively hot during leveldb database |
| // compaction. Please avoid making them slower. |
| Status Read(uint64_t offset, |
| size_t n, |
| Slice* result, |
| char* scratch) const override { |
| leveldb::Cache::Handle* handle = file_cache_->Lookup(cache_key_); |
| if (!handle) { |
| int flags = base::File::FLAG_READ | base::File::FLAG_OPEN; |
| FileErrorOr<base::File> result = filesystem_->OpenFile(filepath_, flags); |
| if (result.is_error()) { |
| return MakeIOError(filepath_.AsUTF8Unsafe(), "Could not perform read", |
| kRandomAccessFileRead); |
| } |
| handle = file_cache_->Insert(cache_key_, |
| new base::File(std::move(result.value())), |
| sizeof(base::File), &RemoveFile); |
| } |
| base::File* file = static_cast<base::File*>(file_cache_->Value(handle)); |
| Status status = |
| ReadFromFileToScratch(offset, n, result, scratch, file, filepath_); |
| file_cache_->Release(handle); |
| return status; |
| } |
| |
| private: |
| const base::FilePath filepath_; |
| storage::FilesystemProxy* const filesystem_; |
| mutable leveldb::Cache* file_cache_; |
| const ChromiumEvictableRandomAccessFile* cache_key_data_; |
| leveldb::Slice cache_key_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ChromiumEvictableRandomAccessFile); |
| }; |
| |
| class ChromiumRandomAccessFile : public leveldb::RandomAccessFile { |
| public: |
| ChromiumRandomAccessFile(base::FilePath file_path, base::File file) |
| : filepath_(std::move(file_path)), file_(std::move(file)) {} |
| |
| virtual ~ChromiumRandomAccessFile() {} |
| |
| // Note: This method is relatively hot during leveldb database |
| // compaction. Please avoid making them slower. |
| Status Read(uint64_t offset, |
| size_t n, |
| Slice* result, |
| char* scratch) const override { |
| return ReadFromFileToScratch(offset, n, result, scratch, &file_, filepath_); |
| } |
| |
| private: |
| const base::FilePath filepath_; |
| mutable base::File file_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ChromiumRandomAccessFile); |
| }; |
| |
| class ChromiumWritableFile : public leveldb::WritableFile { |
| public: |
| ChromiumWritableFile(const std::string& fname, |
| base::File f, |
| storage::FilesystemProxy* filesystem); |
| ~ChromiumWritableFile() override = default; |
| leveldb::Status Append(const leveldb::Slice& data) override; |
| leveldb::Status Close() override; |
| leveldb::Status Flush() override; |
| leveldb::Status Sync() override; |
| |
| private: |
| enum Type { kManifest, kTable, kOther }; |
| leveldb::Status SyncParent(); |
| |
| std::string filename_; |
| base::File file_; |
| #if defined(OS_POSIX) || defined(OS_FUCHSIA) |
| storage::FilesystemProxy* const filesystem_; |
| #endif |
| Type file_type_; |
| std::string parent_dir_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ChromiumWritableFile); |
| }; |
| |
| ChromiumWritableFile::ChromiumWritableFile(const std::string& fname, |
| base::File f, |
| storage::FilesystemProxy* filesystem) |
| : filename_(fname), |
| file_(std::move(f)), |
| #if defined(OS_POSIX) || defined(OS_FUCHSIA) |
| filesystem_(filesystem), |
| #endif |
| file_type_(kOther) { |
| FilePath path = FilePath::FromUTF8Unsafe(fname); |
| if (path.BaseName().AsUTF8Unsafe().find("MANIFEST") == 0) |
| file_type_ = kManifest; |
| else if (path.MatchesExtension(table_extension)) |
| file_type_ = kTable; |
| parent_dir_ = FilePath::FromUTF8Unsafe(fname).DirName().AsUTF8Unsafe(); |
| } |
| |
| Status ChromiumWritableFile::SyncParent() { |
| TRACE_EVENT0("leveldb", "SyncParent"); |
| #if defined(OS_POSIX) || defined(OS_FUCHSIA) |
| FilePath path = FilePath::FromUTF8Unsafe(parent_dir_); |
| FileErrorOr<base::File> result = filesystem_->OpenFile( |
| path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| if (result.is_error()) { |
| return MakeIOError(parent_dir_, "Unable to open directory", kSyncParent, |
| result.error()); |
| } |
| if (!result->Flush()) { |
| base::File::Error error = base::File::GetLastFileError(); |
| return MakeIOError(parent_dir_, base::File::ErrorToString(error), |
| kSyncParent, error); |
| } |
| #endif |
| return Status::OK(); |
| } |
| |
| Status ChromiumWritableFile::Append(const Slice& data) { |
| DCHECK(file_.IsValid()); |
| int bytes_written = file_.WriteAtCurrentPos(data.data(), data.size()); |
| if (static_cast<size_t>(bytes_written) != data.size()) { |
| base::File::Error error = base::File::GetLastFileError(); |
| return MakeIOError(filename_, base::File::ErrorToString(error), |
| kWritableFileAppend, error); |
| } |
| return Status::OK(); |
| } |
| |
| Status ChromiumWritableFile::Close() { |
| file_.Close(); |
| return Status::OK(); |
| } |
| |
| Status ChromiumWritableFile::Flush() { |
| // base::File doesn't do buffered I/O (i.e. POSIX FILE streams) so nothing to |
| // flush. |
| return Status::OK(); |
| } |
| |
| Status ChromiumWritableFile::Sync() { |
| TRACE_EVENT0("leveldb", "WritableFile::Sync"); |
| |
| // leveldb's implicit contract for Sync() is that if this instance is for a |
| // manifest file then the directory is also sync'ed, to ensure new files |
| // referred to by the manifest are in the filesystem. |
| // |
| // This needs to happen before the manifest file is flushed to disk, to |
| // avoid crashing in a state where the manifest refers to files that are not |
| // yet on disk. |
| // |
| // See leveldb's env_posix.cc. |
| if (file_type_ == kManifest) { |
| Status status = SyncParent(); |
| if (!status.ok()) |
| return status; |
| } |
| |
| if (!file_.Flush()) { |
| base::File::Error error = base::File::GetLastFileError(); |
| return MakeIOError(filename_, base::File::ErrorToString(error), |
| kWritableFileSync, error); |
| } |
| |
| return Status::OK(); |
| } |
| |
| // Return the maximum number of read-only files to keep open. |
| size_t GetLevelDBFileLimit(size_t max_file_descriptors) { |
| // Allow use of 20% of available file descriptors for read-only files. |
| return max_file_descriptors / 5; |
| } |
| |
| std::string GetDumpNameForDB(const leveldb::DB* db) { |
| return base::StringPrintf("leveldatabase/db_0x%" PRIXPTR, |
| reinterpret_cast<uintptr_t>(db)); |
| } |
| |
| std::string GetDumpNameForCache(DBTracker::SharedReadCacheUse cache) { |
| switch (cache) { |
| case DBTracker::SharedReadCacheUse_Browser: |
| return "leveldatabase/block_cache/browser"; |
| case DBTracker::SharedReadCacheUse_Web: |
| return "leveldatabase/block_cache/web"; |
| case DBTracker::SharedReadCacheUse_Unified: |
| return "leveldatabase/block_cache/unified"; |
| case DBTracker::SharedReadCacheUse_InMemory: |
| return "leveldatabase/block_cache/in_memory"; |
| case DBTracker::SharedReadCacheUse_NumCacheUses: |
| NOTREACHED(); |
| } |
| NOTREACHED(); |
| return ""; |
| } |
| |
| MemoryAllocatorDump* CreateDumpMalloced(ProcessMemoryDump* pmd, |
| const std::string& name, |
| size_t size) { |
| auto* dump = pmd->CreateAllocatorDump(name); |
| dump->AddScalar(MemoryAllocatorDump::kNameSize, |
| MemoryAllocatorDump::kUnitsBytes, size); |
| static const char* system_allocator_name = |
| base::trace_event::MemoryDumpManager::GetInstance() |
| ->system_allocator_pool_name(); |
| if (system_allocator_name) |
| pmd->AddSuballocation(dump->guid(), system_allocator_name); |
| return dump; |
| } |
| |
| void RecordCacheUsageInTracing(ProcessMemoryDump* pmd, |
| DBTracker::SharedReadCacheUse cache) { |
| std::string name = GetDumpNameForCache(cache); |
| leveldb::Cache* cache_ptr = nullptr; |
| switch (cache) { |
| case DBTracker::SharedReadCacheUse_Browser: |
| cache_ptr = leveldb_chrome::GetSharedBrowserBlockCache(); |
| break; |
| case DBTracker::SharedReadCacheUse_Web: |
| cache_ptr = leveldb_chrome::GetSharedWebBlockCache(); |
| break; |
| case DBTracker::SharedReadCacheUse_Unified: |
| cache_ptr = leveldb_chrome::GetSharedBrowserBlockCache(); |
| break; |
| case DBTracker::SharedReadCacheUse_InMemory: |
| cache_ptr = leveldb_chrome::GetSharedInMemoryBlockCache(); |
| break; |
| case DBTracker::SharedReadCacheUse_NumCacheUses: |
| NOTREACHED(); |
| } |
| if (!cache_ptr) |
| return; |
| CreateDumpMalloced(pmd, name, cache_ptr->TotalCharge()); |
| } |
| |
| } // namespace |
| |
| Options::Options() { |
| // Note: Ensure that these default values correspond to those in |
| // components/services/leveldb/public/mojom/leveldb.mojom. |
| // TODO(cmumford) Create struct-trait for leveldb.mojom.OpenOptions to force |
| // users to pass in a leveldb_env::Options instance (and it's defaults). |
| // |
| // Currently log reuse is an experimental feature in leveldb. More info at: |
| // https://github.com/google/leveldb/commit/251ebf5dc70129ad3 |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // Reusing logs on Chrome OS resulted in an unacceptably high leveldb |
| // corruption rate (at least for Indexed DB). More info at |
| // https://crbug.com/460568 |
| reuse_logs = false; |
| #else |
| // Low end devices have limited RAM. Reusing logs will prevent the database |
| // from being compacted on open and instead load the log file back into the |
| // memory buffer which won't be written until it hits the maximum size |
| // (leveldb::Options::write_buffer_size - 4MB by default). The downside here |
| // is that databases opens take longer as the open is blocked on compaction. |
| reuse_logs = !base::SysInfo::IsLowEndDevice(); |
| #endif |
| // By default use a single shared block cache to conserve memory. The owner of |
| // this object can create their own, or set to NULL to have leveldb create a |
| // new db-specific block cache. |
| block_cache = leveldb_chrome::GetSharedBrowserBlockCache(); |
| } |
| |
| const char* MethodIDToString(MethodID method) { |
| switch (method) { |
| case kSequentialFileRead: |
| return "SequentialFileRead"; |
| case kSequentialFileSkip: |
| return "SequentialFileSkip"; |
| case kRandomAccessFileRead: |
| return "RandomAccessFileRead"; |
| case kWritableFileAppend: |
| return "WritableFileAppend"; |
| case kWritableFileClose: |
| return "WritableFileClose"; |
| case kWritableFileFlush: |
| return "WritableFileFlush"; |
| case kWritableFileSync: |
| return "WritableFileSync"; |
| case kNewSequentialFile: |
| return "NewSequentialFile"; |
| case kNewRandomAccessFile: |
| return "NewRandomAccessFile"; |
| case kNewWritableFile: |
| return "NewWritableFile"; |
| case kNewAppendableFile: |
| return "NewAppendableFile"; |
| case kCreateDir: |
| return "CreateDir"; |
| case kGetFileSize: |
| return "GetFileSize"; |
| case kRenameFile: |
| return "RenameFile"; |
| case kLockFile: |
| return "LockFile"; |
| case kUnlockFile: |
| return "UnlockFile"; |
| case kGetTestDirectory: |
| return "GetTestDirectory"; |
| case kNewLogger: |
| return "NewLogger"; |
| case kSyncParent: |
| return "SyncParent"; |
| case kGetChildren: |
| return "GetChildren"; |
| case kRemoveFile: |
| return "RemoveFile"; |
| case kRemoveDir: |
| return "RemoveDir"; |
| case kObsoleteDeleteFile: |
| case kObsoleteDeleteDir: |
| case kNumEntries: |
| NOTREACHED(); |
| return "Unknown"; |
| } |
| NOTREACHED(); |
| return "Unknown"; |
| } |
| |
| Status MakeIOError(Slice filename, |
| const std::string& message, |
| MethodID method, |
| base::File::Error error) { |
| DCHECK_LT(error, 0); |
| char buf[512]; |
| base::snprintf(buf, sizeof(buf), "%s (ChromeMethodBFE: %d::%s::%d)", |
| message.c_str(), method, MethodIDToString(method), -error); |
| // TOOD(crbug.com/760362): Map base::File::FILE_ERROR_NOT_FOUND to |
| // Status::NotFound, after fixing LevelDB to handle |
| // the NotFound correctly. |
| return Status::IOError(filename, buf); |
| } |
| |
| Status MakeIOError(Slice filename, |
| const std::string& message, |
| MethodID method) { |
| char buf[512]; |
| base::snprintf(buf, sizeof(buf), "%s (ChromeMethodOnly: %d::%s)", |
| message.c_str(), method, MethodIDToString(method)); |
| return Status::IOError(filename, buf); |
| } |
| |
| ErrorParsingResult ParseMethodAndError(const leveldb::Status& status, |
| MethodID* method_param, |
| base::File::Error* error) { |
| const std::string status_string = status.ToString(); |
| int method; |
| if (RE2::PartialMatch(status_string.c_str(), "ChromeMethodOnly: (\\d+)", |
| &method)) { |
| *method_param = static_cast<MethodID>(method); |
| return METHOD_ONLY; |
| } |
| int parsed_error; |
| if (RE2::PartialMatch(status_string.c_str(), |
| "ChromeMethodBFE: (\\d+)::.*::(\\d+)", &method, |
| &parsed_error)) { |
| *method_param = static_cast<MethodID>(method); |
| *error = static_cast<base::File::Error>(-parsed_error); |
| DCHECK_LT(*error, base::File::FILE_OK); |
| DCHECK_GT(*error, base::File::FILE_ERROR_MAX); |
| return METHOD_AND_BFE; |
| } |
| return NONE; |
| } |
| |
| // Keep in sync with LevelDBCorruptionTypes in histograms.xml. Also, don't |
| // change the order because indices into this array have been recorded in uma |
| // histograms. |
| const char* patterns[] = { |
| "missing files", |
| "log record too small", |
| "corrupted internal key", |
| "partial record", |
| "missing start of fragmented record", |
| "error in middle of record", |
| "unknown record type", |
| "truncated record at end", |
| "bad record length", |
| "VersionEdit", |
| "FileReader invoked with unexpected value", |
| "corrupted key", |
| "CURRENT file does not end with newline", |
| "no meta-nextfile entry", |
| "no meta-lognumber entry", |
| "no last-sequence-number entry", |
| "malformed WriteBatch", |
| "bad WriteBatch Put", |
| "bad WriteBatch Delete", |
| "unknown WriteBatch tag", |
| "WriteBatch has wrong count", |
| "bad entry in block", |
| "bad block contents", |
| "bad block handle", |
| "truncated block read", |
| "block checksum mismatch", |
| "checksum mismatch", |
| "corrupted compressed block contents", |
| "bad block type", |
| "bad magic number", |
| "file is too short", |
| }; |
| |
| // Returns 1-based index into the above array or 0 if nothing matches. |
| int GetCorruptionCode(const leveldb::Status& status) { |
| DCHECK(!status.IsIOError()); |
| DCHECK(!status.ok()); |
| const int kOtherError = 0; |
| int error = kOtherError; |
| const std::string& str_error = status.ToString(); |
| const size_t kNumPatterns = base::size(patterns); |
| for (size_t i = 0; i < kNumPatterns; ++i) { |
| if (str_error.find(patterns[i]) != std::string::npos) { |
| error = i + 1; |
| break; |
| } |
| } |
| return error; |
| } |
| |
| int GetNumCorruptionCodes() { |
| // + 1 for the "other" error that is returned when a corruption message |
| // doesn't match any of the patterns. |
| return base::size(patterns) + 1; |
| } |
| |
| std::string GetCorruptionMessage(const leveldb::Status& status) { |
| int code = GetCorruptionCode(status); |
| if (code == 0) |
| return "Unknown corruption"; |
| return patterns[code - 1]; |
| } |
| |
| bool IndicatesDiskFull(const leveldb::Status& status) { |
| if (status.ok()) |
| return false; |
| leveldb_env::MethodID method; |
| base::File::Error error = base::File::FILE_OK; |
| leveldb_env::ErrorParsingResult result = |
| leveldb_env::ParseMethodAndError(status, &method, &error); |
| return (result == leveldb_env::METHOD_AND_BFE && |
| static_cast<base::File::Error>(error) == |
| base::File::FILE_ERROR_NO_SPACE); |
| } |
| |
| std::string DatabaseNameForRewriteDB(const std::string& original_name) { |
| return original_name + kDatabaseNameSuffixForRebuildDB; |
| } |
| |
| // Given the size of the disk, identified by |disk_size| in bytes, determine the |
| // appropriate write_buffer_size. Ignoring snapshots, if the current set of |
| // tables in a database contains a set of key/value pairs identified by {A}, and |
| // a set of key/value pairs identified by {B} has been written and is in the log |
| // file, then during compaction you will have {A} + {B} + {A, B} = 2A + 2B. |
| // There is no way to know the size of A, so minimizing the size of B will |
| // maximize the likelihood of a successful compaction. |
| size_t WriteBufferSize(int64_t disk_size) { |
| const leveldb_env::Options default_options; |
| const int64_t kMinBufferSize = 1024 * 1024; |
| const int64_t kMaxBufferSize = default_options.write_buffer_size; |
| const int64_t kDiskMinBuffSize = 10 * 1024 * 1024; |
| const int64_t kDiskMaxBuffSize = 40 * 1024 * 1024; |
| |
| if (disk_size == -1) |
| return default_options.write_buffer_size; |
| |
| if (disk_size <= kDiskMinBuffSize) |
| return kMinBufferSize; |
| |
| if (disk_size >= kDiskMaxBuffSize) |
| return kMaxBufferSize; |
| |
| // A linear equation to intersect (kDiskMinBuffSize, kMinBufferSize) and |
| // (kDiskMaxBuffSize, kMaxBufferSize). |
| return static_cast<size_t>( |
| kMinBufferSize + |
| ((kMaxBufferSize - kMinBufferSize) * (disk_size - kDiskMinBuffSize)) / |
| (kDiskMaxBuffSize - kDiskMinBuffSize)); |
| } |
| |
| ChromiumEnv::ChromiumEnv() : ChromiumEnv("LevelDBEnv") {} |
| |
| ChromiumEnv::ChromiumEnv(std::unique_ptr<storage::FilesystemProxy> filesystem) |
| : ChromiumEnv("LevelDBEnv", std::move(filesystem)) {} |
| |
| ChromiumEnv::ChromiumEnv(const std::string& name) |
| : ChromiumEnv(name, |
| std::make_unique<storage::FilesystemProxy>( |
| storage::FilesystemProxy::UNRESTRICTED, |
| base::FilePath())) {} |
| |
| ChromiumEnv::ChromiumEnv(const std::string& name, |
| std::unique_ptr<storage::FilesystemProxy> filesystem) |
| : filesystem_(std::move(filesystem)), name_(name) { |
| DCHECK(filesystem_); |
| |
| size_t max_open_files = base::GetMaxFds(); |
| if (max_open_files < kFileLimitToDisableEviction) { |
| file_cache_.reset( |
| leveldb::NewLRUCache(GetLevelDBFileLimit(max_open_files))); |
| } |
| } |
| |
| ChromiumEnv::~ChromiumEnv() { |
| // In chromium, ChromiumEnv is leaked. It'd be nice to add NOTREACHED here to |
| // ensure that behavior isn't accidentally changed, but there's an instance in |
| // a unit test that is deleted. |
| } |
| |
| bool ChromiumEnv::FileExists(const std::string& fname) { |
| return filesystem_->PathExists(FilePath::FromUTF8Unsafe(fname)); |
| } |
| |
| const char* ChromiumEnv::FileErrorString(base::File::Error error) { |
| switch (error) { |
| case base::File::FILE_ERROR_FAILED: |
| return "No further details."; |
| case base::File::FILE_ERROR_IN_USE: |
| return "File currently in use."; |
| case base::File::FILE_ERROR_EXISTS: |
| return "File already exists."; |
| case base::File::FILE_ERROR_NOT_FOUND: |
| return "File not found."; |
| case base::File::FILE_ERROR_ACCESS_DENIED: |
| return "Access denied."; |
| case base::File::FILE_ERROR_TOO_MANY_OPENED: |
| return "Too many files open."; |
| case base::File::FILE_ERROR_NO_MEMORY: |
| return "Out of memory."; |
| case base::File::FILE_ERROR_NO_SPACE: |
| return "No space left on drive."; |
| case base::File::FILE_ERROR_NOT_A_DIRECTORY: |
| return "Not a directory."; |
| case base::File::FILE_ERROR_INVALID_OPERATION: |
| return "Invalid operation."; |
| case base::File::FILE_ERROR_SECURITY: |
| return "Security error."; |
| case base::File::FILE_ERROR_ABORT: |
| return "File operation aborted."; |
| case base::File::FILE_ERROR_NOT_A_FILE: |
| return "The supplied path was not a file."; |
| case base::File::FILE_ERROR_NOT_EMPTY: |
| return "The file was not empty."; |
| case base::File::FILE_ERROR_INVALID_URL: |
| return "Invalid URL."; |
| case base::File::FILE_ERROR_IO: |
| return "OS or hardware error."; |
| case base::File::FILE_OK: |
| return "OK."; |
| case base::File::FILE_ERROR_MAX: |
| NOTREACHED(); |
| } |
| NOTIMPLEMENTED(); |
| return "Unknown error."; |
| } |
| |
| // Delete unused table backup files - a feature no longer supported. |
| // TODO(cmumford): Delete this function once found backup files drop below some |
| // very small (TBD) number. |
| void ChromiumEnv::RemoveBackupFiles(const FilePath& dir) { |
| base::HistogramBase* histogram = base::BooleanHistogram::FactoryGet( |
| "LevelDBEnv.DeleteTableBackupFile", |
| base::Histogram::kUmaTargetedHistogramFlag); |
| |
| FileErrorOr<std::vector<base::FilePath>> result = |
| filesystem_->GetDirectoryEntries( |
| dir, storage::FilesystemProxy::DirectoryEntryType::kFilesOnly); |
| if (result.is_error()) |
| return; |
| |
| for (const auto& path : result.value()) { |
| if (path.Extension() == FILE_PATH_LITERAL(".bak")) |
| histogram->AddBoolean(filesystem_->DeleteFile(path)); |
| } |
| } |
| |
| // Test must call this *before* opening any random-access files. |
| void ChromiumEnv::SetReadOnlyFileLimitForTesting(int max_open_files) { |
| DCHECK(!file_cache_ || file_cache_->TotalCharge() == 0); |
| file_cache_.reset(leveldb::NewLRUCache(max_open_files)); |
| } |
| |
| Status ChromiumEnv::GetChildren(const std::string& dir, |
| std::vector<std::string>* result) { |
| FilePath dir_path = FilePath::FromUTF8Unsafe(dir); |
| RemoveBackupFiles(dir_path); |
| |
| FileErrorOr<std::vector<FilePath>> entries_result = |
| filesystem_->GetDirectoryEntries( |
| dir_path, |
| storage::FilesystemProxy::DirectoryEntryType::kFilesAndDirectories); |
| if (entries_result.is_error()) { |
| return MakeIOError(dir, "Could not open/read directory", kGetChildren, |
| entries_result.error()); |
| } |
| |
| result->clear(); |
| for (const auto& entry : entries_result.value()) |
| result->push_back(entry.BaseName().AsUTF8Unsafe()); |
| |
| return Status::OK(); |
| } |
| |
| Status ChromiumEnv::RemoveFile(const std::string& fname) { |
| Status result; |
| FilePath fname_filepath = FilePath::FromUTF8Unsafe(fname); |
| if (!filesystem_->DeleteFile(fname_filepath)) { |
| result = MakeIOError(fname, "Could not delete file.", kRemoveFile); |
| } |
| return result; |
| } |
| |
| Status ChromiumEnv::CreateDir(const std::string& name) { |
| Status result; |
| base::File::Error error = base::File::FILE_OK; |
| Retrier retrier; |
| do { |
| error = filesystem_->CreateDirectory(base::FilePath::FromUTF8Unsafe(name)); |
| if (error == base::File::FILE_OK) |
| return result; |
| } while (retrier.ShouldKeepTrying()); |
| return MakeIOError(name, "Could not create directory.", kCreateDir, error); |
| } |
| |
| Status ChromiumEnv::RemoveDir(const std::string& name) { |
| Status result; |
| if (!filesystem_->DeleteFile(FilePath::FromUTF8Unsafe(name))) { |
| result = MakeIOError(name, "Could not delete directory.", kRemoveDir); |
| } |
| return result; |
| } |
| |
| Status ChromiumEnv::GetFileSize(const std::string& fname, uint64_t* size) { |
| Status s; |
| base::Optional<base::File::Info> info = |
| filesystem_->GetFileInfo(base::FilePath::FromUTF8Unsafe(fname)); |
| if (!info) { |
| *size = 0; |
| s = MakeIOError(fname, "Could not determine file size.", kGetFileSize); |
| } else { |
| *size = static_cast<uint64_t>(info->size); |
| } |
| return s; |
| } |
| |
| Status ChromiumEnv::RenameFile(const std::string& src, const std::string& dst) { |
| Status result; |
| FilePath src_file_path = FilePath::FromUTF8Unsafe(src); |
| if (!filesystem_->PathExists(src_file_path)) |
| return result; |
| FilePath destination = FilePath::FromUTF8Unsafe(dst); |
| |
| Retrier retrier; |
| base::File::Error error = base::File::FILE_OK; |
| do { |
| error = filesystem_->RenameFile(src_file_path, destination); |
| if (error == base::File::FILE_OK) |
| return result; |
| } while (retrier.ShouldKeepTrying()); |
| |
| DCHECK(error != base::File::FILE_OK); |
| char buf[100]; |
| base::snprintf(buf, |
| sizeof(buf), |
| "Could not rename file: %s", |
| FileErrorString(error)); |
| return MakeIOError(src, buf, kRenameFile, error); |
| } |
| |
| Status ChromiumEnv::LockFile(const std::string& fname, FileLock** lock) { |
| *lock = nullptr; |
| Status result; |
| const base::FilePath path = base::FilePath::FromUTF8Unsafe(fname); |
| Retrier retrier; |
| FileErrorOr<std::unique_ptr<storage::FilesystemProxy::FileLock>> lock_result; |
| do { |
| lock_result = filesystem_->LockFile(path); |
| } while (lock_result.is_error() && retrier.ShouldKeepTrying()); |
| if (lock_result.is_error()) { |
| return MakeIOError(fname, FileErrorString(lock_result.error()), kLockFile, |
| lock_result.error()); |
| } |
| |
| *lock = new ChromiumFileLock(std::move(lock_result.value()), fname); |
| return result; |
| } |
| |
| Status ChromiumEnv::UnlockFile(FileLock* lock) { |
| std::unique_ptr<ChromiumFileLock> my_lock( |
| reinterpret_cast<ChromiumFileLock*>(lock)); |
| Status result = Status::OK(); |
| |
| base::File::Error error_code = my_lock->lock->Release(); |
| if (error_code != base::File::FILE_OK) { |
| result = |
| MakeIOError(my_lock->name, "Could not unlock lock file.", kUnlockFile); |
| } |
| return result; |
| } |
| |
| Status ChromiumEnv::GetTestDirectory(std::string* path) { |
| mu_.Acquire(); |
| if (test_directory_.empty()) { |
| if (!base::CreateNewTempDirectory(kLevelDBTestDirectoryPrefix, |
| &test_directory_)) { |
| mu_.Release(); |
| return MakeIOError( |
| "Could not create temp directory.", "", kGetTestDirectory); |
| } |
| } |
| *path = test_directory_.AsUTF8Unsafe(); |
| mu_.Release(); |
| return Status::OK(); |
| } |
| |
| Status ChromiumEnv::NewLogger(const std::string& fname, |
| leveldb::Logger** result) { |
| FilePath path = FilePath::FromUTF8Unsafe(fname); |
| FileErrorOr<base::File> open_result = filesystem_->OpenFile( |
| path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| if (open_result.is_error()) { |
| *result = nullptr; |
| return MakeIOError(fname, "Unable to create log file", kNewLogger, |
| open_result.error()); |
| } else { |
| *result = new leveldb::ChromiumLogger(std::move(open_result.value())); |
| return Status::OK(); |
| } |
| } |
| |
| Status ChromiumEnv::NewSequentialFile(const std::string& fname, |
| leveldb::SequentialFile** result) { |
| FilePath path = FilePath::FromUTF8Unsafe(fname); |
| FileErrorOr<base::File> open_result = filesystem_->OpenFile( |
| path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| if (open_result.is_error()) { |
| *result = nullptr; |
| return MakeIOError(fname, "Unable to create sequential file", |
| kNewSequentialFile, open_result.error()); |
| } else { |
| *result = new ChromiumSequentialFile(fname, std::move(open_result.value())); |
| return Status::OK(); |
| } |
| } |
| |
| Status ChromiumEnv::NewRandomAccessFile(const std::string& fname, |
| leveldb::RandomAccessFile** result) { |
| base::FilePath file_path = FilePath::FromUTF8Unsafe(fname); |
| FileErrorOr<base::File> open_result = filesystem_->OpenFile( |
| file_path, base::File::FLAG_READ | base::File::FLAG_OPEN); |
| if (!open_result.is_error()) { |
| base::File file = std::move(open_result.value()); |
| if (file_cache_) { |
| *result = new ChromiumEvictableRandomAccessFile( |
| std::move(file_path), std::move(file), filesystem_.get(), |
| file_cache_.get()); |
| } else { |
| *result = |
| new ChromiumRandomAccessFile(std::move(file_path), std::move(file)); |
| } |
| return Status::OK(); |
| } |
| *result = nullptr; |
| return MakeIOError(fname, FileErrorString(open_result.error()), |
| kNewRandomAccessFile, open_result.error()); |
| } |
| |
| Status ChromiumEnv::NewWritableFile(const std::string& fname, |
| leveldb::WritableFile** result) { |
| FilePath path = FilePath::FromUTF8Unsafe(fname); |
| FileErrorOr<base::File> open_result = filesystem_->OpenFile( |
| path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| if (open_result.is_error()) { |
| *result = nullptr; |
| return MakeIOError(fname, "Unable to create writable file", |
| kNewWritableFile, open_result.error()); |
| } |
| *result = new ChromiumWritableFile(fname, std::move(open_result.value()), |
| filesystem_.get()); |
| return Status::OK(); |
| } |
| |
| Status ChromiumEnv::NewAppendableFile(const std::string& fname, |
| leveldb::WritableFile** result) { |
| FilePath path = FilePath::FromUTF8Unsafe(fname); |
| FileErrorOr<base::File> open_result = filesystem_->OpenFile( |
| path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND); |
| if (open_result.is_error()) { |
| *result = nullptr; |
| return MakeIOError(fname, "Unable to create appendable file", |
| kNewAppendableFile, open_result.error()); |
| } |
| *result = new ChromiumWritableFile(fname, std::move(open_result.value()), |
| filesystem_.get()); |
| return Status::OK(); |
| } |
| |
| uint64_t ChromiumEnv::NowMicros() { |
| return base::TimeTicks::Now().ToInternalValue(); |
| } |
| |
| void ChromiumEnv::SleepForMicroseconds(int micros) { |
| // Round up to the next millisecond. |
| base::PlatformThread::Sleep(base::TimeDelta::FromMicroseconds(micros)); |
| } |
| |
| class Thread : public base::PlatformThread::Delegate { |
| public: |
| Thread(void (*function)(void* arg), void* arg) |
| : function_(function), arg_(arg) { |
| base::PlatformThreadHandle handle; |
| bool success = base::PlatformThread::Create(0, this, &handle); |
| DCHECK(success); |
| } |
| virtual ~Thread() {} |
| void ThreadMain() override { |
| (*function_)(arg_); |
| delete this; |
| } |
| |
| private: |
| void (*function_)(void* arg); |
| void* arg_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Thread); |
| }; |
| |
| void ChromiumEnv::Schedule(ScheduleFunc* function, void* arg) { |
| // The BLOCK_SHUTDOWN is required to avoid shutdown hangs. The scheduled |
| // tasks may be blocking foreground threads waiting for their completions. |
| // see: https://crbug.com/1086185. |
| base::ThreadPool::PostTask(FROM_HERE, |
| {base::MayBlock(), base::WithBaseSyncPrimitives(), |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}, |
| base::BindOnce(function, arg)); |
| } |
| |
| void ChromiumEnv::StartThread(void (*function)(void* arg), void* arg) { |
| new Thread(function, arg); // Will self-delete. |
| } |
| |
| LevelDBStatusValue GetLevelDBStatusUMAValue(const leveldb::Status& s) { |
| if (s.ok()) |
| return LEVELDB_STATUS_OK; |
| if (s.IsNotFound()) |
| return LEVELDB_STATUS_NOT_FOUND; |
| if (s.IsCorruption()) |
| return LEVELDB_STATUS_CORRUPTION; |
| if (s.IsNotSupportedError()) |
| return LEVELDB_STATUS_NOT_SUPPORTED; |
| if (s.IsIOError()) |
| return LEVELDB_STATUS_IO_ERROR; |
| // TODO(cmumford): IsInvalidArgument() was just added to leveldb. Use this |
| // function once that change goes to the public repository. |
| return LEVELDB_STATUS_INVALID_ARGUMENT; |
| } |
| |
| // Forwards all calls to the underlying leveldb::DB instance. |
| // Adds / removes itself in the DBTracker it's created with. |
| class DBTracker::TrackedDBImpl : public base::LinkNode<TrackedDBImpl>, |
| public TrackedDB { |
| public: |
| TrackedDBImpl(DBTracker* tracker, |
| const std::string name, |
| leveldb::DB* db, |
| const leveldb::Cache* block_cache, |
| DatabaseErrorReportingCallback on_get_error, |
| DatabaseErrorReportingCallback on_write_error) |
| : tracker_(tracker), |
| name_(name), |
| db_(db), |
| on_get_error_(std::move(on_get_error)), |
| on_write_error_(std::move(on_write_error)) { |
| if (leveldb_chrome::GetSharedWebBlockCache() == |
| leveldb_chrome::GetSharedBrowserBlockCache()) { |
| shared_read_cache_use_ = SharedReadCacheUse_Unified; |
| } else if (block_cache == leveldb_chrome::GetSharedBrowserBlockCache()) { |
| shared_read_cache_use_ = SharedReadCacheUse_Browser; |
| } else if (block_cache == leveldb_chrome::GetSharedWebBlockCache()) { |
| shared_read_cache_use_ = SharedReadCacheUse_Web; |
| } else if (block_cache == leveldb_chrome::GetSharedInMemoryBlockCache()) { |
| shared_read_cache_use_ = SharedReadCacheUse_InMemory; |
| } else { |
| NOTREACHED(); |
| } |
| tracker_->DatabaseOpened(this, shared_read_cache_use_); |
| } |
| |
| ~TrackedDBImpl() override { |
| tracker_->DatabaseDestroyed(this, shared_read_cache_use_); |
| base::ScopedAllowBaseSyncPrimitives allow_base_sync_primitives; |
| db_.reset(); |
| } |
| |
| const std::string& name() const override { return name_; } |
| |
| SharedReadCacheUse block_cache_type() const override { |
| return shared_read_cache_use_; |
| } |
| |
| leveldb::Status Put(const leveldb::WriteOptions& options, |
| const leveldb::Slice& key, |
| const leveldb::Slice& value) override { |
| return db_->Put(options, key, value); |
| } |
| |
| leveldb::Status Delete(const leveldb::WriteOptions& options, |
| const leveldb::Slice& key) override { |
| return db_->Delete(options, key); |
| } |
| |
| leveldb::Status Write(const leveldb::WriteOptions& options, |
| leveldb::WriteBatch* updates) override { |
| leveldb::Status status = db_->Write(options, updates); |
| if (LIKELY(status.ok())) |
| return status; |
| if (on_write_error_) |
| on_write_error_.Run(status); |
| return status; |
| } |
| |
| leveldb::Status Get(const leveldb::ReadOptions& options, |
| const leveldb::Slice& key, |
| std::string* value) override { |
| leveldb::Status status = db_->Get(options, key, value); |
| if (LIKELY(status.ok() || status.IsNotFound())) |
| return status; |
| if (on_get_error_) |
| on_get_error_.Run(status); |
| return status; |
| } |
| |
| const leveldb::Snapshot* GetSnapshot() override { return db_->GetSnapshot(); } |
| |
| void ReleaseSnapshot(const leveldb::Snapshot* snapshot) override { |
| return db_->ReleaseSnapshot(snapshot); |
| } |
| |
| bool GetProperty(const leveldb::Slice& property, |
| std::string* value) override { |
| return db_->GetProperty(property, value); |
| } |
| |
| void GetApproximateSizes(const leveldb::Range* range, |
| int n, |
| uint64_t* sizes) override { |
| return db_->GetApproximateSizes(range, n, sizes); |
| } |
| |
| void CompactRange(const leveldb::Slice* begin, |
| const leveldb::Slice* end) override { |
| return db_->CompactRange(begin, end); |
| } |
| |
| leveldb::Iterator* NewIterator(const leveldb::ReadOptions& options) override { |
| return db_->NewIterator(options); |
| } |
| |
| private: |
| DBTracker* tracker_; |
| std::string name_; |
| std::unique_ptr<leveldb::DB> db_; |
| SharedReadCacheUse shared_read_cache_use_; |
| const DatabaseErrorReportingCallback on_get_error_; |
| const DatabaseErrorReportingCallback on_write_error_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TrackedDBImpl); |
| }; |
| |
| // Reports live databases and in-memory env's to memory-infra. For each live |
| // database the following information is reported: |
| // 1. Instance pointer (to disambiguate databases). |
| // 2. Memory taken by the database, with the shared cache being attributed |
| // equally to each database sharing 3. The name of the database (when not in |
| // BACKGROUND mode to avoid exposing |
| // PIIs in slow reports). |
| // |
| // Example report (as seen after clicking "leveldatabase" in "Overview" pane |
| // in Chrome tracing UI): |
| // |
| // Component effective_size size name |
| // --------------------------------------------------------------------------- |
| // leveldatabase 390 KiB 490 KiB |
| // db_0x7FE70F2040A0 100 KiB 100 KiB Users/.../Sync |
| // block_cache (browser) 40 KiB 40 KiB |
| // db_0x7FE70F530D80 150 KiB 150 KiB Users/.../Data Proxy |
| // block_cache (web) 30 KiB 30 KiB |
| // db_0x7FE70F530D80 140 KiB 140 KiB Users/.../Extensions |
| // block_cache (web) 30 KiB 30 KiB |
| // block_cache 0 KiB 100 KiB |
| // browser 0 KiB 40 KiB |
| // web 0 KiB 60 KiB |
| // memenv_0x7FE80F2040A0 4 KiB 4 KiB |
| // memenv_0x7FE80F3040A0 4 KiB 4 KiB |
| // |
| class DBTracker::MemoryDumpProvider |
| : public base::trace_event::MemoryDumpProvider { |
| public: |
| void DumpAllDatabases(ProcessMemoryDump* pmd); |
| |
| bool OnMemoryDump(const MemoryDumpArgs& args, |
| ProcessMemoryDump* pmd) override { |
| DumpAllDatabases(pmd); |
| return true; |
| } |
| |
| void DatabaseOpened(const TrackedDBImpl* database) { |
| database_use_count_[database->block_cache_type()]++; |
| } |
| |
| void DatabaseDestroyed(const TrackedDBImpl* database) { |
| database_use_count_[database->block_cache_type()]--; |
| DCHECK_GE(database_use_count_[database->block_cache_type()], 0); |
| } |
| |
| private: |
| void DumpVisitor(ProcessMemoryDump* pmd, TrackedDB* db); |
| |
| int database_use_count_[SharedReadCacheUse_NumCacheUses] = {}; |
| }; |
| |
| void DBTracker::MemoryDumpProvider::DumpAllDatabases(ProcessMemoryDump* pmd) { |
| if (pmd->GetAllocatorDump("leveldatabase")) |
| return; |
| pmd->CreateAllocatorDump("leveldatabase"); |
| |
| const auto* browser_cache = leveldb_chrome::GetSharedBrowserBlockCache(); |
| const auto* web_cache = leveldb_chrome::GetSharedWebBlockCache(); |
| if (browser_cache == web_cache) { |
| RecordCacheUsageInTracing(pmd, SharedReadCacheUse_Unified); |
| } else { |
| RecordCacheUsageInTracing(pmd, SharedReadCacheUse_Browser); |
| RecordCacheUsageInTracing(pmd, SharedReadCacheUse_Web); |
| } |
| RecordCacheUsageInTracing(pmd, SharedReadCacheUse_InMemory); |
| |
| DBTracker::GetInstance()->VisitDatabases( |
| base::BindRepeating(&DBTracker::MemoryDumpProvider::DumpVisitor, |
| base::Unretained(this), base::Unretained(pmd))); |
| leveldb_chrome::DumpAllTrackedEnvs(pmd); |
| } |
| |
| void DBTracker::MemoryDumpProvider::DumpVisitor(ProcessMemoryDump* pmd, |
| TrackedDB* db) { |
| std::string db_dump_name = GetDumpNameForDB(db); |
| |
| auto* db_cache_dump = pmd->CreateAllocatorDump(db_dump_name + "/block_cache"); |
| const std::string cache_dump_name = |
| GetDumpNameForCache(db->block_cache_type()); |
| pmd->AddSuballocation(db_cache_dump->guid(), cache_dump_name); |
| size_t cache_usage = |
| pmd->GetAllocatorDump(cache_dump_name)->GetSizeInternal(); |
| // The |database_use_count_| can be accessed by the visitor because the |
| // visitor is called holding the lock at DBTracker. |
| size_t cache_usage_pss = |
| cache_usage / database_use_count_[db->block_cache_type()]; |
| db_cache_dump->AddScalar(MemoryAllocatorDump::kNameSize, |
| MemoryAllocatorDump::kUnitsBytes, cache_usage_pss); |
| |
| auto* db_dump = pmd->CreateAllocatorDump(db_dump_name); |
| uint64_t total_usage = 0; |
| std::string usage_string; |
| bool success = |
| db->GetProperty("leveldb.approximate-memory-usage", &usage_string) && |
| base::StringToUint64(usage_string, &total_usage); |
| DCHECK(success); |
| db_dump->AddScalar(MemoryAllocatorDump::kNameSize, |
| MemoryAllocatorDump::kUnitsBytes, |
| total_usage - cache_usage + cache_usage_pss); |
| |
| if (pmd->dump_args().level_of_detail != |
| base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND) { |
| db_dump->AddString("name", "", db->name()); |
| } |
| } |
| |
| DBTracker::DBTracker() : mdp_(new MemoryDumpProvider()) { |
| base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( |
| mdp_.get(), "LevelDB", nullptr); |
| } |
| |
| DBTracker::~DBTracker() { |
| NOTREACHED(); // DBTracker is a singleton |
| } |
| |
| // static |
| DBTracker* DBTracker::GetInstance() { |
| static DBTracker* instance = new DBTracker(); |
| return instance; |
| } |
| |
| // static |
| MemoryAllocatorDump* DBTracker::GetOrCreateAllocatorDump( |
| ProcessMemoryDump* pmd, |
| leveldb::DB* tracked_db) { |
| DCHECK(GetInstance()->IsTrackedDB(tracked_db)) |
| << std::hex << tracked_db << " is not tracked"; |
| |
| // Create dumps for all databases to make sure the shared cache is equally |
| // attributed to each database sharing it. |
| GetInstance()->mdp_->DumpAllDatabases(pmd); |
| return pmd->GetAllocatorDump(GetDumpNameForDB(tracked_db)); |
| } |
| |
| // static |
| MemoryAllocatorDump* DBTracker::GetOrCreateAllocatorDump( |
| ProcessMemoryDump* pmd, |
| leveldb::Env* tracked_memenv) { |
| GetInstance()->mdp_->DumpAllDatabases(pmd); |
| return leveldb_chrome::GetEnvAllocatorDump(pmd, tracked_memenv); |
| } |
| |
| bool DBTracker::IsTrackedDB(const leveldb::DB* db) const { |
| base::AutoLock lock(databases_lock_); |
| for (auto* i = databases_.head(); i != databases_.end(); i = i->next()) { |
| if (i->value() == db) |
| return true; |
| } |
| return false; |
| } |
| |
| leveldb::Status DBTracker::OpenDatabase(const leveldb_env::Options& options, |
| const std::string& name, |
| TrackedDB** dbptr) { |
| leveldb::DB* db = nullptr; |
| auto status = leveldb::DB::Open(options, name, &db); |
| // Enforce expectations: either we succeed, and get a valid object in |db|, |
| // or we fail, and |db| is still NULL. |
| CHECK((status.ok() && db) || (!status.ok() && !db)); |
| if (status.ok()) { |
| // TrackedDBImpl ctor adds the instance to the tracker. |
| *dbptr = new TrackedDBImpl(GetInstance(), name, db, options.block_cache, |
| std::move(options.on_get_error), |
| std::move(options.on_write_error)); |
| } |
| return status; |
| } |
| |
| void DBTracker::VisitDatabases(const DatabaseVisitor& visitor) { |
| base::AutoLock lock(databases_lock_); |
| for (auto* i = databases_.head(); i != databases_.end(); i = i->next()) |
| visitor.Run(i->value()); |
| } |
| |
| void DBTracker::DatabaseOpened(TrackedDBImpl* database, |
| SharedReadCacheUse cache_use) { |
| base::AutoLock lock(databases_lock_); |
| databases_.Append(database); |
| mdp_->DatabaseOpened(database); |
| } |
| |
| void DBTracker::DatabaseDestroyed(TrackedDBImpl* database, |
| SharedReadCacheUse cache_use) { |
| base::AutoLock lock(databases_lock_); |
| mdp_->DatabaseDestroyed(database); |
| database->RemoveFromList(); |
| } |
| |
| leveldb::Status OpenDB(const leveldb_env::Options& options, |
| const std::string& name, |
| std::unique_ptr<leveldb::DB>* dbptr) { |
| // For UMA logging purposes we need the block cache to be created outside of |
| // leveldb so that the size can be logged and it can be pruned. |
| DCHECK(options.block_cache != nullptr); |
| DBTracker::TrackedDB* tracked_db = nullptr; |
| leveldb::Status s; |
| if (options.env && leveldb_chrome::IsMemEnv(options.env)) { |
| Options mem_options = options; |
| mem_options.block_cache = leveldb_chrome::GetSharedInMemoryBlockCache(); |
| mem_options.write_buffer_size = 0; // minimum size. |
| s = DBTracker::GetInstance()->OpenDatabase(mem_options, name, &tracked_db); |
| } else { |
| std::string tmp_name = DatabaseNameForRewriteDB(name); |
| // If Chrome crashes during rewrite, there might be a temporary db but |
| // no actual db. |
| if (options.env->FileExists(tmp_name) && |
| !options.env->FileExists(name + "/CURRENT")) { |
| s = leveldb::DestroyDB(name, options); |
| if (!s.ok()) |
| return s; |
| s = options.env->RenameFile(tmp_name, name); |
| if (!s.ok()) |
| return s; |
| } |
| s = DBTracker::GetInstance()->OpenDatabase(options, name, &tracked_db); |
| // It is possible that the database was partially deleted during a |
| // rewrite and can't be opened anymore. |
| if (!s.ok() && options.env->FileExists(tmp_name)) { |
| s = leveldb::DestroyDB(name, options); |
| if (!s.ok()) |
| return s; |
| s = options.env->RenameFile(tmp_name, name); |
| if (!s.ok()) |
| return s; |
| s = DBTracker::GetInstance()->OpenDatabase(options, name, &tracked_db); |
| } |
| // There might be a temporary database that needs to be cleaned up. |
| if (options.env->FileExists(tmp_name)) { |
| leveldb::DestroyDB(tmp_name, options); |
| } |
| } |
| if (s.ok()) |
| dbptr->reset(tracked_db); |
| return s; |
| } |
| |
| leveldb::Status RewriteDB(const leveldb_env::Options& options, |
| const std::string& name, |
| std::unique_ptr<leveldb::DB>* dbptr) { |
| DCHECK(options.create_if_missing); |
| if (!base::FeatureList::IsEnabled(leveldb::kLevelDBRewriteFeature)) |
| return Status::OK(); |
| if (leveldb_chrome::IsMemEnv(options.env)) |
| return Status::OK(); |
| TRACE_EVENT1("leveldb", "ChromiumEnv::RewriteDB", "name", name); |
| leveldb::Status s; |
| std::string tmp_name = DatabaseNameForRewriteDB(name); |
| if (options.env->FileExists(tmp_name)) { |
| s = leveldb::DestroyDB(tmp_name, options); |
| if (!s.ok()) |
| return s; |
| } |
| // Copy all data from *dbptr to a temporary db. |
| std::unique_ptr<leveldb::DB> tmp_db; |
| s = leveldb_env::OpenDB(options, tmp_name, &tmp_db); |
| if (!s.ok()) |
| return s; |
| std::unique_ptr<leveldb::Iterator> it( |
| (*dbptr)->NewIterator(leveldb::ReadOptions())); |
| for (it->SeekToFirst(); it->Valid(); it->Next()) { |
| s = tmp_db->Put(leveldb::WriteOptions(), it->key(), it->value()); |
| if (!s.ok()) |
| break; |
| } |
| it.reset(); |
| tmp_db.reset(); |
| if (!s.ok()) { |
| leveldb::DestroyDB(tmp_name, options); |
| return s; |
| } |
| // Replace the old database with tmp_db. |
| (*dbptr).reset(); |
| s = leveldb::DestroyDB(name, options); |
| if (!s.ok()) |
| return s; |
| s = options.env->RenameFile(tmp_name, name); |
| if (!s.ok()) |
| return s; |
| return leveldb_env::OpenDB(options, name, dbptr); |
| } |
| |
| base::StringPiece MakeStringPiece(const leveldb::Slice& s) { |
| return base::StringPiece(s.data(), s.size()); |
| } |
| |
| leveldb::Slice MakeSlice(const base::StringPiece& s) { |
| return leveldb::Slice(s.begin(), s.size()); |
| } |
| |
| leveldb::Slice MakeSlice(base::span<const uint8_t> s) { |
| return MakeSlice( |
| base::StringPiece(reinterpret_cast<const char*>(s.data()), s.size())); |
| } |
| |
| } // namespace leveldb_env |
| |
| namespace leveldb { |
| |
| Env* Env::Default() { |
| static base::NoDestructor<leveldb_env::ChromiumEnv> default_env; |
| return default_env.get(); |
| } |
| |
| } // namespace leveldb |