mirror of
https://github.com/isar/libmdbx.git
synced 2024-12-30 02:54:13 +08:00
aea40fb79f
Поддерживающий выпуск с исправлением обнаруженных ошибок и устранением недочетов в память Героя России гвардии майора Дмитрия Семёнова с позывным "СЭМ". Значимые исправления и доработки: --------------------------------- - Устранение унаследованной от LMDB ошибки приводящей к повреждению БД при использовании `MDBX_DUPFIXED`. - Исправление ложной ошибки `MDBX_CORRUPTED (-30796)` в сценарии работы в режиме `MDBX_DUPFIXED` и нечетной длинной мульти-значений. - Исправление недочета корректировки сопутствующих курсоров при разделении страницы по сценарию добавления пустой страницы слева. - Доработка `rebalance()` ради уменьшения WAF. - Исправление assert-проверки внутри `check_txn()` для случая завершенных транзакций в режиме `MDBX_NO_TLS`. Последствий ошибки, кроме срабатывания assert-проверки в отладочных сборках, нет. - Устранение ошибки при открытии БД на файловой системе только-для-чтения. - Удалены излишне строгие проверки в утилите `mdbx_chk`, которые приводили к ложно-позитивным ошибкам при проверке БД после серии последних доработок. Более подробная информация в [ChangeLog](https://libmdbx.dqdkfa.ru/md__change_log.html). git diff' stat: 19 commits, 57 files changed, 751 insertions(+), 331 deletions(-) Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
464 lines
13 KiB
C++
464 lines
13 KiB
C++
/*
|
|
* Copyright 2017-2024 Leonid Yuriev <leo@yuriev.ru>
|
|
* and other libmdbx authors: please see AUTHORS file.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted only as authorized by the OpenLDAP
|
|
* Public License.
|
|
*
|
|
* A copy of this license is available in the file LICENSE in the
|
|
* top-level directory of the distribution or, alternatively, at
|
|
* <http://www.OpenLDAP.org/license.html>.
|
|
*/
|
|
|
|
#include "test.h++"
|
|
|
|
#if defined(_WIN32) || defined(_WIN64)
|
|
|
|
static std::unordered_map<unsigned, HANDLE> events;
|
|
static HANDLE hBarrierSemaphore, hBarrierEvent;
|
|
static HANDLE hProgressActiveEvent, hProgressPassiveEvent;
|
|
|
|
static int waitstatus2errcode(DWORD result) {
|
|
switch (result) {
|
|
case WAIT_OBJECT_0:
|
|
return MDBX_SUCCESS;
|
|
case WAIT_FAILED:
|
|
return GetLastError();
|
|
case WAIT_ABANDONED:
|
|
return ERROR_ABANDONED_WAIT_0;
|
|
case WAIT_IO_COMPLETION:
|
|
return ERROR_USER_APC;
|
|
case WAIT_TIMEOUT:
|
|
return ERROR_TIMEOUT;
|
|
default:
|
|
return ERROR_UNHANDLED_ERROR;
|
|
}
|
|
}
|
|
|
|
void osal_wait4barrier(void) {
|
|
DWORD rc = WaitForSingleObject(hBarrierSemaphore, 0);
|
|
switch (rc) {
|
|
default:
|
|
failure_perror("WaitForSingleObject(BarrierSemaphore)",
|
|
waitstatus2errcode(rc));
|
|
case WAIT_OBJECT_0:
|
|
rc = WaitForSingleObject(hBarrierEvent, INFINITE);
|
|
if (rc != WAIT_OBJECT_0)
|
|
failure_perror("WaitForSingleObject(BarrierEvent)",
|
|
waitstatus2errcode(rc));
|
|
break;
|
|
case WAIT_TIMEOUT:
|
|
if (!SetEvent(hBarrierEvent))
|
|
failure_perror("SetEvent(BarrierEvent)", GetLastError());
|
|
break;
|
|
}
|
|
}
|
|
|
|
static HANDLE make_inheritable(HANDLE hHandle) {
|
|
assert(hHandle != NULL && hHandle != INVALID_HANDLE_VALUE);
|
|
if (!DuplicateHandle(GetCurrentProcess(), hHandle, GetCurrentProcess(),
|
|
&hHandle, 0, TRUE,
|
|
DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS))
|
|
failure_perror("DuplicateHandle()", GetLastError());
|
|
return hHandle;
|
|
}
|
|
|
|
void osal_setup(const std::vector<actor_config> &actors) {
|
|
assert(events.empty());
|
|
const size_t n = actors.size() + 1;
|
|
events.reserve(n);
|
|
|
|
for (unsigned i = 0; i < n; ++i) {
|
|
HANDLE hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
|
|
if (!hEvent)
|
|
failure_perror("CreateEvent()", GetLastError());
|
|
hEvent = make_inheritable(hEvent);
|
|
log_trace("osal_setup: event %u -> %p", i, hEvent);
|
|
events[i] = hEvent;
|
|
}
|
|
|
|
hBarrierSemaphore = CreateSemaphoreW(NULL, 0, (LONG)actors.size(), NULL);
|
|
if (!hBarrierSemaphore)
|
|
failure_perror("CreateSemaphore(BarrierSemaphore)", GetLastError());
|
|
hBarrierSemaphore = make_inheritable(hBarrierSemaphore);
|
|
|
|
hBarrierEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
|
|
if (!hBarrierEvent)
|
|
failure_perror("CreateEvent(BarrierEvent)", GetLastError());
|
|
hBarrierEvent = make_inheritable(hBarrierEvent);
|
|
|
|
hProgressActiveEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
|
|
if (!hProgressActiveEvent)
|
|
failure_perror("CreateEvent(ProgressActiveEvent)", GetLastError());
|
|
hProgressActiveEvent = make_inheritable(hProgressActiveEvent);
|
|
|
|
hProgressPassiveEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
|
|
if (!hProgressPassiveEvent)
|
|
failure_perror("CreateEvent(ProgressPassiveEvent)", GetLastError());
|
|
hProgressPassiveEvent = make_inheritable(hProgressPassiveEvent);
|
|
}
|
|
|
|
void osal_broadcast(unsigned id) {
|
|
log_trace("osal_broadcast: event %u", id);
|
|
if (!SetEvent(events.at(id)))
|
|
failure_perror("SetEvent()", GetLastError());
|
|
}
|
|
|
|
int osal_waitfor(unsigned id) {
|
|
log_trace("osal_waitfor: event %u", id);
|
|
DWORD rc = WaitForSingleObject(events.at(id), INFINITE);
|
|
return waitstatus2errcode(rc);
|
|
}
|
|
|
|
mdbx_pid_t osal_getpid(void) { return GetCurrentProcessId(); }
|
|
|
|
int osal_delay(unsigned seconds) {
|
|
Sleep(seconds * 1000u);
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const std::string
|
|
actor_config::osal_serialize(simple_checksum &checksum) const {
|
|
checksum.push(hBarrierSemaphore);
|
|
checksum.push(hBarrierEvent);
|
|
checksum.push(hProgressActiveEvent);
|
|
checksum.push(hProgressPassiveEvent);
|
|
|
|
HANDLE hWait = INVALID_HANDLE_VALUE;
|
|
if (wait4id) {
|
|
hWait = events.at(wait4id);
|
|
checksum.push(hWait);
|
|
}
|
|
|
|
HANDLE hSignal = INVALID_HANDLE_VALUE;
|
|
if (wanna_event4signalling()) {
|
|
hSignal = events.at(actor_id);
|
|
checksum.push(hSignal);
|
|
}
|
|
|
|
return format("%p.%p.%p.%p.%p.%p", hBarrierSemaphore, hBarrierEvent, hWait,
|
|
hSignal, hProgressActiveEvent, hProgressPassiveEvent);
|
|
}
|
|
|
|
bool actor_config::osal_deserialize(const char *str, const char *end,
|
|
simple_checksum &checksum) {
|
|
|
|
std::string copy(str, end - str);
|
|
TRACE(">> osal_deserialize(%s)\n", copy.c_str());
|
|
|
|
assert(hBarrierSemaphore == 0);
|
|
assert(hBarrierEvent == 0);
|
|
assert(hProgressActiveEvent == 0);
|
|
assert(hProgressPassiveEvent == 0);
|
|
assert(events.empty());
|
|
|
|
HANDLE hWait, hSignal;
|
|
if (sscanf_s(copy.c_str(), "%p.%p.%p.%p.%p.%p", &hBarrierSemaphore,
|
|
&hBarrierEvent, &hWait, &hSignal, &hProgressActiveEvent,
|
|
&hProgressPassiveEvent) != 6) {
|
|
TRACE("<< osal_deserialize: failed\n");
|
|
return false;
|
|
}
|
|
|
|
checksum.push(hBarrierSemaphore);
|
|
checksum.push(hBarrierEvent);
|
|
checksum.push(hProgressActiveEvent);
|
|
checksum.push(hProgressPassiveEvent);
|
|
|
|
if (wait4id) {
|
|
checksum.push(hWait);
|
|
events[wait4id] = hWait;
|
|
}
|
|
|
|
if (wanna_event4signalling()) {
|
|
checksum.push(hSignal);
|
|
events[actor_id] = hSignal;
|
|
}
|
|
|
|
TRACE("<< osal_deserialize: OK\n");
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
typedef std::pair<HANDLE, actor_status> child;
|
|
static std::unordered_map<mdbx_pid_t, child> children;
|
|
|
|
bool osal_progress_push(bool active) {
|
|
if (!children.empty()) {
|
|
if (!SetEvent(active ? hProgressActiveEvent : hProgressPassiveEvent))
|
|
failure_perror("osal_progress_push: SetEvent(overlord.progress)",
|
|
GetLastError());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void ArgvQuote(std::string &CommandLine, const std::string &Argument,
|
|
bool Force = false)
|
|
|
|
/*++
|
|
|
|
https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
|
|
|
|
Routine Description:
|
|
|
|
This routine appends the given argument to a command line such
|
|
that CommandLineToArgvW will return the argument string unchanged.
|
|
Arguments in a command line should be separated by spaces; this
|
|
function does not add these spaces.
|
|
|
|
Arguments:
|
|
|
|
Argument - Supplies the argument to encode.
|
|
|
|
CommandLine - Supplies the command line to which we append the encoded
|
|
argument string.
|
|
|
|
Force - Supplies an indication of whether we should quote
|
|
the argument even if it does not contain any characters that would
|
|
ordinarily require quoting.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Arbitrary.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Unless we're told otherwise, don't quote unless we actually
|
|
// need to do so --- hopefully avoid problems if programs won't
|
|
// parse quotes properly
|
|
//
|
|
|
|
if (Force == false && Argument.empty() == false &&
|
|
Argument.find_first_of(" \t\n\v\"") == Argument.npos) {
|
|
CommandLine.append(Argument);
|
|
} else {
|
|
CommandLine.push_back('"');
|
|
|
|
for (auto It = Argument.begin();; ++It) {
|
|
size_t NumberBackslashes = 0;
|
|
|
|
while (It != Argument.end() && *It == '\\') {
|
|
++It;
|
|
++NumberBackslashes;
|
|
}
|
|
|
|
if (It == Argument.end()) {
|
|
//
|
|
// Escape all backslashes, but let the terminating
|
|
// double quotation mark we add below be interpreted
|
|
// as a metacharacter.
|
|
//
|
|
CommandLine.append(NumberBackslashes * 2, '\\');
|
|
break;
|
|
} else if (*It == L'"') {
|
|
//
|
|
// Escape all backslashes and the following
|
|
// double quotation mark.
|
|
//
|
|
CommandLine.append(NumberBackslashes * 2 + 1, '\\');
|
|
CommandLine.push_back(*It);
|
|
} else {
|
|
//
|
|
// Backslashes aren't special here.
|
|
//
|
|
CommandLine.append(NumberBackslashes, '\\');
|
|
CommandLine.push_back(*It);
|
|
}
|
|
}
|
|
|
|
CommandLine.push_back('"');
|
|
}
|
|
}
|
|
|
|
int osal_actor_start(const actor_config &config, mdbx_pid_t &pid) {
|
|
if (children.size() == MAXIMUM_WAIT_OBJECTS)
|
|
failure("Couldn't manage more that %u actors on Windows\n",
|
|
MAXIMUM_WAIT_OBJECTS);
|
|
|
|
_flushall();
|
|
|
|
STARTUPINFOA StartupInfo;
|
|
GetStartupInfoA(&StartupInfo);
|
|
|
|
char exename[_MAX_PATH + 1];
|
|
DWORD exename_size = sizeof(exename);
|
|
if (!QueryFullProcessImageNameA(GetCurrentProcess(), 0, exename,
|
|
&exename_size))
|
|
failure_perror("QueryFullProcessImageName()", GetLastError());
|
|
|
|
if (exename[1] != ':') {
|
|
exename_size = GetModuleFileName(NULL, exename, sizeof(exename));
|
|
if (exename_size >= sizeof(exename))
|
|
return ERROR_BAD_LENGTH;
|
|
}
|
|
|
|
std::string cmdline = "$ ";
|
|
ArgvQuote(cmdline, thunk_param(config));
|
|
|
|
if (cmdline.size() >= 32767)
|
|
return ERROR_BAD_LENGTH;
|
|
|
|
PROCESS_INFORMATION ProcessInformation;
|
|
if (!CreateProcessA(exename, const_cast<char *>(cmdline.c_str()),
|
|
NULL, // Retuned process handle is not inheritable.
|
|
NULL, // Retuned thread handle is not inheritable.
|
|
TRUE, // Child inherits all inheritable handles.
|
|
NORMAL_PRIORITY_CLASS | INHERIT_PARENT_AFFINITY,
|
|
NULL, // Inherit the parent's environment.
|
|
NULL, // Inherit the parent's current directory.
|
|
&StartupInfo, &ProcessInformation))
|
|
failure_perror(exename, GetLastError());
|
|
|
|
CloseHandle(ProcessInformation.hThread);
|
|
pid = ProcessInformation.dwProcessId;
|
|
children[pid] = std::make_pair(ProcessInformation.hProcess, as_running);
|
|
return 0;
|
|
}
|
|
|
|
actor_status osal_actor_info(const mdbx_pid_t pid) {
|
|
actor_status status = children.at(pid).second;
|
|
if (status > as_running)
|
|
return status;
|
|
|
|
DWORD ExitCode;
|
|
if (!GetExitCodeProcess(children.at(pid).first, &ExitCode))
|
|
failure_perror("GetExitCodeProcess()", GetLastError());
|
|
|
|
switch (ExitCode) {
|
|
case STILL_ACTIVE:
|
|
return as_running;
|
|
case EXIT_SUCCESS:
|
|
status = as_successful;
|
|
break;
|
|
case EXCEPTION_BREAKPOINT:
|
|
case EXCEPTION_SINGLE_STEP:
|
|
status = as_debugging;
|
|
break;
|
|
case STATUS_CONTROL_C_EXIT:
|
|
case /* STATUS_INTERRUPTED */ 0xC0000515L:
|
|
status = as_killed;
|
|
break;
|
|
case EXCEPTION_ACCESS_VIOLATION:
|
|
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
|
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
|
case EXCEPTION_STACK_OVERFLOW:
|
|
case EXCEPTION_INVALID_DISPOSITION:
|
|
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
|
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
|
case /* STATUS_STACK_BUFFER_OVERRUN, STATUS_BUFFER_OVERFLOW_PREVENTED */
|
|
0xC0000409L:
|
|
case /* STATUS_ASSERTION_FAILURE */ 0xC0000420L:
|
|
case /* STATUS_HEAP_CORRUPTION */ 0xC0000374L:
|
|
case /* STATUS_CONTROL_STACK_VIOLATION */ 0xC00001B2L:
|
|
log_error("pid %zu, exception 0x%x", (intptr_t)pid, (unsigned)ExitCode);
|
|
status = as_coredump;
|
|
break;
|
|
default:
|
|
log_error("pid %zu, exit code %u", (intptr_t)pid, (unsigned)ExitCode);
|
|
status = as_failed;
|
|
break;
|
|
}
|
|
|
|
children.at(pid).second = status;
|
|
return status;
|
|
}
|
|
|
|
void osal_killall_actors(void) {
|
|
for (auto &pair : children)
|
|
TerminateProcess(pair.second.first, STATUS_CONTROL_C_EXIT);
|
|
}
|
|
|
|
int osal_actor_poll(mdbx_pid_t &pid, unsigned timeout) {
|
|
std::vector<HANDLE> handles;
|
|
handles.reserve(children.size() + 2);
|
|
handles.push_back(hProgressActiveEvent);
|
|
handles.push_back(hProgressPassiveEvent);
|
|
for (const auto &pair : children)
|
|
if (pair.second.second <= as_running)
|
|
handles.push_back(pair.second.first);
|
|
|
|
while (true) {
|
|
DWORD rc =
|
|
MsgWaitForMultipleObjectsEx((DWORD)handles.size(), &handles[0],
|
|
(timeout > 60) ? 60 * 1000 : timeout * 1000,
|
|
QS_ALLINPUT | QS_ALLPOSTMESSAGE, 0);
|
|
|
|
if (rc == WAIT_OBJECT_0) {
|
|
logging::progress_canary(true);
|
|
continue;
|
|
}
|
|
if (rc == WAIT_OBJECT_0 + 1) {
|
|
logging::progress_canary(false);
|
|
continue;
|
|
}
|
|
|
|
if (rc >= WAIT_OBJECT_0 + 2 && rc < WAIT_OBJECT_0 + handles.size()) {
|
|
pid = 0;
|
|
for (const auto &pair : children)
|
|
if (pair.second.first == handles[rc - WAIT_OBJECT_0]) {
|
|
pid = pair.first;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (rc == WAIT_TIMEOUT) {
|
|
pid = 0;
|
|
return 0;
|
|
}
|
|
|
|
return waitstatus2errcode(rc);
|
|
}
|
|
}
|
|
|
|
void osal_yield(void) { SwitchToThread(); }
|
|
|
|
void osal_udelay(size_t us) {
|
|
chrono::time until, now = chrono::now_monotonic();
|
|
until.fixedpoint = now.fixedpoint + chrono::from_us(us).fixedpoint;
|
|
|
|
static size_t threshold_us;
|
|
if (threshold_us == 0) {
|
|
unsigned timeslice_ms = 1;
|
|
while (timeBeginPeriod(timeslice_ms) == TIMERR_NOCANDO)
|
|
++timeslice_ms;
|
|
threshold_us = timeslice_ms * size_t(1500);
|
|
assert(threshold_us > 0);
|
|
}
|
|
|
|
do {
|
|
if (us > threshold_us && us > 1000) {
|
|
DWORD rc = SleepEx(unsigned(us / 1000), TRUE);
|
|
if (rc)
|
|
failure_perror("SleepEx()", waitstatus2errcode(rc));
|
|
us = 0;
|
|
}
|
|
|
|
YieldProcessor();
|
|
now = chrono::now_monotonic();
|
|
} while (now.fixedpoint < until.fixedpoint);
|
|
}
|
|
|
|
bool osal_istty(int fd) { return _isatty(fd) != 0; }
|
|
|
|
std::string osal_tempdir(void) {
|
|
char buf[MAX_PATH + 1];
|
|
DWORD len = GetTempPathA(sizeof(buf), buf);
|
|
return std::string(buf, len);
|
|
}
|
|
|
|
#endif /* Windows */
|