68d14a723a
I recently encountered a problem with this because Windows doesn't allow files to be deleted when there's open handles to them. Other files opened by leveldb are not affected because by and large they are using CreateFileA, which does not allow inheritance when lpSecurityAttributes is null (ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea) However, fopen() _does_ allow inheritance, and it needs to be expressly disabled. https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-160
797 lines
25 KiB
C++
797 lines
25 KiB
C++
// Copyright (c) 2018 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.
|
|
|
|
// Prevent Windows headers from defining min/max macros and instead
|
|
// use STL.
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif // ifndef NOMINMAX
|
|
#include <windows.h>
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <condition_variable>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <queue>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "leveldb/env.h"
|
|
#include "leveldb/slice.h"
|
|
#include "port/port.h"
|
|
#include "port/thread_annotations.h"
|
|
#include "util/env_windows_test_helper.h"
|
|
#include "util/logging.h"
|
|
#include "util/mutexlock.h"
|
|
#include "util/windows_logger.h"
|
|
|
|
namespace leveldb {
|
|
|
|
namespace {
|
|
|
|
constexpr const size_t kWritableFileBufferSize = 65536;
|
|
|
|
// Up to 1000 mmaps for 64-bit binaries; none for 32-bit.
|
|
constexpr int kDefaultMmapLimit = (sizeof(void*) >= 8) ? 1000 : 0;
|
|
|
|
// Can be set by by EnvWindowsTestHelper::SetReadOnlyMMapLimit().
|
|
int g_mmap_limit = kDefaultMmapLimit;
|
|
|
|
std::string GetWindowsErrorMessage(DWORD error_code) {
|
|
std::string message;
|
|
char* error_text = nullptr;
|
|
// Use MBCS version of FormatMessage to match return value.
|
|
size_t error_text_size = ::FormatMessageA(
|
|
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
reinterpret_cast<char*>(&error_text), 0, nullptr);
|
|
if (!error_text) {
|
|
return message;
|
|
}
|
|
message.assign(error_text, error_text_size);
|
|
::LocalFree(error_text);
|
|
return message;
|
|
}
|
|
|
|
Status WindowsError(const std::string& context, DWORD error_code) {
|
|
if (error_code == ERROR_FILE_NOT_FOUND || error_code == ERROR_PATH_NOT_FOUND)
|
|
return Status::NotFound(context, GetWindowsErrorMessage(error_code));
|
|
return Status::IOError(context, GetWindowsErrorMessage(error_code));
|
|
}
|
|
|
|
class ScopedHandle {
|
|
public:
|
|
ScopedHandle(HANDLE handle) : handle_(handle) {}
|
|
ScopedHandle(const ScopedHandle&) = delete;
|
|
ScopedHandle(ScopedHandle&& other) noexcept : handle_(other.Release()) {}
|
|
~ScopedHandle() { Close(); }
|
|
|
|
ScopedHandle& operator=(const ScopedHandle&) = delete;
|
|
|
|
ScopedHandle& operator=(ScopedHandle&& rhs) noexcept {
|
|
if (this != &rhs) handle_ = rhs.Release();
|
|
return *this;
|
|
}
|
|
|
|
bool Close() {
|
|
if (!is_valid()) {
|
|
return true;
|
|
}
|
|
HANDLE h = handle_;
|
|
handle_ = INVALID_HANDLE_VALUE;
|
|
return ::CloseHandle(h);
|
|
}
|
|
|
|
bool is_valid() const {
|
|
return handle_ != INVALID_HANDLE_VALUE && handle_ != nullptr;
|
|
}
|
|
|
|
HANDLE get() const { return handle_; }
|
|
|
|
HANDLE Release() {
|
|
HANDLE h = handle_;
|
|
handle_ = INVALID_HANDLE_VALUE;
|
|
return h;
|
|
}
|
|
|
|
private:
|
|
HANDLE handle_;
|
|
};
|
|
|
|
// Helper class to limit resource usage to avoid exhaustion.
|
|
// Currently used to limit read-only file descriptors and mmap file usage
|
|
// so that we do not run out of file descriptors or virtual memory, or run into
|
|
// kernel performance problems for very large databases.
|
|
class Limiter {
|
|
public:
|
|
// Limit maximum number of resources to |max_acquires|.
|
|
Limiter(int max_acquires) : acquires_allowed_(max_acquires) {}
|
|
|
|
Limiter(const Limiter&) = delete;
|
|
Limiter operator=(const Limiter&) = delete;
|
|
|
|
// If another resource is available, acquire it and return true.
|
|
// Else return false.
|
|
bool Acquire() {
|
|
int old_acquires_allowed =
|
|
acquires_allowed_.fetch_sub(1, std::memory_order_relaxed);
|
|
|
|
if (old_acquires_allowed > 0) return true;
|
|
|
|
acquires_allowed_.fetch_add(1, std::memory_order_relaxed);
|
|
return false;
|
|
}
|
|
|
|
// Release a resource acquired by a previous call to Acquire() that returned
|
|
// true.
|
|
void Release() { acquires_allowed_.fetch_add(1, std::memory_order_relaxed); }
|
|
|
|
private:
|
|
// The number of available resources.
|
|
//
|
|
// This is a counter and is not tied to the invariants of any other class, so
|
|
// it can be operated on safely using std::memory_order_relaxed.
|
|
std::atomic<int> acquires_allowed_;
|
|
};
|
|
|
|
class WindowsSequentialFile : public SequentialFile {
|
|
public:
|
|
WindowsSequentialFile(std::string filename, ScopedHandle handle)
|
|
: handle_(std::move(handle)), filename_(std::move(filename)) {}
|
|
~WindowsSequentialFile() override {}
|
|
|
|
Status Read(size_t n, Slice* result, char* scratch) override {
|
|
DWORD bytes_read;
|
|
// DWORD is 32-bit, but size_t could technically be larger. However leveldb
|
|
// files are limited to leveldb::Options::max_file_size which is clamped to
|
|
// 1<<30 or 1 GiB.
|
|
assert(n <= std::numeric_limits<DWORD>::max());
|
|
if (!::ReadFile(handle_.get(), scratch, static_cast<DWORD>(n), &bytes_read,
|
|
nullptr)) {
|
|
return WindowsError(filename_, ::GetLastError());
|
|
}
|
|
|
|
*result = Slice(scratch, bytes_read);
|
|
return Status::OK();
|
|
}
|
|
|
|
Status Skip(uint64_t n) override {
|
|
LARGE_INTEGER distance;
|
|
distance.QuadPart = n;
|
|
if (!::SetFilePointerEx(handle_.get(), distance, nullptr, FILE_CURRENT)) {
|
|
return WindowsError(filename_, ::GetLastError());
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
private:
|
|
const ScopedHandle handle_;
|
|
const std::string filename_;
|
|
};
|
|
|
|
class WindowsRandomAccessFile : public RandomAccessFile {
|
|
public:
|
|
WindowsRandomAccessFile(std::string filename, ScopedHandle handle)
|
|
: handle_(std::move(handle)), filename_(std::move(filename)) {}
|
|
|
|
~WindowsRandomAccessFile() override = default;
|
|
|
|
Status Read(uint64_t offset, size_t n, Slice* result,
|
|
char* scratch) const override {
|
|
DWORD bytes_read = 0;
|
|
OVERLAPPED overlapped = {0};
|
|
|
|
overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32);
|
|
overlapped.Offset = static_cast<DWORD>(offset);
|
|
if (!::ReadFile(handle_.get(), scratch, static_cast<DWORD>(n), &bytes_read,
|
|
&overlapped)) {
|
|
DWORD error_code = ::GetLastError();
|
|
if (error_code != ERROR_HANDLE_EOF) {
|
|
*result = Slice(scratch, 0);
|
|
return Status::IOError(filename_, GetWindowsErrorMessage(error_code));
|
|
}
|
|
}
|
|
|
|
*result = Slice(scratch, bytes_read);
|
|
return Status::OK();
|
|
}
|
|
|
|
private:
|
|
const ScopedHandle handle_;
|
|
const std::string filename_;
|
|
};
|
|
|
|
class WindowsMmapReadableFile : public RandomAccessFile {
|
|
public:
|
|
// base[0,length-1] contains the mmapped contents of the file.
|
|
WindowsMmapReadableFile(std::string filename, char* mmap_base, size_t length,
|
|
Limiter* mmap_limiter)
|
|
: mmap_base_(mmap_base),
|
|
length_(length),
|
|
mmap_limiter_(mmap_limiter),
|
|
filename_(std::move(filename)) {}
|
|
|
|
~WindowsMmapReadableFile() override {
|
|
::UnmapViewOfFile(mmap_base_);
|
|
mmap_limiter_->Release();
|
|
}
|
|
|
|
Status Read(uint64_t offset, size_t n, Slice* result,
|
|
char* scratch) const override {
|
|
if (offset + n > length_) {
|
|
*result = Slice();
|
|
return WindowsError(filename_, ERROR_INVALID_PARAMETER);
|
|
}
|
|
|
|
*result = Slice(mmap_base_ + offset, n);
|
|
return Status::OK();
|
|
}
|
|
|
|
private:
|
|
char* const mmap_base_;
|
|
const size_t length_;
|
|
Limiter* const mmap_limiter_;
|
|
const std::string filename_;
|
|
};
|
|
|
|
class WindowsWritableFile : public WritableFile {
|
|
public:
|
|
WindowsWritableFile(std::string filename, ScopedHandle handle)
|
|
: pos_(0), handle_(std::move(handle)), filename_(std::move(filename)) {}
|
|
|
|
~WindowsWritableFile() override = default;
|
|
|
|
Status Append(const Slice& data) override {
|
|
size_t write_size = data.size();
|
|
const char* write_data = data.data();
|
|
|
|
// Fit as much as possible into buffer.
|
|
size_t copy_size = std::min(write_size, kWritableFileBufferSize - pos_);
|
|
std::memcpy(buf_ + pos_, write_data, copy_size);
|
|
write_data += copy_size;
|
|
write_size -= copy_size;
|
|
pos_ += copy_size;
|
|
if (write_size == 0) {
|
|
return Status::OK();
|
|
}
|
|
|
|
// Can't fit in buffer, so need to do at least one write.
|
|
Status status = FlushBuffer();
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
// Small writes go to buffer, large writes are written directly.
|
|
if (write_size < kWritableFileBufferSize) {
|
|
std::memcpy(buf_, write_data, write_size);
|
|
pos_ = write_size;
|
|
return Status::OK();
|
|
}
|
|
return WriteUnbuffered(write_data, write_size);
|
|
}
|
|
|
|
Status Close() override {
|
|
Status status = FlushBuffer();
|
|
if (!handle_.Close() && status.ok()) {
|
|
status = WindowsError(filename_, ::GetLastError());
|
|
}
|
|
return status;
|
|
}
|
|
|
|
Status Flush() override { return FlushBuffer(); }
|
|
|
|
Status Sync() override {
|
|
// On Windows no need to sync parent directory. Its metadata will be updated
|
|
// via the creation of the new file, without an explicit sync.
|
|
|
|
Status status = FlushBuffer();
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
if (!::FlushFileBuffers(handle_.get())) {
|
|
return Status::IOError(filename_,
|
|
GetWindowsErrorMessage(::GetLastError()));
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
private:
|
|
Status FlushBuffer() {
|
|
Status status = WriteUnbuffered(buf_, pos_);
|
|
pos_ = 0;
|
|
return status;
|
|
}
|
|
|
|
Status WriteUnbuffered(const char* data, size_t size) {
|
|
DWORD bytes_written;
|
|
if (!::WriteFile(handle_.get(), data, static_cast<DWORD>(size),
|
|
&bytes_written, nullptr)) {
|
|
return Status::IOError(filename_,
|
|
GetWindowsErrorMessage(::GetLastError()));
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
// buf_[0, pos_-1] contains data to be written to handle_.
|
|
char buf_[kWritableFileBufferSize];
|
|
size_t pos_;
|
|
|
|
ScopedHandle handle_;
|
|
const std::string filename_;
|
|
};
|
|
|
|
// Lock or unlock the entire file as specified by |lock|. Returns true
|
|
// when successful, false upon failure. Caller should call ::GetLastError()
|
|
// to determine cause of failure
|
|
bool LockOrUnlock(HANDLE handle, bool lock) {
|
|
if (lock) {
|
|
return ::LockFile(handle,
|
|
/*dwFileOffsetLow=*/0, /*dwFileOffsetHigh=*/0,
|
|
/*nNumberOfBytesToLockLow=*/MAXDWORD,
|
|
/*nNumberOfBytesToLockHigh=*/MAXDWORD);
|
|
} else {
|
|
return ::UnlockFile(handle,
|
|
/*dwFileOffsetLow=*/0, /*dwFileOffsetHigh=*/0,
|
|
/*nNumberOfBytesToLockLow=*/MAXDWORD,
|
|
/*nNumberOfBytesToLockHigh=*/MAXDWORD);
|
|
}
|
|
}
|
|
|
|
class WindowsFileLock : public FileLock {
|
|
public:
|
|
WindowsFileLock(ScopedHandle handle, std::string filename)
|
|
: handle_(std::move(handle)), filename_(std::move(filename)) {}
|
|
|
|
const ScopedHandle& handle() const { return handle_; }
|
|
const std::string& filename() const { return filename_; }
|
|
|
|
private:
|
|
const ScopedHandle handle_;
|
|
const std::string filename_;
|
|
};
|
|
|
|
class WindowsEnv : public Env {
|
|
public:
|
|
WindowsEnv();
|
|
~WindowsEnv() override {
|
|
static const char msg[] =
|
|
"WindowsEnv singleton destroyed. Unsupported behavior!\n";
|
|
std::fwrite(msg, 1, sizeof(msg), stderr);
|
|
std::abort();
|
|
}
|
|
|
|
Status NewSequentialFile(const std::string& filename,
|
|
SequentialFile** result) override {
|
|
*result = nullptr;
|
|
DWORD desired_access = GENERIC_READ;
|
|
DWORD share_mode = FILE_SHARE_READ;
|
|
ScopedHandle handle = ::CreateFileA(
|
|
filename.c_str(), desired_access, share_mode,
|
|
/*lpSecurityAttributes=*/nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
|
|
/*hTemplateFile=*/nullptr);
|
|
if (!handle.is_valid()) {
|
|
return WindowsError(filename, ::GetLastError());
|
|
}
|
|
|
|
*result = new WindowsSequentialFile(filename, std::move(handle));
|
|
return Status::OK();
|
|
}
|
|
|
|
Status NewRandomAccessFile(const std::string& filename,
|
|
RandomAccessFile** result) override {
|
|
*result = nullptr;
|
|
DWORD desired_access = GENERIC_READ;
|
|
DWORD share_mode = FILE_SHARE_READ;
|
|
ScopedHandle handle =
|
|
::CreateFileA(filename.c_str(), desired_access, share_mode,
|
|
/*lpSecurityAttributes=*/nullptr, OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_READONLY,
|
|
/*hTemplateFile=*/nullptr);
|
|
if (!handle.is_valid()) {
|
|
return WindowsError(filename, ::GetLastError());
|
|
}
|
|
if (!mmap_limiter_.Acquire()) {
|
|
*result = new WindowsRandomAccessFile(filename, std::move(handle));
|
|
return Status::OK();
|
|
}
|
|
|
|
LARGE_INTEGER file_size;
|
|
Status status;
|
|
if (!::GetFileSizeEx(handle.get(), &file_size)) {
|
|
mmap_limiter_.Release();
|
|
return WindowsError(filename, ::GetLastError());
|
|
}
|
|
|
|
ScopedHandle mapping =
|
|
::CreateFileMappingA(handle.get(),
|
|
/*security attributes=*/nullptr, PAGE_READONLY,
|
|
/*dwMaximumSizeHigh=*/0,
|
|
/*dwMaximumSizeLow=*/0,
|
|
/*lpName=*/nullptr);
|
|
if (mapping.is_valid()) {
|
|
void* mmap_base = ::MapViewOfFile(mapping.get(), FILE_MAP_READ,
|
|
/*dwFileOffsetHigh=*/0,
|
|
/*dwFileOffsetLow=*/0,
|
|
/*dwNumberOfBytesToMap=*/0);
|
|
if (mmap_base) {
|
|
*result = new WindowsMmapReadableFile(
|
|
filename, reinterpret_cast<char*>(mmap_base),
|
|
static_cast<size_t>(file_size.QuadPart), &mmap_limiter_);
|
|
return Status::OK();
|
|
}
|
|
}
|
|
mmap_limiter_.Release();
|
|
return WindowsError(filename, ::GetLastError());
|
|
}
|
|
|
|
Status NewWritableFile(const std::string& filename,
|
|
WritableFile** result) override {
|
|
DWORD desired_access = GENERIC_WRITE;
|
|
DWORD share_mode = 0; // Exclusive access.
|
|
ScopedHandle handle = ::CreateFileA(
|
|
filename.c_str(), desired_access, share_mode,
|
|
/*lpSecurityAttributes=*/nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
|
|
/*hTemplateFile=*/nullptr);
|
|
if (!handle.is_valid()) {
|
|
*result = nullptr;
|
|
return WindowsError(filename, ::GetLastError());
|
|
}
|
|
|
|
*result = new WindowsWritableFile(filename, std::move(handle));
|
|
return Status::OK();
|
|
}
|
|
|
|
Status NewAppendableFile(const std::string& filename,
|
|
WritableFile** result) override {
|
|
DWORD desired_access = FILE_APPEND_DATA;
|
|
DWORD share_mode = 0; // Exclusive access.
|
|
ScopedHandle handle = ::CreateFileA(
|
|
filename.c_str(), desired_access, share_mode,
|
|
/*lpSecurityAttributes=*/nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
|
|
/*hTemplateFile=*/nullptr);
|
|
if (!handle.is_valid()) {
|
|
*result = nullptr;
|
|
return WindowsError(filename, ::GetLastError());
|
|
}
|
|
|
|
*result = new WindowsWritableFile(filename, std::move(handle));
|
|
return Status::OK();
|
|
}
|
|
|
|
bool FileExists(const std::string& filename) override {
|
|
return GetFileAttributesA(filename.c_str()) != INVALID_FILE_ATTRIBUTES;
|
|
}
|
|
|
|
Status GetChildren(const std::string& directory_path,
|
|
std::vector<std::string>* result) override {
|
|
const std::string find_pattern = directory_path + "\\*";
|
|
WIN32_FIND_DATAA find_data;
|
|
HANDLE dir_handle = ::FindFirstFileA(find_pattern.c_str(), &find_data);
|
|
if (dir_handle == INVALID_HANDLE_VALUE) {
|
|
DWORD last_error = ::GetLastError();
|
|
if (last_error == ERROR_FILE_NOT_FOUND) {
|
|
return Status::OK();
|
|
}
|
|
return WindowsError(directory_path, last_error);
|
|
}
|
|
do {
|
|
char base_name[_MAX_FNAME];
|
|
char ext[_MAX_EXT];
|
|
|
|
if (!_splitpath_s(find_data.cFileName, nullptr, 0, nullptr, 0, base_name,
|
|
ARRAYSIZE(base_name), ext, ARRAYSIZE(ext))) {
|
|
result->emplace_back(std::string(base_name) + ext);
|
|
}
|
|
} while (::FindNextFileA(dir_handle, &find_data));
|
|
DWORD last_error = ::GetLastError();
|
|
::FindClose(dir_handle);
|
|
if (last_error != ERROR_NO_MORE_FILES) {
|
|
return WindowsError(directory_path, last_error);
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
Status RemoveFile(const std::string& filename) override {
|
|
if (!::DeleteFileA(filename.c_str())) {
|
|
return WindowsError(filename, ::GetLastError());
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
Status CreateDir(const std::string& dirname) override {
|
|
if (!::CreateDirectoryA(dirname.c_str(), nullptr)) {
|
|
return WindowsError(dirname, ::GetLastError());
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
Status RemoveDir(const std::string& dirname) override {
|
|
if (!::RemoveDirectoryA(dirname.c_str())) {
|
|
return WindowsError(dirname, ::GetLastError());
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
Status GetFileSize(const std::string& filename, uint64_t* size) override {
|
|
WIN32_FILE_ATTRIBUTE_DATA file_attributes;
|
|
if (!::GetFileAttributesExA(filename.c_str(), GetFileExInfoStandard,
|
|
&file_attributes)) {
|
|
return WindowsError(filename, ::GetLastError());
|
|
}
|
|
ULARGE_INTEGER file_size;
|
|
file_size.HighPart = file_attributes.nFileSizeHigh;
|
|
file_size.LowPart = file_attributes.nFileSizeLow;
|
|
*size = file_size.QuadPart;
|
|
return Status::OK();
|
|
}
|
|
|
|
Status RenameFile(const std::string& from, const std::string& to) override {
|
|
// Try a simple move first. It will only succeed when |to| doesn't already
|
|
// exist.
|
|
if (::MoveFileA(from.c_str(), to.c_str())) {
|
|
return Status::OK();
|
|
}
|
|
DWORD move_error = ::GetLastError();
|
|
|
|
// Try the full-blown replace if the move fails, as ReplaceFile will only
|
|
// succeed when |to| does exist. When writing to a network share, we may not
|
|
// be able to change the ACLs. Ignore ACL errors then
|
|
// (REPLACEFILE_IGNORE_MERGE_ERRORS).
|
|
if (::ReplaceFileA(to.c_str(), from.c_str(), /*lpBackupFileName=*/nullptr,
|
|
REPLACEFILE_IGNORE_MERGE_ERRORS,
|
|
/*lpExclude=*/nullptr, /*lpReserved=*/nullptr)) {
|
|
return Status::OK();
|
|
}
|
|
DWORD replace_error = ::GetLastError();
|
|
// In the case of FILE_ERROR_NOT_FOUND from ReplaceFile, it is likely that
|
|
// |to| does not exist. In this case, the more relevant error comes from the
|
|
// call to MoveFile.
|
|
if (replace_error == ERROR_FILE_NOT_FOUND ||
|
|
replace_error == ERROR_PATH_NOT_FOUND) {
|
|
return WindowsError(from, move_error);
|
|
} else {
|
|
return WindowsError(from, replace_error);
|
|
}
|
|
}
|
|
|
|
Status LockFile(const std::string& filename, FileLock** lock) override {
|
|
*lock = nullptr;
|
|
Status result;
|
|
ScopedHandle handle = ::CreateFileA(
|
|
filename.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
|
|
/*lpSecurityAttributes=*/nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
|
|
nullptr);
|
|
if (!handle.is_valid()) {
|
|
result = WindowsError(filename, ::GetLastError());
|
|
} else if (!LockOrUnlock(handle.get(), true)) {
|
|
result = WindowsError("lock " + filename, ::GetLastError());
|
|
} else {
|
|
*lock = new WindowsFileLock(std::move(handle), filename);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Status UnlockFile(FileLock* lock) override {
|
|
WindowsFileLock* windows_file_lock =
|
|
reinterpret_cast<WindowsFileLock*>(lock);
|
|
if (!LockOrUnlock(windows_file_lock->handle().get(), false)) {
|
|
return WindowsError("unlock " + windows_file_lock->filename(),
|
|
::GetLastError());
|
|
}
|
|
delete windows_file_lock;
|
|
return Status::OK();
|
|
}
|
|
|
|
void Schedule(void (*background_work_function)(void* background_work_arg),
|
|
void* background_work_arg) override;
|
|
|
|
void StartThread(void (*thread_main)(void* thread_main_arg),
|
|
void* thread_main_arg) override {
|
|
std::thread new_thread(thread_main, thread_main_arg);
|
|
new_thread.detach();
|
|
}
|
|
|
|
Status GetTestDirectory(std::string* result) override {
|
|
const char* env = getenv("TEST_TMPDIR");
|
|
if (env && env[0] != '\0') {
|
|
*result = env;
|
|
return Status::OK();
|
|
}
|
|
|
|
char tmp_path[MAX_PATH];
|
|
if (!GetTempPathA(ARRAYSIZE(tmp_path), tmp_path)) {
|
|
return WindowsError("GetTempPath", ::GetLastError());
|
|
}
|
|
std::stringstream ss;
|
|
ss << tmp_path << "leveldbtest-" << std::this_thread::get_id();
|
|
*result = ss.str();
|
|
|
|
// Directory may already exist
|
|
CreateDir(*result);
|
|
return Status::OK();
|
|
}
|
|
|
|
Status NewLogger(const std::string& filename, Logger** result) override {
|
|
std::FILE* fp = std::fopen(filename.c_str(), "wN");
|
|
if (fp == nullptr) {
|
|
*result = nullptr;
|
|
return WindowsError(filename, ::GetLastError());
|
|
} else {
|
|
*result = new WindowsLogger(fp);
|
|
return Status::OK();
|
|
}
|
|
}
|
|
|
|
uint64_t NowMicros() override {
|
|
// GetSystemTimeAsFileTime typically has a resolution of 10-20 msec.
|
|
// TODO(cmumford): Switch to GetSystemTimePreciseAsFileTime which is
|
|
// available in Windows 8 and later.
|
|
FILETIME ft;
|
|
::GetSystemTimeAsFileTime(&ft);
|
|
// Each tick represents a 100-nanosecond intervals since January 1, 1601
|
|
// (UTC).
|
|
uint64_t num_ticks =
|
|
(static_cast<uint64_t>(ft.dwHighDateTime) << 32) + ft.dwLowDateTime;
|
|
return num_ticks / 10;
|
|
}
|
|
|
|
void SleepForMicroseconds(int micros) override {
|
|
std::this_thread::sleep_for(std::chrono::microseconds(micros));
|
|
}
|
|
|
|
private:
|
|
void BackgroundThreadMain();
|
|
|
|
static void BackgroundThreadEntryPoint(WindowsEnv* env) {
|
|
env->BackgroundThreadMain();
|
|
}
|
|
|
|
// Stores the work item data in a Schedule() call.
|
|
//
|
|
// Instances are constructed on the thread calling Schedule() and used on the
|
|
// background thread.
|
|
//
|
|
// This structure is thread-safe beacuse it is immutable.
|
|
struct BackgroundWorkItem {
|
|
explicit BackgroundWorkItem(void (*function)(void* arg), void* arg)
|
|
: function(function), arg(arg) {}
|
|
|
|
void (*const function)(void*);
|
|
void* const arg;
|
|
};
|
|
|
|
port::Mutex background_work_mutex_;
|
|
port::CondVar background_work_cv_ GUARDED_BY(background_work_mutex_);
|
|
bool started_background_thread_ GUARDED_BY(background_work_mutex_);
|
|
|
|
std::queue<BackgroundWorkItem> background_work_queue_
|
|
GUARDED_BY(background_work_mutex_);
|
|
|
|
Limiter mmap_limiter_; // Thread-safe.
|
|
};
|
|
|
|
// Return the maximum number of concurrent mmaps.
|
|
int MaxMmaps() { return g_mmap_limit; }
|
|
|
|
WindowsEnv::WindowsEnv()
|
|
: background_work_cv_(&background_work_mutex_),
|
|
started_background_thread_(false),
|
|
mmap_limiter_(MaxMmaps()) {}
|
|
|
|
void WindowsEnv::Schedule(
|
|
void (*background_work_function)(void* background_work_arg),
|
|
void* background_work_arg) {
|
|
background_work_mutex_.Lock();
|
|
|
|
// Start the background thread, if we haven't done so already.
|
|
if (!started_background_thread_) {
|
|
started_background_thread_ = true;
|
|
std::thread background_thread(WindowsEnv::BackgroundThreadEntryPoint, this);
|
|
background_thread.detach();
|
|
}
|
|
|
|
// If the queue is empty, the background thread may be waiting for work.
|
|
if (background_work_queue_.empty()) {
|
|
background_work_cv_.Signal();
|
|
}
|
|
|
|
background_work_queue_.emplace(background_work_function, background_work_arg);
|
|
background_work_mutex_.Unlock();
|
|
}
|
|
|
|
void WindowsEnv::BackgroundThreadMain() {
|
|
while (true) {
|
|
background_work_mutex_.Lock();
|
|
|
|
// Wait until there is work to be done.
|
|
while (background_work_queue_.empty()) {
|
|
background_work_cv_.Wait();
|
|
}
|
|
|
|
assert(!background_work_queue_.empty());
|
|
auto background_work_function = background_work_queue_.front().function;
|
|
void* background_work_arg = background_work_queue_.front().arg;
|
|
background_work_queue_.pop();
|
|
|
|
background_work_mutex_.Unlock();
|
|
background_work_function(background_work_arg);
|
|
}
|
|
}
|
|
|
|
// Wraps an Env instance whose destructor is never created.
|
|
//
|
|
// Intended usage:
|
|
// using PlatformSingletonEnv = SingletonEnv<PlatformEnv>;
|
|
// void ConfigurePosixEnv(int param) {
|
|
// PlatformSingletonEnv::AssertEnvNotInitialized();
|
|
// // set global configuration flags.
|
|
// }
|
|
// Env* Env::Default() {
|
|
// static PlatformSingletonEnv default_env;
|
|
// return default_env.env();
|
|
// }
|
|
template <typename EnvType>
|
|
class SingletonEnv {
|
|
public:
|
|
SingletonEnv() {
|
|
#if !defined(NDEBUG)
|
|
env_initialized_.store(true, std::memory_order::memory_order_relaxed);
|
|
#endif // !defined(NDEBUG)
|
|
static_assert(sizeof(env_storage_) >= sizeof(EnvType),
|
|
"env_storage_ will not fit the Env");
|
|
static_assert(alignof(decltype(env_storage_)) >= alignof(EnvType),
|
|
"env_storage_ does not meet the Env's alignment needs");
|
|
new (&env_storage_) EnvType();
|
|
}
|
|
~SingletonEnv() = default;
|
|
|
|
SingletonEnv(const SingletonEnv&) = delete;
|
|
SingletonEnv& operator=(const SingletonEnv&) = delete;
|
|
|
|
Env* env() { return reinterpret_cast<Env*>(&env_storage_); }
|
|
|
|
static void AssertEnvNotInitialized() {
|
|
#if !defined(NDEBUG)
|
|
assert(!env_initialized_.load(std::memory_order::memory_order_relaxed));
|
|
#endif // !defined(NDEBUG)
|
|
}
|
|
|
|
private:
|
|
typename std::aligned_storage<sizeof(EnvType), alignof(EnvType)>::type
|
|
env_storage_;
|
|
#if !defined(NDEBUG)
|
|
static std::atomic<bool> env_initialized_;
|
|
#endif // !defined(NDEBUG)
|
|
};
|
|
|
|
#if !defined(NDEBUG)
|
|
template <typename EnvType>
|
|
std::atomic<bool> SingletonEnv<EnvType>::env_initialized_;
|
|
#endif // !defined(NDEBUG)
|
|
|
|
using WindowsDefaultEnv = SingletonEnv<WindowsEnv>;
|
|
|
|
} // namespace
|
|
|
|
void EnvWindowsTestHelper::SetReadOnlyMMapLimit(int limit) {
|
|
WindowsDefaultEnv::AssertEnvNotInitialized();
|
|
g_mmap_limit = limit;
|
|
}
|
|
|
|
Env* Env::Default() {
|
|
static WindowsDefaultEnv env_container;
|
|
return env_container.env();
|
|
}
|
|
|
|
} // namespace leveldb
|