From d9f49b17dee1ab709c0d825fb32843415a398edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B5=D0=BE=D0=BD=D0=B8=D0=B4=20=D0=AE=D1=80=D1=8C?= =?UTF-8?q?=D0=B5=D0=B2=20=28Leonid=20Yuriev=29?= Date: Wed, 8 Nov 2023 19:58:18 +0300 Subject: [PATCH] =?UTF-8?q?mdbx-test:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B4=D0=BB=D1=8F=20`mdbx=5Fenv=5Fresurrect=5Fafter?= =?UTF-8?q?=5Ffork()`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/CMakeLists.txt | 1 + test/cases.c++ | 4 + test/config.h++ | 4 + test/fork.c++ | 224 ++++++++++++++++++++++++++++++++++++++++++++ test/log.c++ | 9 +- test/log.h++ | 1 + test/main.c++ | 16 ++++ test/osal-unix.c++ | 16 ++-- test/osal.h++ | 4 + test/test.c++ | 6 ++ 10 files changed, 276 insertions(+), 9 deletions(-) create mode 100644 test/fork.c++ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f6901916..23789be0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -28,6 +28,7 @@ set(LIBMDBX_TEST_SOURCES append.c++ ttl.c++ nested.c++ + fork.c++ ) if(NOT MDBX_BUILD_CXX) diff --git a/test/cases.c++ b/test/cases.c++ index 97421e7d..5ccb87ae 100644 --- a/test/cases.c++ +++ b/test/cases.c++ @@ -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); diff --git a/test/config.h++ b/test/config.h++ index f57dce7c..80996157 100644 --- a/test/config.h++ +++ b/test/config.h++ @@ -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, diff --git a/test/fork.c++ b/test/fork.c++ new file mode 100644 index 00000000..7f1c9b19 --- /dev/null +++ b/test/fork.c++ @@ -0,0 +1,224 @@ +/* + * Copyright 2023 Leonid Yuriev + * 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 + * . + */ + +#include "test.h++" + +#if !defined(_WIN32) && !defined(_WIN64) + +#include +#include + +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 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 */ diff --git a/test/log.c++ b/test/log.c++ index 04dad84d..5fe485c8 100644 --- a/test/log.c++ +++ b/test/log.c++ @@ -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 diff --git a/test/log.h++ b/test/log.h++ index aa111ac9..96d68848 100644 --- a/test/log.h++ +++ b/test/log.h++ @@ -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, ...) { diff --git a/test/main.c++ b/test/main.c++ index 2b8ff655..ba086e90 100644 --- a/test/main.c++ +++ b/test/main.c++ @@ -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); diff --git a/test/osal-unix.c++ b/test/osal-unix.c++ index 094d6769..0554000a 100644 --- a/test/osal-unix.c++ +++ b/test/osal-unix.c++ @@ -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)) diff --git a/test/osal.h++ b/test/osal.h++ index ef3b5562..5c92b2e9 100644 --- a/test/osal.h++ +++ b/test/osal.h++ @@ -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 */ diff --git a/test/test.c++ b/test/test.c++ index e590d3ce..79ca8a43 100644 --- a/test/test.c++ +++ b/test/test.c++ @@ -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 */ } }