| // 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 <deque> |
| #include <memory> |
| #include <mutex> |
| #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" |
| |
| #if defined(DeleteFile) |
| #undef DeleteFile |
| #endif // defined(DeleteFile) |
| |
| 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; |
| |
| // Modified 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(ScopedHandle&& other) noexcept : handle_(other.Release()) {} |
| ~ScopedHandle() { Close(); } |
| |
| 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 fname, ScopedHandle file) |
| : filename_(fname), file_(std::move(file)) {} |
| ~WindowsSequentialFile() override {} |
| |
| Status Read(size_t n, Slice* result, char* scratch) override { |
| Status s; |
| 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(file_.get(), scratch, static_cast<DWORD>(n), &bytes_read, |
| nullptr)) { |
| s = WindowsError(filename_, ::GetLastError()); |
| } else { |
| *result = Slice(scratch, bytes_read); |
| } |
| return s; |
| } |
| |
| Status Skip(uint64_t n) override { |
| LARGE_INTEGER distance; |
| distance.QuadPart = n; |
| if (!::SetFilePointerEx(file_.get(), distance, nullptr, FILE_CURRENT)) { |
| return WindowsError(filename_, ::GetLastError()); |
| } |
| return Status::OK(); |
| } |
| |
| private: |
| std::string filename_; |
| ScopedHandle file_; |
| }; |
| |
| class WindowsRandomAccessFile : public RandomAccessFile { |
| public: |
| WindowsRandomAccessFile(std::string fname, ScopedHandle handle) |
| : filename_(fname), handle_(std::move(handle)) {} |
| |
| ~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: |
| std::string filename_; |
| ScopedHandle handle_; |
| }; |
| |
| class WindowsMmapReadableFile : public RandomAccessFile { |
| public: |
| // base[0,length-1] contains the mmapped contents of the file. |
| WindowsMmapReadableFile(std::string fname, void* base, size_t length, |
| Limiter* limiter) |
| : filename_(std::move(fname)), |
| mmapped_region_(base), |
| length_(length), |
| limiter_(limiter) {} |
| |
| ~WindowsMmapReadableFile() override { |
| ::UnmapViewOfFile(mmapped_region_); |
| limiter_->Release(); |
| } |
| |
| Status Read(uint64_t offset, size_t n, Slice* result, |
| char* scratch) const override { |
| Status s; |
| if (offset + n > length_) { |
| *result = Slice(); |
| s = WindowsError(filename_, ERROR_INVALID_PARAMETER); |
| } else { |
| *result = Slice(reinterpret_cast<char*>(mmapped_region_) + offset, n); |
| } |
| return s; |
| } |
| |
| private: |
| std::string filename_; |
| void* mmapped_region_; |
| size_t length_; |
| Limiter* limiter_; |
| }; |
| |
| class WindowsWritableFile : public WritableFile { |
| public: |
| WindowsWritableFile(std::string fname, ScopedHandle handle) |
| : filename_(std::move(fname)), handle_(std::move(handle)), pos_(0) {} |
| |
| ~WindowsWritableFile() override = default; |
| |
| Status Append(const Slice& data) override { |
| size_t n = data.size(); |
| const char* p = data.data(); |
| |
| // Fit as much as possible into buffer. |
| size_t copy = std::min(n, kWritableFileBufferSize - pos_); |
| memcpy(buf_ + pos_, p, copy); |
| p += copy; |
| n -= copy; |
| pos_ += copy; |
| if (n == 0) { |
| return Status::OK(); |
| } |
| |
| // Can't fit in buffer, so need to do at least one write. |
| Status s = FlushBuffered(); |
| if (!s.ok()) { |
| return s; |
| } |
| |
| // Small writes go to buffer, large writes are written directly. |
| if (n < kWritableFileBufferSize) { |
| memcpy(buf_, p, n); |
| pos_ = n; |
| return Status::OK(); |
| } |
| return WriteRaw(p, n); |
| } |
| |
| Status Close() override { |
| Status result = FlushBuffered(); |
| if (!handle_.Close() && result.ok()) { |
| result = WindowsError(filename_, ::GetLastError()); |
| } |
| return result; |
| } |
| |
| Status Flush() override { return FlushBuffered(); } |
| |
| Status Sync() override { |
| // On Windows no need to sync parent directory. It's metadata will be |
| // updated via the creation of the new file, without an explicit sync. |
| return FlushBuffered(); |
| } |
| |
| private: |
| Status FlushBuffered() { |
| Status s = WriteRaw(buf_, pos_); |
| pos_ = 0; |
| return s; |
| } |
| |
| Status WriteRaw(const char* p, size_t n) { |
| DWORD bytes_written; |
| if (!::WriteFile(handle_.get(), p, static_cast<DWORD>(n), &bytes_written, |
| nullptr)) { |
| return Status::IOError(filename_, |
| GetWindowsErrorMessage(::GetLastError())); |
| } |
| return Status::OK(); |
| } |
| |
| // buf_[0, pos_-1] contains data to be written to handle_. |
| const std::string filename_; |
| ScopedHandle handle_; |
| char buf_[kWritableFileBufferSize]; |
| size_t pos_; |
| }; |
| |
| // 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 name) |
| : handle_(std::move(handle)), name_(std::move(name)) {} |
| |
| ScopedHandle& handle() { return handle_; } |
| const std::string& name() const { return name_; } |
| |
| private: |
| ScopedHandle handle_; |
| std::string name_; |
| }; |
| |
| class WindowsEnv : public Env { |
| public: |
| WindowsEnv(); |
| ~WindowsEnv() override { |
| static char msg[] = "Destroying Env::Default()\n"; |
| fwrite(msg, 1, sizeof(msg), stderr); |
| abort(); |
| } |
| |
| Status NewSequentialFile(const std::string& fname, |
| SequentialFile** result) override { |
| *result = nullptr; |
| DWORD desired_access = GENERIC_READ; |
| DWORD share_mode = FILE_SHARE_READ; |
| ScopedHandle handle = |
| ::CreateFileA(fname.c_str(), desired_access, share_mode, nullptr, |
| OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); |
| if (!handle.is_valid()) { |
| return WindowsError(fname, ::GetLastError()); |
| } |
| *result = new WindowsSequentialFile(fname, std::move(handle)); |
| return Status::OK(); |
| } |
| |
| Status NewRandomAccessFile(const std::string& fname, |
| RandomAccessFile** result) override { |
| *result = nullptr; |
| DWORD desired_access = GENERIC_READ; |
| DWORD share_mode = FILE_SHARE_READ; |
| DWORD file_flags = FILE_ATTRIBUTE_READONLY; |
| |
| ScopedHandle handle = |
| ::CreateFileA(fname.c_str(), desired_access, share_mode, nullptr, |
| OPEN_EXISTING, file_flags, nullptr); |
| if (!handle.is_valid()) { |
| return WindowsError(fname, ::GetLastError()); |
| } |
| if (!mmap_limiter_.Acquire()) { |
| *result = new WindowsRandomAccessFile(fname, std::move(handle)); |
| return Status::OK(); |
| } |
| |
| LARGE_INTEGER file_size; |
| if (!::GetFileSizeEx(handle.get(), &file_size)) { |
| return WindowsError(fname, ::GetLastError()); |
| } |
| |
| ScopedHandle mapping = |
| ::CreateFileMappingA(handle.get(), |
| /*security attributes=*/nullptr, PAGE_READONLY, |
| /*dwMaximumSizeHigh=*/0, |
| /*dwMaximumSizeLow=*/0, nullptr); |
| if (mapping.is_valid()) { |
| void* base = MapViewOfFile(mapping.get(), FILE_MAP_READ, 0, 0, 0); |
| if (base) { |
| *result = new WindowsMmapReadableFile( |
| fname, base, static_cast<size_t>(file_size.QuadPart), |
| &mmap_limiter_); |
| return Status::OK(); |
| } |
| } |
| Status s = WindowsError(fname, ::GetLastError()); |
| |
| if (!s.ok()) { |
| mmap_limiter_.Release(); |
| } |
| return s; |
| } |
| |
| Status NewWritableFile(const std::string& fname, |
| WritableFile** result) override { |
| DWORD desired_access = GENERIC_WRITE; |
| DWORD share_mode = 0; |
| |
| ScopedHandle handle = |
| ::CreateFileA(fname.c_str(), desired_access, share_mode, nullptr, |
| CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); |
| if (!handle.is_valid()) { |
| *result = nullptr; |
| return WindowsError(fname, ::GetLastError()); |
| } |
| |
| *result = new WindowsWritableFile(fname, std::move(handle)); |
| return Status::OK(); |
| } |
| |
| Status NewAppendableFile(const std::string& fname, |
| WritableFile** result) override { |
| ScopedHandle handle = |
| ::CreateFileA(fname.c_str(), FILE_APPEND_DATA, 0, nullptr, OPEN_ALWAYS, |
| FILE_ATTRIBUTE_NORMAL, nullptr); |
| if (!handle.is_valid()) { |
| *result = nullptr; |
| return WindowsError(fname, ::GetLastError()); |
| } |
| |
| *result = new WindowsWritableFile(fname, std::move(handle)); |
| return Status::OK(); |
| } |
| |
| bool FileExists(const std::string& fname) override { |
| return GetFileAttributesA(fname.c_str()) != INVALID_FILE_ATTRIBUTES; |
| } |
| |
| Status GetChildren(const std::string& dir, |
| std::vector<std::string>* result) override { |
| const std::string find_pattern = dir + "\\*"; |
| 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(dir, 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(dir, last_error); |
| } |
| return Status::OK(); |
| } |
| |
| Status DeleteFile(const std::string& fname) override { |
| if (!::DeleteFileA(fname.c_str())) { |
| return WindowsError(fname, ::GetLastError()); |
| } |
| return Status::OK(); |
| } |
| |
| Status CreateDir(const std::string& name) override { |
| if (!::CreateDirectoryA(name.c_str(), nullptr)) { |
| return WindowsError(name, ::GetLastError()); |
| } |
| return Status::OK(); |
| } |
| |
| Status DeleteDir(const std::string& name) override { |
| if (!::RemoveDirectoryA(name.c_str())) { |
| return WindowsError(name, ::GetLastError()); |
| } |
| return Status::OK(); |
| } |
| |
| Status GetFileSize(const std::string& fname, uint64_t* size) override { |
| WIN32_FILE_ATTRIBUTE_DATA attrs; |
| if (!::GetFileAttributesExA(fname.c_str(), GetFileExInfoStandard, &attrs)) { |
| return WindowsError(fname, ::GetLastError()); |
| } |
| ULARGE_INTEGER file_size; |
| file_size.HighPart = attrs.nFileSizeHigh; |
| file_size.LowPart = attrs.nFileSizeLow; |
| *size = file_size.QuadPart; |
| return Status::OK(); |
| } |
| |
| Status RenameFile(const std::string& src, |
| const std::string& target) override { |
| // Try a simple move first. It will only succeed when |to_path| doesn't |
| // already exist. |
| if (::MoveFileA(src.c_str(), target.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_path| 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(target.c_str(), src.c_str(), nullptr, |
| REPLACEFILE_IGNORE_MERGE_ERRORS, nullptr, nullptr)) { |
| return Status::OK(); |
| } |
| DWORD replace_error = ::GetLastError(); |
| // In the case of FILE_ERROR_NOT_FOUND from ReplaceFile, it is likely |
| // that |to_path| 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(src, move_error); |
| } else { |
| return WindowsError(src, replace_error); |
| } |
| } |
| |
| Status LockFile(const std::string& fname, FileLock** lock) override { |
| *lock = nullptr; |
| Status result; |
| ScopedHandle handle = ::CreateFileA( |
| fname.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, |
| /*lpSecurityAttributes=*/nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, |
| nullptr); |
| if (!handle.is_valid()) { |
| result = WindowsError(fname, ::GetLastError()); |
| } else if (!LockOrUnlock(handle.get(), true)) { |
| result = WindowsError("lock " + fname, ::GetLastError()); |
| } else { |
| *lock = new WindowsFileLock(std::move(handle), std::move(fname)); |
| } |
| return result; |
| } |
| |
| Status UnlockFile(FileLock* lock) override { |
| std::unique_ptr<WindowsFileLock> my_lock( |
| reinterpret_cast<WindowsFileLock*>(lock)); |
| Status result; |
| if (!LockOrUnlock(my_lock->handle().get(), false)) { |
| result = WindowsError("unlock", ::GetLastError()); |
| } |
| return result; |
| } |
| |
| void Schedule(void (*function)(void*), void* arg) override; |
| |
| void StartThread(void (*function)(void* arg), void* arg) override { |
| std::thread t(function, arg); |
| t.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(), "w"); |
| if (fp == nullptr) { |
| *result = nullptr; |
| return WindowsError("NewLogger", ::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: |
| // Entry per Schedule() call |
| struct BGItem { |
| void* arg; |
| void (*function)(void*); |
| }; |
| |
| // BGThread() is the body of the background thread |
| void BGThread(); |
| |
| std::mutex mu_; |
| std::condition_variable bgsignal_; |
| bool started_bgthread_; |
| std::deque<BGItem> queue_; |
| Limiter mmap_limiter_; |
| }; |
| |
| // Return the maximum number of concurrent mmaps. |
| int MaxMmaps() { |
| if (g_mmap_limit >= 0) { |
| return g_mmap_limit; |
| } |
| // Up to 1000 mmaps for 64-bit binaries; none for smaller pointer sizes. |
| g_mmap_limit = sizeof(void*) >= 8 ? 1000 : 0; |
| return g_mmap_limit; |
| } |
| |
| WindowsEnv::WindowsEnv() |
| : started_bgthread_(false), mmap_limiter_(MaxMmaps()) {} |
| |
| void WindowsEnv::Schedule(void (*function)(void*), void* arg) { |
| std::lock_guard<std::mutex> guard(mu_); |
| |
| // Start background thread if necessary |
| if (!started_bgthread_) { |
| started_bgthread_ = true; |
| std::thread t(&WindowsEnv::BGThread, this); |
| t.detach(); |
| } |
| |
| // If the queue is currently empty, the background thread may currently be |
| // waiting. |
| if (queue_.empty()) { |
| bgsignal_.notify_one(); |
| } |
| |
| // Add to priority queue |
| queue_.push_back(BGItem()); |
| queue_.back().function = function; |
| queue_.back().arg = arg; |
| } |
| |
| void WindowsEnv::BGThread() { |
| while (true) { |
| // Wait until there is an item that is ready to run |
| std::unique_lock<std::mutex> lk(mu_); |
| bgsignal_.wait(lk, [this] { return !queue_.empty(); }); |
| |
| void (*function)(void*) = queue_.front().function; |
| void* arg = queue_.front().arg; |
| queue_.pop_front(); |
| |
| lk.unlock(); |
| (*function)(arg); |
| } |
| } |
| |
| } // namespace |
| |
| static std::once_flag once; |
| static Env* default_env; |
| static void InitDefaultEnv() { default_env = new WindowsEnv(); } |
| |
| void EnvWindowsTestHelper::SetReadOnlyMMapLimit(int limit) { |
| assert(default_env == nullptr); |
| g_mmap_limit = limit; |
| } |
| |
| Env* Env::Default() { |
| std::call_once(once, InitDefaultEnv); |
| return default_env; |
| } |
| |
| } // namespace leveldb |