mirror of
https://github.com/isar/libmdbx.git
synced 2024-12-30 03:04:12 +08:00
mdbx-test: добавление тестов для mdbx_env_resurrect_after_fork()
.
This commit is contained in:
parent
af4dfe541b
commit
d9f49b17de
@ -28,6 +28,7 @@ set(LIBMDBX_TEST_SOURCES
|
||||
append.c++
|
||||
ttl.c++
|
||||
nested.c++
|
||||
fork.c++
|
||||
)
|
||||
|
||||
if(NOT MDBX_BUILD_CXX)
|
||||
|
@ -105,6 +105,10 @@ void testcase_setup(const char *casename, const actor_params ¶ms,
|
||||
configure_actor(last_space_id, ac_try, nullptr, params);
|
||||
configure_actor(last_space_id, ac_jitter, nullptr, params);
|
||||
configure_actor(last_space_id, ac_try, nullptr, params);
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
configure_actor(last_space_id, ac_forkread, nullptr, params);
|
||||
configure_actor(last_space_id, ac_forkwrite, nullptr, params);
|
||||
#endif /* Windows */
|
||||
log_notice("<<< testcase_setup(%s): done", casename);
|
||||
} else {
|
||||
failure("unknown testcase `%s`", casename);
|
||||
|
@ -25,6 +25,10 @@ enum actor_testcase {
|
||||
ac_hill,
|
||||
ac_deadread,
|
||||
ac_deadwrite,
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
ac_forkread,
|
||||
ac_forkwrite,
|
||||
#endif /* Windows */
|
||||
ac_jitter,
|
||||
ac_try,
|
||||
ac_copy,
|
||||
|
224
test/fork.c++
Normal file
224
test/fork.c++
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2023 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)
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
class testcase_smoke4fork : public testcase {
|
||||
using inherited = testcase;
|
||||
|
||||
public:
|
||||
testcase_smoke4fork(const actor_config &config, const mdbx_pid_t pid)
|
||||
: testcase(config, pid) {}
|
||||
bool run() override;
|
||||
virtual bool smoke() = 0;
|
||||
};
|
||||
|
||||
bool testcase_smoke4fork::run() {
|
||||
static std::vector<pid_t> history;
|
||||
const pid_t current_pid = getpid();
|
||||
if (history.empty() || current_pid != history.front()) {
|
||||
history.push_back(current_pid);
|
||||
if (history.size() > /* TODO: add test option */ 2) {
|
||||
log_notice("force exit to avoid fork-bomb: deep %zu, pid stack",
|
||||
history.size());
|
||||
for (const auto pid : history)
|
||||
logging::feed(" %d", pid);
|
||||
logging::ln();
|
||||
log_flush();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
const int deep = (int)history.size();
|
||||
|
||||
int err = db_open__begin__table_create_open_clean(dbi);
|
||||
if (unlikely(err != MDBX_SUCCESS)) {
|
||||
log_notice("fork[deep %d, pid %d]: bailout-prepare due '%s'", deep,
|
||||
current_pid, mdbx_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flipcoin()) {
|
||||
if (!smoke()) {
|
||||
log_notice("%s[deep %d, pid %d] probe %s", "pre-fork", deep, current_pid,
|
||||
"failed");
|
||||
return false;
|
||||
}
|
||||
log_verbose("%s[deep %d, pid %d] probe %s", "pre-fork", deep, current_pid,
|
||||
"done");
|
||||
} else {
|
||||
log_verbose("%s[deep %d, pid %d] probe %s", "pre-fork", deep, current_pid,
|
||||
"skipped");
|
||||
#ifdef __SANITIZE_ADDRESS__
|
||||
const bool abort_txn_to_avoid_memleak = true;
|
||||
#else
|
||||
const bool abort_txn_to_avoid_memleak = !RUNNING_ON_VALGRIND && flipcoin();
|
||||
#endif
|
||||
if (abort_txn_to_avoid_memleak && txn_guard)
|
||||
txn_end(false);
|
||||
}
|
||||
|
||||
log_flush();
|
||||
const pid_t child = fork();
|
||||
if (child < 0)
|
||||
failure_perror("fork()", errno);
|
||||
|
||||
if (child == 0) {
|
||||
const pid_t new_pid = getpid();
|
||||
log_verbose(">>> %s, deep %d, parent-pid %d, child-pid %d",
|
||||
"mdbx_env_resurrect_after_fork()", deep, current_pid, new_pid);
|
||||
log_flush();
|
||||
int err = mdbx_env_resurrect_after_fork(db_guard.get());
|
||||
log_verbose("<<< %s, deep %d, parent-pid %d, child-pid %d, err %d",
|
||||
"mdbx_env_resurrect_after_fork()", deep, current_pid, new_pid,
|
||||
err);
|
||||
log_flush();
|
||||
if (err != MDBX_SUCCESS)
|
||||
failure_perror("mdbx_env_resurrect_after_fork()", err);
|
||||
if (txn_guard)
|
||||
mdbx_txn_abort(txn_guard.release());
|
||||
if (!smoke()) {
|
||||
log_notice("%s[deep %d, pid %d] probe %s", "fork-child", deep, new_pid,
|
||||
"failed");
|
||||
return false;
|
||||
}
|
||||
log_verbose("%s[deep %d, pid %d] probe %s", "fork-child", deep, new_pid,
|
||||
"done");
|
||||
log_flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (txn_guard)
|
||||
txn_end(false);
|
||||
|
||||
int status = 0xdeadbeef;
|
||||
if (waitpid(child, &status, 0) != child)
|
||||
failure_perror("waitpid()", errno);
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
const int code = WEXITSTATUS(status);
|
||||
if (code != EXIT_SUCCESS) {
|
||||
log_notice("%s[deep %d, pid %d] child-pid %d failed, err %d",
|
||||
"fork-child", deep, current_pid, child, code);
|
||||
return false;
|
||||
}
|
||||
log_notice("%s[deep %d, pid %d] child-pid %d done", "fork-child", deep,
|
||||
current_pid, child);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
const int sig = WTERMSIG(status);
|
||||
switch (sig) {
|
||||
case SIGABRT:
|
||||
case SIGBUS:
|
||||
case SIGFPE:
|
||||
case SIGILL:
|
||||
case SIGSEGV:
|
||||
log_notice("%s[deep %d, pid %d] child-pid %d %s by SIG%s", "fork-child",
|
||||
deep, current_pid, child, "terminated", signal_name(sig));
|
||||
break;
|
||||
default:
|
||||
log_notice("%s[deep %d, pid %d] child-id %d %s by SIG%s", "fork-child",
|
||||
deep, current_pid, child, "killed", signal_name(sig));
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (!smoke()) {
|
||||
log_notice("%s[deep %d, pid %d] probe %s", "post-fork", deep, current_pid,
|
||||
"failed");
|
||||
return false;
|
||||
}
|
||||
log_verbose("%s[deep %d, pid %d] probe %s", "post-fork", deep, current_pid,
|
||||
"done");
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class testcase_forkread : public testcase_smoke4fork {
|
||||
using inherited = testcase_smoke4fork;
|
||||
|
||||
public:
|
||||
testcase_forkread(const actor_config &config, const mdbx_pid_t pid)
|
||||
: testcase_smoke4fork(config, pid) {}
|
||||
bool smoke() override;
|
||||
};
|
||||
REGISTER_TESTCASE(forkread);
|
||||
|
||||
bool testcase_forkread::smoke() {
|
||||
MDBX_envinfo env_info;
|
||||
int err = mdbx_env_info_ex(db_guard.get(), txn_guard.get(), &env_info,
|
||||
sizeof(env_info));
|
||||
if (err)
|
||||
failure_perror("mdbx_env_info_ex()", err);
|
||||
|
||||
if (!txn_guard)
|
||||
txn_begin(true);
|
||||
|
||||
MDBX_txn_info txn_info;
|
||||
err = mdbx_txn_info(txn_guard.get(), &txn_info, sizeof(txn_info));
|
||||
if (err)
|
||||
failure_perror("mdbx_txn_info()", err);
|
||||
fetch_canary();
|
||||
err = mdbx_env_info_ex(db_guard.get(), txn_guard.get(), &env_info,
|
||||
sizeof(env_info));
|
||||
if (err)
|
||||
failure_perror("mdbx_env_info_ex()", err);
|
||||
|
||||
uint64_t seq;
|
||||
err = mdbx_dbi_sequence(txn_guard.get(), dbi, &seq, 0);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
failure_perror("mdbx_dbi_sequence(get)", err);
|
||||
txn_end(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class testcase_forkwrite : public testcase_forkread {
|
||||
using inherited = testcase_forkread;
|
||||
|
||||
public:
|
||||
testcase_forkwrite(const actor_config &config, const mdbx_pid_t pid)
|
||||
: testcase_forkread(config, pid) {}
|
||||
bool smoke() override;
|
||||
};
|
||||
REGISTER_TESTCASE(forkwrite);
|
||||
|
||||
bool testcase_forkwrite::smoke() {
|
||||
const bool firstly_read = flipcoin();
|
||||
if (firstly_read) {
|
||||
if (!testcase_forkread::smoke())
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!txn_guard)
|
||||
txn_begin(false);
|
||||
uint64_t seq;
|
||||
int err = mdbx_dbi_sequence(txn_guard.get(), dbi, &seq, 1);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
failure_perror("mdbx_dbi_sequence(inc)", err);
|
||||
txn_end(false);
|
||||
|
||||
if (!firstly_read && !testcase_forkread::smoke())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* Windows */
|
@ -108,8 +108,7 @@ bool output(const loglevel priority, const char *format, ...) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void output_nocheckloglevel_ap(const logging::loglevel priority,
|
||||
const char *format, va_list ap) {
|
||||
bool ln() {
|
||||
if (last) {
|
||||
putc('\n', last);
|
||||
fflush(last);
|
||||
@ -118,8 +117,14 @@ void output_nocheckloglevel_ap(const logging::loglevel priority,
|
||||
fflush(stdout);
|
||||
}
|
||||
last = nullptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void output_nocheckloglevel_ap(const logging::loglevel priority,
|
||||
const char *format, va_list ap) {
|
||||
ln();
|
||||
chrono::time now = chrono::now_realtime();
|
||||
struct tm tm;
|
||||
#ifdef _MSC_VER
|
||||
|
@ -55,6 +55,7 @@ bool MDBX_PRINTF_ARGS(2, 3)
|
||||
output(const loglevel priority, const char *format, ...);
|
||||
bool feed_ap(const char *format, va_list ap);
|
||||
bool MDBX_PRINTF_ARGS(1, 2) feed(const char *format, ...);
|
||||
bool ln();
|
||||
|
||||
void inline MDBX_PRINTF_ARGS(2, 3)
|
||||
output_nocheckloglevel(const loglevel priority, const char *format, ...) {
|
||||
|
@ -60,6 +60,10 @@ MDBX_NORETURN void usage(void) {
|
||||
" --append Append-mode insertions\n"
|
||||
" --dead.reader Dead-reader simulator\n"
|
||||
" --dead.writer Dead-writer simulator\n"
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
" --fork.reader After-fork reader\n"
|
||||
" --fork.writer After-fork writer\n"
|
||||
#endif /* Windows */
|
||||
"Actor options:\n"
|
||||
" --batch.read=N Read-operations batch size\n"
|
||||
" --batch.write=N Write-operations batch size\n"
|
||||
@ -591,6 +595,18 @@ int main(int argc, char *const argv[]) {
|
||||
configure_actor(last_space_id, ac_nested, value, params);
|
||||
continue;
|
||||
}
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
if (config::parse_option(argc, argv, narg, "fork.reader", nullptr)) {
|
||||
fixup4qemu(params);
|
||||
configure_actor(last_space_id, ac_forkread, value, params);
|
||||
continue;
|
||||
}
|
||||
if (config::parse_option(argc, argv, narg, "fork.writer", nullptr)) {
|
||||
fixup4qemu(params);
|
||||
configure_actor(last_space_id, ac_forkwrite, value, params);
|
||||
continue;
|
||||
}
|
||||
#endif /* Windows */
|
||||
|
||||
if (*argv[narg] != '-') {
|
||||
fixup4qemu(params);
|
||||
|
@ -356,6 +356,7 @@ mdbx_pid_t osal_getpid(void) { return getpid(); }
|
||||
int osal_delay(unsigned seconds) { return sleep(seconds) ? errno : 0; }
|
||||
|
||||
int osal_actor_start(const actor_config &config, mdbx_pid_t &pid) {
|
||||
static sigset_t mask;
|
||||
if (children.empty()) {
|
||||
struct sigaction act;
|
||||
memset(&act, 0, sizeof(act));
|
||||
@ -366,7 +367,6 @@ int osal_actor_start(const actor_config &config, mdbx_pid_t &pid) {
|
||||
sigaction(SIGUSR1, &act, nullptr);
|
||||
sigaction(SIGUSR2, &act, nullptr);
|
||||
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGCHLD);
|
||||
sigaddset(&mask, SIGUSR1);
|
||||
@ -377,6 +377,7 @@ int osal_actor_start(const actor_config &config, mdbx_pid_t &pid) {
|
||||
pid = fork();
|
||||
|
||||
if (pid == 0) {
|
||||
sigprocmask(SIG_BLOCK, &mask, nullptr);
|
||||
overlord_pid = getppid();
|
||||
const bool result = test_execute(config);
|
||||
exit(result ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
@ -400,7 +401,7 @@ void osal_killall_actors(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static const char *signal_name(const int sig) {
|
||||
const char *signal_name(const int sig) {
|
||||
if (sig == SIGHUP)
|
||||
return "HUP";
|
||||
if (sig == SIGINT)
|
||||
@ -532,24 +533,25 @@ int osal_actor_poll(mdbx_pid_t &pid, unsigned timeout) {
|
||||
children[pid] =
|
||||
(WEXITSTATUS(status) == EXIT_SUCCESS) ? as_successful : as_failed;
|
||||
else if (WIFSIGNALED(status)) {
|
||||
int sig = WTERMSIG(status);
|
||||
#ifdef WCOREDUMP
|
||||
if (WCOREDUMP(status))
|
||||
children[pid] = as_coredump;
|
||||
else
|
||||
#endif /* WCOREDUMP */
|
||||
switch (WTERMSIG(status)) {
|
||||
switch (sig) {
|
||||
case SIGABRT:
|
||||
case SIGBUS:
|
||||
case SIGFPE:
|
||||
case SIGILL:
|
||||
case SIGSEGV:
|
||||
log_notice("child pid %lu terminated by SIG%s", (long)pid,
|
||||
signal_name(WTERMSIG(status)));
|
||||
log_notice("child pid %lu %s by SIG%s", (long)pid, "terminated",
|
||||
signal_name(sig));
|
||||
children[pid] = as_coredump;
|
||||
break;
|
||||
default:
|
||||
log_notice("child pid %lu killed by SIG%s", (long)pid,
|
||||
signal_name(WTERMSIG(status)));
|
||||
log_notice("child pid %lu %s by SIG%s", (long)pid, "killed",
|
||||
signal_name(sig));
|
||||
children[pid] = as_killed;
|
||||
}
|
||||
} else if (WIFSTOPPED(status))
|
||||
|
@ -46,3 +46,7 @@ std::string osal_tempdir(void);
|
||||
#define STDERR_FILENO _fileno(stderr)
|
||||
#endif
|
||||
#endif /* _MSC_VER */
|
||||
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
const char *signal_name(const int sig);
|
||||
#endif /* Windows */
|
||||
|
@ -39,6 +39,12 @@ const char *testcase2str(const actor_testcase testcase) {
|
||||
return "ttl";
|
||||
case ac_nested:
|
||||
return "nested";
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
case ac_forkread:
|
||||
return "forkread";
|
||||
case ac_forkwrite:
|
||||
return "forkwrite";
|
||||
#endif /* Windows */
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user