mirror of
https://github.com/facebook/rocksdb.git
synced 2024-11-29 00:34:03 +00:00
13ded69484
Summary: Env::GenerateUniqueId() works fine on Windows and on POSIX where /proc/sys/kernel/random/uuid exists. Our other implementation is flawed and easily produces collision in a new multi-threaded test. As we rely more heavily on DB session ID uniqueness, this becomes a serious issue. This change combines several individually suitable entropy sources for reliable generation of random unique IDs, with goal of uniqueness and portability, not cryptographic strength nor maximum speed. Specifically: * Moves code for getting UUIDs from the OS to port::GenerateRfcUuid rather than in Env implementation details. Callers are now told whether the operation fails or succeeds. * Adds an internal API GenerateRawUniqueId for generating high-quality 128-bit unique identifiers, by combining entropy from three "tracks": * Lots of info from default Env like time, process id, and hostname. * std::random_device * port::GenerateRfcUuid (when working) * Built-in implementations of Env::GenerateUniqueId() will now always produce an RFC 4122 UUID string, either from platform-specific API or by converting the output of GenerateRawUniqueId. DB session IDs now use GenerateRawUniqueId while DB IDs (not as critical) try to use port::GenerateRfcUuid but fall back on GenerateRawUniqueId with conversion to an RFC 4122 UUID. GenerateRawUniqueId is declared and defined under env/ rather than util/ or even port/ because of the Env dependency. Likely follow-up: enhance GenerateRawUniqueId to be faster after the first call and to guarantee uniqueness within the lifetime of a single process (imparting the same property onto DB session IDs). Pull Request resolved: https://github.com/facebook/rocksdb/pull/8708 Test Plan: A new mini-stress test in env_test checks the various public and internal APIs for uniqueness, including each track of GenerateRawUniqueId individually. We can't hope to verify anywhere close to 128 bits of entropy, but it can at least detect flaws as bad as the old code. Serial execution of the new tests takes about 350 ms on my machine. Reviewed By: zhichao-cao, mrambacher Differential Revision: D30563780 Pulled By: pdillinger fbshipit-source-id: de4c9ff4b2f581cf784fcedb5f39f16e5185c364
306 lines
7.3 KiB
C++
306 lines
7.3 KiB
C++
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
//
|
|
// 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.
|
|
|
|
#if defined(OS_WIN)
|
|
|
|
#include "port/win/port_win.h"
|
|
|
|
#include <assert.h>
|
|
#include <io.h>
|
|
#include <rpc.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
#include <exception>
|
|
#include <memory>
|
|
|
|
#include "port/port_dirent.h"
|
|
#include "port/sys_time.h"
|
|
|
|
#ifdef ROCKSDB_WINDOWS_UTF8_FILENAMES
|
|
// utf8 <-> utf16
|
|
#include <string>
|
|
#include <locale>
|
|
#include <codecvt>
|
|
#endif
|
|
|
|
#include "logging/logging.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
extern const bool kDefaultToAdaptiveMutex = false;
|
|
|
|
namespace port {
|
|
|
|
#ifdef ROCKSDB_WINDOWS_UTF8_FILENAMES
|
|
std::string utf16_to_utf8(const std::wstring& utf16) {
|
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t> convert;
|
|
return convert.to_bytes(utf16);
|
|
}
|
|
|
|
std::wstring utf8_to_utf16(const std::string& utf8) {
|
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
|
return converter.from_bytes(utf8);
|
|
}
|
|
#endif
|
|
|
|
void gettimeofday(struct timeval* tv, struct timezone* /* tz */) {
|
|
using namespace std::chrono;
|
|
|
|
microseconds usNow(
|
|
duration_cast<microseconds>(system_clock::now().time_since_epoch()));
|
|
|
|
seconds secNow(duration_cast<seconds>(usNow));
|
|
|
|
tv->tv_sec = static_cast<long>(secNow.count());
|
|
tv->tv_usec = static_cast<long>(usNow.count() -
|
|
duration_cast<microseconds>(secNow).count());
|
|
}
|
|
|
|
Mutex::~Mutex() {}
|
|
|
|
CondVar::~CondVar() {}
|
|
|
|
void CondVar::Wait() {
|
|
// Caller must ensure that mutex is held prior to calling this method
|
|
std::unique_lock<std::mutex> lk(mu_->getLock(), std::adopt_lock);
|
|
#ifndef NDEBUG
|
|
mu_->locked_ = false;
|
|
#endif
|
|
cv_.wait(lk);
|
|
#ifndef NDEBUG
|
|
mu_->locked_ = true;
|
|
#endif
|
|
// Release ownership of the lock as we don't want it to be unlocked when
|
|
// it goes out of scope (as we adopted the lock and didn't lock it ourselves)
|
|
lk.release();
|
|
}
|
|
|
|
bool CondVar::TimedWait(uint64_t abs_time_us) {
|
|
|
|
using namespace std::chrono;
|
|
|
|
// MSVC++ library implements wait_until in terms of wait_for so
|
|
// we need to convert absolute wait into relative wait.
|
|
microseconds usAbsTime(abs_time_us);
|
|
|
|
microseconds usNow(
|
|
duration_cast<microseconds>(system_clock::now().time_since_epoch()));
|
|
microseconds relTimeUs =
|
|
(usAbsTime > usNow) ? (usAbsTime - usNow) : microseconds::zero();
|
|
|
|
// Caller must ensure that mutex is held prior to calling this method
|
|
std::unique_lock<std::mutex> lk(mu_->getLock(), std::adopt_lock);
|
|
|
|
// Work around https://github.com/microsoft/STL/issues/369
|
|
#if defined(_MSC_VER) && \
|
|
(!defined(_MSVC_STL_UPDATE) || _MSVC_STL_UPDATE < 202008L)
|
|
if (relTimeUs == microseconds::zero()) {
|
|
lk.unlock();
|
|
lk.lock();
|
|
}
|
|
#endif
|
|
#ifndef NDEBUG
|
|
mu_->locked_ = false;
|
|
#endif
|
|
std::cv_status cvStatus = cv_.wait_for(lk, relTimeUs);
|
|
#ifndef NDEBUG
|
|
mu_->locked_ = true;
|
|
#endif
|
|
// Release ownership of the lock as we don't want it to be unlocked when
|
|
// it goes out of scope (as we adopted the lock and didn't lock it ourselves)
|
|
lk.release();
|
|
|
|
if (cvStatus == std::cv_status::timeout) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CondVar::Signal() { cv_.notify_one(); }
|
|
|
|
void CondVar::SignalAll() { cv_.notify_all(); }
|
|
|
|
int PhysicalCoreID() { return GetCurrentProcessorNumber(); }
|
|
|
|
void InitOnce(OnceType* once, void (*initializer)()) {
|
|
std::call_once(once->flag_, initializer);
|
|
}
|
|
|
|
// Private structure, exposed only by pointer
|
|
struct DIR {
|
|
HANDLE handle_;
|
|
bool firstread_;
|
|
RX_WIN32_FIND_DATA data_;
|
|
dirent entry_;
|
|
|
|
DIR() : handle_(INVALID_HANDLE_VALUE),
|
|
firstread_(true) {}
|
|
|
|
DIR(const DIR&) = delete;
|
|
DIR& operator=(const DIR&) = delete;
|
|
|
|
~DIR() {
|
|
if (INVALID_HANDLE_VALUE != handle_) {
|
|
::FindClose(handle_);
|
|
}
|
|
}
|
|
};
|
|
|
|
DIR* opendir(const char* name) {
|
|
if (!name || *name == 0) {
|
|
errno = ENOENT;
|
|
return nullptr;
|
|
}
|
|
|
|
std::string pattern(name);
|
|
pattern.append("\\").append("*");
|
|
|
|
std::unique_ptr<DIR> dir(new DIR);
|
|
|
|
dir->handle_ =
|
|
RX_FindFirstFileEx(RX_FN(pattern).c_str(),
|
|
FindExInfoBasic, // Do not want alternative name
|
|
&dir->data_, FindExSearchNameMatch,
|
|
NULL, // lpSearchFilter
|
|
0);
|
|
|
|
if (dir->handle_ == INVALID_HANDLE_VALUE) {
|
|
return nullptr;
|
|
}
|
|
|
|
RX_FILESTRING x(dir->data_.cFileName, RX_FNLEN(dir->data_.cFileName));
|
|
strcpy_s(dir->entry_.d_name, sizeof(dir->entry_.d_name), FN_TO_RX(x).c_str());
|
|
|
|
return dir.release();
|
|
}
|
|
|
|
struct dirent* readdir(DIR* dirp) {
|
|
if (!dirp || dirp->handle_ == INVALID_HANDLE_VALUE) {
|
|
errno = EBADF;
|
|
return nullptr;
|
|
}
|
|
|
|
if (dirp->firstread_) {
|
|
dirp->firstread_ = false;
|
|
return &dirp->entry_;
|
|
}
|
|
|
|
auto ret = RX_FindNextFile(dirp->handle_, &dirp->data_);
|
|
|
|
if (ret == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
RX_FILESTRING x(dirp->data_.cFileName, RX_FNLEN(dirp->data_.cFileName));
|
|
strcpy_s(dirp->entry_.d_name, sizeof(dirp->entry_.d_name),
|
|
FN_TO_RX(x).c_str());
|
|
|
|
return &dirp->entry_;
|
|
}
|
|
|
|
int closedir(DIR* dirp) {
|
|
delete dirp;
|
|
return 0;
|
|
}
|
|
|
|
int truncate(const char* path, int64_t length) {
|
|
if (path == nullptr) {
|
|
errno = EFAULT;
|
|
return -1;
|
|
}
|
|
return ROCKSDB_NAMESPACE::port::Truncate(path, length);
|
|
}
|
|
|
|
int Truncate(std::string path, int64_t len) {
|
|
|
|
if (len < 0) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
HANDLE hFile =
|
|
RX_CreateFile(RX_FN(path).c_str(), GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
NULL, // Security attrs
|
|
OPEN_EXISTING, // Truncate existing file only
|
|
FILE_ATTRIBUTE_NORMAL, NULL);
|
|
|
|
if (INVALID_HANDLE_VALUE == hFile) {
|
|
auto lastError = GetLastError();
|
|
if (lastError == ERROR_FILE_NOT_FOUND) {
|
|
errno = ENOENT;
|
|
} else if (lastError == ERROR_ACCESS_DENIED) {
|
|
errno = EACCES;
|
|
} else {
|
|
errno = EIO;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int result = 0;
|
|
FILE_END_OF_FILE_INFO end_of_file;
|
|
end_of_file.EndOfFile.QuadPart = len;
|
|
|
|
if (!SetFileInformationByHandle(hFile, FileEndOfFileInfo, &end_of_file,
|
|
sizeof(FILE_END_OF_FILE_INFO))) {
|
|
errno = EIO;
|
|
result = -1;
|
|
}
|
|
|
|
CloseHandle(hFile);
|
|
return result;
|
|
}
|
|
|
|
void Crash(const std::string& srcfile, int srcline) {
|
|
fprintf(stdout, "Crashing at %s:%d\n", srcfile.c_str(), srcline);
|
|
fflush(stdout);
|
|
abort();
|
|
}
|
|
|
|
int GetMaxOpenFiles() { return -1; }
|
|
|
|
// Assume 4KB page size
|
|
const size_t kPageSize = 4U * 1024U;
|
|
|
|
void SetCpuPriority(ThreadId id, CpuPriority priority) {
|
|
// Not supported
|
|
(void)id;
|
|
(void)priority;
|
|
}
|
|
|
|
int64_t GetProcessID() { return GetCurrentProcessId(); }
|
|
|
|
bool GenerateRfcUuid(std::string* output) {
|
|
UUID uuid;
|
|
UuidCreateSequential(&uuid);
|
|
|
|
RPC_CSTR rpc_str;
|
|
auto status = UuidToStringA(&uuid, &rpc_str);
|
|
if (status != RPC_S_OK) {
|
|
return false;
|
|
}
|
|
|
|
// rpc_str is nul-terminated
|
|
*output = reinterpret_cast<char*>(rpc_str);
|
|
|
|
status = RpcStringFreeA(&rpc_str);
|
|
assert(status == RPC_S_OK);
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace port
|
|
} // namespace ROCKSDB_NAMESPACE
|
|
|
|
#endif
|