// 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"
#if defined(OS_WIN)
#include <io.h>
#endif
#include "base/debug/trace_event.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/metrics/histogram.h"
#include "base/strings/utf_string_conversions.h"
#include "third_party/leveldatabase/env_chromium_stdio.h"
#include "third_party/re2/re2/re2.h"
#if defined(OS_WIN)
#include "base/command_line.h"
#include "base/win/win_util.h"
#include "third_party/leveldatabase/env_chromium_win.h"
#endif
using leveldb::FileLock;
using leveldb::Slice;
using leveldb::Status;
namespace leveldb_env {
namespace {
const base::FilePath::CharType backup_table_extension[] =
FILE_PATH_LITERAL(".bak");
const base::FilePath::CharType table_extension[] = FILE_PATH_LITERAL(".ldb");
static const base::FilePath::CharType kLevelDBTestDirectoryPrefix[]
= FILE_PATH_LITERAL("leveldb-test-");
class ChromiumFileLock : public FileLock {
public:
::base::File file_;
std::string name_;
};
class Retrier {
public:
Retrier(MethodID method, RetrierProvider* provider)
: start_(base::TimeTicks::Now()),
limit_(start_ + base::TimeDelta::FromMilliseconds(
provider->MaxRetryTimeMillis())),
last_(start_),
time_to_sleep_(base::TimeDelta::FromMilliseconds(10)),
success_(true),
method_(method),
last_error_(base::File::FILE_OK),
provider_(provider) {}
~Retrier() {
if (success_) {
provider_->GetRetryTimeHistogram(method_)->AddTime(last_ - start_);
if (last_error_ != base::File::FILE_OK) {
DCHECK_LT(last_error_, 0);
provider_->GetRecoveredFromErrorHistogram(method_)->Add(-last_error_);
}
}
}
bool ShouldKeepTrying(base::File::Error last_error) {
DCHECK_NE(last_error, base::File::FILE_OK);
last_error_ = last_error;
if (last_ < limit_) {
base::PlatformThread::Sleep(time_to_sleep_);
last_ = base::TimeTicks::Now();
return true;
}
success_ = false;
return false;
}
private:
base::TimeTicks start_;
base::TimeTicks limit_;
base::TimeTicks last_;
base::TimeDelta time_to_sleep_;
bool success_;
MethodID method_;
base::File::Error last_error_;
RetrierProvider* provider_;
};
class IDBEnvStdio : public ChromiumEnvStdio {
public:
IDBEnvStdio() : ChromiumEnvStdio() {
name_ = "LevelDBEnv.IDB";
make_backup_ = true;
}
};
#if defined(OS_WIN)
class IDBEnvWin : public ChromiumEnvWin {
public:
IDBEnvWin() : ChromiumEnvWin() {
name_ = "LevelDBEnv.IDB";
make_backup_ = true;
}
};
#endif
#if defined(OS_WIN)
::base::LazyInstance<IDBEnvWin>::Leaky idb_env =
LAZY_INSTANCE_INITIALIZER;
#else
::base::LazyInstance<IDBEnvStdio>::Leaky idb_env =
LAZY_INSTANCE_INITIALIZER;
#endif
::base::LazyInstance<ChromiumEnvStdio>::Leaky default_env =
LAZY_INSTANCE_INITIALIZER;
} // unnamed namespace
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 kDeleteFile:
return "DeleteFile";
case kCreateDir:
return "CreateDir";
case kDeleteDir:
return "DeleteDir";
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 kNumEntries:
NOTREACHED();
return "kNumEntries";
}
NOTREACHED();
return "Unknown";
}
Status MakeIOError(Slice filename,
const char* message,
MethodID method,
int saved_errno) {
char buf[512];
snprintf(buf,
sizeof(buf),
"%s (ChromeMethodErrno: %d::%s::%d)",
message,
method,
MethodIDToString(method),
saved_errno);
return Status::IOError(filename, buf);
}
Status MakeIOError(Slice filename,
const char* message,
MethodID method,
base::File::Error error) {
DCHECK_LT(error, 0);
char buf[512];
snprintf(buf,
sizeof(buf),
"%s (ChromeMethodPFE: %d::%s::%d)",
message,
method,
MethodIDToString(method),
-error);
return Status::IOError(filename, buf);
}
Status MakeIOError(Slice filename, const char* message, MethodID method) {
char buf[512];
snprintf(buf,
sizeof(buf),
"%s (ChromeMethodOnly: %d::%s)",
message,
method,
MethodIDToString(method));
return Status::IOError(filename, buf);
}
ErrorParsingResult ParseMethodAndError(const char* string,
MethodID* method_param,
int* error) {
int method;
if (RE2::PartialMatch(string, "ChromeMethodOnly: (\\d+)", &method)) {
*method_param = static_cast<MethodID>(method);
return METHOD_ONLY;
}
if (RE2::PartialMatch(
string, "ChromeMethodPFE: (\\d+)::.*::(\\d+)", &method, error)) {
*error = -*error;
*method_param = static_cast<MethodID>(method);
return METHOD_AND_PFE;
}
if (RE2::PartialMatch(
string, "ChromeMethodErrno: (\\d+)::.*::(\\d+)", &method, error)) {
*method_param = static_cast<MethodID>(method);
return METHOD_AND_ERRNO;
}
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 = arraysize(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 arraysize(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;
int error = -1;
leveldb_env::ErrorParsingResult result = leveldb_env::ParseMethodAndError(
status.ToString().c_str(), &method, &error);
return (result == leveldb_env::METHOD_AND_PFE &&
static_cast<base::File::Error>(error) ==
base::File::FILE_ERROR_NO_SPACE) ||
(result == leveldb_env::METHOD_AND_ERRNO && error == ENOSPC);
}
bool IsIOError(const leveldb::Status& status) {
leveldb_env::MethodID method;
int error = -1;
leveldb_env::ErrorParsingResult result = leveldb_env::ParseMethodAndError(
status.ToString().c_str(), &method, &error);
return result != leveldb_env::NONE;
}
bool IsCorruption(const leveldb::Status& status) {
// LevelDB returns InvalidArgument when an sst file is truncated but there is
// no IsInvalidArgument() accessor defined.
return status.IsCorruption() || (!status.ok() && !IsIOError(status));
}
std::string FilePathToString(const base::FilePath& file_path) {
#if defined(OS_WIN)
return base::UTF16ToUTF8(file_path.value());
#else
return file_path.value();
#endif
}
base::FilePath ChromiumEnv::CreateFilePath(const std::string& file_path) {
#if defined(OS_WIN)
return base::FilePath(base::UTF8ToUTF16(file_path));
#else
return base::FilePath(file_path);
#endif
}
bool ChromiumEnv::MakeBackup(const std::string& fname) {
base::FilePath original_table_name = CreateFilePath(fname);
base::FilePath backup_table_name =
original_table_name.ReplaceExtension(backup_table_extension);
return base::CopyFile(original_table_name, backup_table_name);
}
bool ChromiumEnv::HasTableExtension(const base::FilePath& path) {
return path.MatchesExtension(table_extension);
}
ChromiumEnv::ChromiumEnv()
: name_("LevelDBEnv"),
make_backup_(false),
bgsignal_(&mu_),
started_bgthread_(false),
kMaxRetryTimeMillis(1000) {
}
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 ::base::PathExists(CreateFilePath(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.";
}
base::FilePath ChromiumEnv::RestoreFromBackup(const base::FilePath& base_name) {
base::FilePath table_name =
base_name.AddExtension(table_extension);
bool result = base::CopyFile(base_name.AddExtension(backup_table_extension),
table_name);
std::string uma_name(name_);
uma_name.append(".TableRestore");
base::BooleanHistogram::FactoryGet(
uma_name, base::Histogram::kUmaTargetedHistogramFlag)->AddBoolean(result);
return table_name;
}
void ChromiumEnv::RestoreIfNecessary(const std::string& dir,
std::vector<std::string>* result) {
std::set<base::FilePath> tables_found;
std::set<base::FilePath> backups_found;
for (std::vector<std::string>::iterator it = result->begin();
it != result->end();
++it) {
base::FilePath current = CreateFilePath(*it);
if (current.MatchesExtension(table_extension))
tables_found.insert(current.RemoveExtension());
if (current.MatchesExtension(backup_table_extension))
backups_found.insert(current.RemoveExtension());
}
std::set<base::FilePath> backups_only =
base::STLSetDifference<std::set<base::FilePath> >(backups_found,
tables_found);
if (backups_only.size()) {
std::string uma_name(name_);
uma_name.append(".MissingFiles");
int num_missing_files =
backups_only.size() > INT_MAX ? INT_MAX : backups_only.size();
base::Histogram::FactoryGet(uma_name,
1 /*min*/,
100 /*max*/,
8 /*num_buckets*/,
base::Histogram::kUmaTargetedHistogramFlag)
->Add(num_missing_files);
}
base::FilePath dir_filepath = base::FilePath::FromUTF8Unsafe(dir);
for (std::set<base::FilePath>::iterator it = backups_only.begin();
it != backups_only.end();
++it) {
base::FilePath restored_table_name =
RestoreFromBackup(dir_filepath.Append(*it));
result->push_back(FilePathToString(restored_table_name.BaseName()));
}
}
Status ChromiumEnv::GetChildren(const std::string& dir_string,
std::vector<std::string>* result) {
std::vector<base::FilePath> entries;
base::File::Error error =
GetDirectoryEntries(CreateFilePath(dir_string), &entries);
if (error != base::File::FILE_OK) {
RecordOSError(kGetChildren, error);
return MakeIOError(
dir_string, "Could not open/read directory", kGetChildren, error);
}
result->clear();
for (std::vector<base::FilePath>::iterator it = entries.begin();
it != entries.end();
++it) {
result->push_back(FilePathToString(*it));
}
if (make_backup_)
RestoreIfNecessary(dir_string, result);
return Status::OK();
}
Status ChromiumEnv::DeleteFile(const std::string& fname) {
Status result;
base::FilePath fname_filepath = CreateFilePath(fname);
// TODO(jorlow): Should we assert this is a file?
if (!::base::DeleteFile(fname_filepath, false)) {
result = MakeIOError(fname, "Could not delete file.", kDeleteFile);
RecordErrorAt(kDeleteFile);
}
if (make_backup_ && fname_filepath.MatchesExtension(table_extension)) {
base::DeleteFile(fname_filepath.ReplaceExtension(backup_table_extension),
false);
}
return result;
}
Status ChromiumEnv::CreateDir(const std::string& name) {
Status result;
base::File::Error error = base::File::FILE_OK;
Retrier retrier(kCreateDir, this);
do {
if (base::CreateDirectoryAndGetError(CreateFilePath(name), &error))
return result;
} while (retrier.ShouldKeepTrying(error));
result = MakeIOError(name, "Could not create directory.", kCreateDir, error);
RecordOSError(kCreateDir, error);
return result;
}
Status ChromiumEnv::DeleteDir(const std::string& name) {
Status result;
// TODO(jorlow): Should we assert this is a directory?
if (!::base::DeleteFile(CreateFilePath(name), false)) {
result = MakeIOError(name, "Could not delete directory.", kDeleteDir);
RecordErrorAt(kDeleteDir);
}
return result;
}
Status ChromiumEnv::GetFileSize(const std::string& fname, uint64_t* size) {
Status s;
int64_t signed_size;
if (!::base::GetFileSize(CreateFilePath(fname), &signed_size)) {
*size = 0;
s = MakeIOError(fname, "Could not determine file size.", kGetFileSize);
RecordErrorAt(kGetFileSize);
} else {
*size = static_cast<uint64_t>(signed_size);
}
return s;
}
Status ChromiumEnv::RenameFile(const std::string& src, const std::string& dst) {
Status result;
base::FilePath src_file_path = CreateFilePath(src);
if (!::base::PathExists(src_file_path))
return result;
base::FilePath destination = CreateFilePath(dst);
Retrier retrier(kRenameFile, this);
base::File::Error error = base::File::FILE_OK;
do {
if (base::ReplaceFile(src_file_path, destination, &error))
return result;
} while (retrier.ShouldKeepTrying(error));
DCHECK(error != base::File::FILE_OK);
RecordOSError(kRenameFile, error);
char buf[100];
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 = NULL;
Status result;
int flags = ::base::File::FLAG_OPEN_ALWAYS |
::base::File::FLAG_READ |
::base::File::FLAG_WRITE;
::base::File::Error error_code;
::base::File file;
Retrier retrier(kLockFile, this);
do {
file.Initialize(CreateFilePath(fname), flags);
if (!file.IsValid())
error_code = file.error_details();
} while (!file.IsValid() && retrier.ShouldKeepTrying(error_code));
if (!file.IsValid()) {
if (error_code == ::base::File::FILE_ERROR_NOT_FOUND) {
::base::FilePath parent = CreateFilePath(fname).DirName();
::base::FilePath last_parent;
int num_missing_ancestors = 0;
do {
if (base::DirectoryExists(parent))
break;
++num_missing_ancestors;
last_parent = parent;
parent = parent.DirName();
} while (parent != last_parent);
RecordLockFileAncestors(num_missing_ancestors);
}
result = MakeIOError(fname, FileErrorString(error_code), kLockFile,
error_code);
RecordOSError(kLockFile, error_code);
return result;
}
if (!locks_.Insert(fname)) {
result = MakeIOError(fname, "Lock file already locked.", kLockFile);
return result;
}
Retrier lock_retrier = Retrier(kLockFile, this);
do {
error_code = file.Lock();
} while (error_code != ::base::File::FILE_OK &&
retrier.ShouldKeepTrying(error_code));
if (error_code != ::base::File::FILE_OK) {
locks_.Remove(fname);
result = MakeIOError(fname, FileErrorString(error_code), kLockFile,
error_code);
RecordOSError(kLockFile, error_code);
return result;
}
ChromiumFileLock* my_lock = new ChromiumFileLock;
my_lock->file_ = file.Pass();
my_lock->name_ = fname;
*lock = my_lock;
return result;
}
Status ChromiumEnv::UnlockFile(FileLock* lock) {
ChromiumFileLock* my_lock = reinterpret_cast<ChromiumFileLock*>(lock);
Status result;
::base::File::Error error_code = my_lock->file_.Unlock();
if (error_code != ::base::File::FILE_OK) {
result =
MakeIOError(my_lock->name_, "Could not unlock lock file.", kUnlockFile);
RecordOSError(kUnlockFile, error_code);
}
bool removed = locks_.Remove(my_lock->name_);
DCHECK(removed);
delete my_lock;
return result;
}
Status ChromiumEnv::GetTestDirectory(std::string* path) {
mu_.Acquire();
if (test_directory_.empty()) {
if (!base::CreateNewTempDirectory(kLevelDBTestDirectoryPrefix,
&test_directory_)) {
mu_.Release();
RecordErrorAt(kGetTestDirectory);
return MakeIOError(
"Could not create temp directory.", "", kGetTestDirectory);
}
}
*path = FilePathToString(test_directory_);
mu_.Release();
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));
}
void ChromiumEnv::RecordErrorAt(MethodID method) const {
GetMethodIOErrorHistogram()->Add(method);
}
void ChromiumEnv::RecordLockFileAncestors(int num_missing_ancestors) const {
GetLockFileAncestorHistogram()->Add(num_missing_ancestors);
}
void ChromiumEnv::RecordOSError(MethodID method,
base::File::Error error) const {
DCHECK_LT(error, 0);
RecordErrorAt(method);
GetOSErrorHistogram(method, -base::File::FILE_ERROR_MAX)->Add(-error);
}
void ChromiumEnv::RecordOSError(MethodID method, int error) const {
DCHECK_GT(error, 0);
RecordErrorAt(method);
GetOSErrorHistogram(method, ERANGE + 1)->Add(error);
}
void ChromiumEnv::RecordBackupResult(bool result) const {
std::string uma_name(name_);
uma_name.append(".TableBackup");
base::BooleanHistogram::FactoryGet(
uma_name, base::Histogram::kUmaTargetedHistogramFlag)->AddBoolean(result);
}
base::HistogramBase* ChromiumEnv::GetOSErrorHistogram(MethodID method,
int limit) const {
std::string uma_name(name_);
// TODO(dgrogan): This is probably not the best way to concatenate strings.
uma_name.append(".IOError.").append(MethodIDToString(method));
return base::LinearHistogram::FactoryGet(uma_name, 1, limit, limit + 1,
base::Histogram::kUmaTargetedHistogramFlag);
}
base::HistogramBase* ChromiumEnv::GetMethodIOErrorHistogram() const {
std::string uma_name(name_);
uma_name.append(".IOError");
return base::LinearHistogram::FactoryGet(uma_name, 1, kNumEntries,
kNumEntries + 1, base::Histogram::kUmaTargetedHistogramFlag);
}
base::HistogramBase* ChromiumEnv::GetMaxFDHistogram(
const std::string& type) const {
std::string uma_name(name_);
uma_name.append(".MaxFDs.").append(type);
// These numbers make each bucket twice as large as the previous bucket.
const int kFirstEntry = 1;
const int kLastEntry = 65536;
const int kNumBuckets = 18;
return base::Histogram::FactoryGet(
uma_name, kFirstEntry, kLastEntry, kNumBuckets,
base::Histogram::kUmaTargetedHistogramFlag);
}
base::HistogramBase* ChromiumEnv::GetLockFileAncestorHistogram() const {
std::string uma_name(name_);
uma_name.append(".LockFileAncestorsNotFound");
const int kMin = 1;
const int kMax = 10;
const int kNumBuckets = 11;
return base::LinearHistogram::FactoryGet(
uma_name, kMin, kMax, kNumBuckets,
base::Histogram::kUmaTargetedHistogramFlag);
}
base::HistogramBase* ChromiumEnv::GetRetryTimeHistogram(MethodID method) const {
std::string uma_name(name_);
// TODO(dgrogan): This is probably not the best way to concatenate strings.
uma_name.append(".TimeUntilSuccessFor").append(MethodIDToString(method));
const int kBucketSizeMillis = 25;
// Add 2, 1 for each of the buckets <1 and >max.
const int kNumBuckets = kMaxRetryTimeMillis / kBucketSizeMillis + 2;
return base::Histogram::FactoryTimeGet(
uma_name, base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMilliseconds(kMaxRetryTimeMillis + 1),
kNumBuckets,
base::Histogram::kUmaTargetedHistogramFlag);
}
base::HistogramBase* ChromiumEnv::GetRecoveredFromErrorHistogram(
MethodID method) const {
std::string uma_name(name_);
uma_name.append(".RetryRecoveredFromErrorIn")
.append(MethodIDToString(method));
return base::LinearHistogram::FactoryGet(uma_name, 1, kNumEntries,
kNumEntries + 1, base::Histogram::kUmaTargetedHistogramFlag);
}
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() {}
virtual void ThreadMain() {
(*function_)(arg_);
delete this;
}
private:
void (*function_)(void* arg);
void* arg_;
};
void ChromiumEnv::Schedule(ScheduleFunc* function, void* arg) {
mu_.Acquire();
// Start background thread if necessary
if (!started_bgthread_) {
started_bgthread_ = true;
StartThread(&ChromiumEnv::BGThreadWrapper, this);
}
// If the queue is currently empty, the background thread may currently be
// waiting.
if (queue_.empty()) {
bgsignal_.Signal();
}
// Add to priority queue
queue_.push_back(BGItem());
queue_.back().function = function;
queue_.back().arg = arg;
mu_.Release();
}
void ChromiumEnv::BGThread() {
base::PlatformThread::SetName(name_.c_str());
while (true) {
// Wait until there is an item that is ready to run
mu_.Acquire();
while (queue_.empty()) {
bgsignal_.Wait();
}
void (*function)(void*) = queue_.front().function;
void* arg = queue_.front().arg;
queue_.pop_front();
mu_.Release();
TRACE_EVENT0("leveldb", "ChromiumEnv::BGThread-Task");
(*function)(arg);
}
}
void ChromiumEnv::StartThread(void (*function)(void* arg), void* arg) {
new Thread(function, arg); // Will self-delete.
}
static std::string GetDirName(const std::string& filename) {
base::FilePath file = base::FilePath::FromUTF8Unsafe(filename);
return FilePathToString(file.DirName());
}
void ChromiumEnv::DidCreateNewFile(const std::string& filename) {
base::AutoLock auto_lock(map_lock_);
needs_sync_map_[GetDirName(filename)] = true;
}
bool ChromiumEnv::DoesDirNeedSync(const std::string& filename) {
base::AutoLock auto_lock(map_lock_);
return needs_sync_map_.find(GetDirName(filename)) != needs_sync_map_.end();
}
void ChromiumEnv::DidSyncDir(const std::string& filename) {
base::AutoLock auto_lock(map_lock_);
needs_sync_map_.erase(GetDirName(filename));
}
} // namespace leveldb_env
namespace leveldb {
Env* IDBEnv() {
return leveldb_env::idb_env.Pointer();
}
Env* Env::Default() {
return leveldb_env::default_env.Pointer();
}
} // namespace leveldb