mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-02 11:55:42 +02:00
Squashed 'src/ipc/libmultiprocess/' changes from a4f92969649..1fc65008f7d
1fc65008f7d Merge bitcoin-core/libmultiprocess#237: Made SpawnProcess() behavior safe post fork() 5205a87cd90 test: check SpawnProcess post-fork safety 69652f0edfa Precompute argv before fork in SpawnProcess 30a8681de62 SpawnProcess: avoid fd leak on close failure d0fc1081d09 Merge bitcoin-core/libmultiprocess#196: ci: Add NetBSD job 7b171f45bfc Merge bitcoin-core/libmultiprocess#234: doc: Fix typos and grammar in documentation and comments 861da39cae9 ci: Add NetBSD job 458745e3940 Fix various typos, spelling mistakes, and grammatical errors in design.md and source code comments. 585decc8561 Merge bitcoin-core/libmultiprocess#236: ci: Install binary package `capnproto` on OpenBSD instead of building it 14e926a3ff3 refactor: extract MakeArgv helper 1ee909393f4 ci: Install binary package `capnproto` on OpenBSD instead of building it 470fc518d4b Merge bitcoin-core/libmultiprocess#230: cmake: add ONLY_CAPNP target_capnp_sources option 2d8886f26c4 Merge bitcoin-core/libmultiprocess#228: Add versions.md and version.h files describing version branches and tags c1838be565d Merge bitcoin-core/libmultiprocess#225: Improve and document act support a173f1704ce Merge bitcoin-core/libmultiprocess#223: ci: Replace nix-shell with equivalent nix develop command 625eaca42fb Merge bitcoin-core/libmultiprocess#229: Design Documentation Update cc234be73a6 Design doc update 81c652687b8 cmake: add ONLY_CAPNP target_capnp_sources option 6e01d2d766e Add versions.md and version.h files describing version branches and tags 4e3f8fa0d2c doc: add instructions for using act 81712ff6bbf ci: disable KVM and sandbox inside act containers 18a2237a8ef ci: Replace nix-shell with equivalent nix develop command git-subtree-dir: src/ipc/libmultiprocess git-subtree-split: 1fc65008f7d64161e84c08cbd93109a23dd6a1e9
This commit is contained in:
@@ -26,6 +26,7 @@ if(BUILD_TESTING AND TARGET CapnProto::kj-test)
|
||||
${MP_PROXY_HDRS}
|
||||
mp/test/foo-types.h
|
||||
mp/test/foo.h
|
||||
mp/test/spawn_tests.cpp
|
||||
mp/test/test.cpp
|
||||
)
|
||||
include(${PROJECT_SOURCE_DIR}/cmake/TargetCapnpSources.cmake)
|
||||
|
||||
110
test/mp/test/spawn_tests.cpp
Normal file
110
test/mp/test/spawn_tests.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <mp/util.h>
|
||||
|
||||
#include <kj/test.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <compare>
|
||||
#include <condition_variable>
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <sys/wait.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
// Poll for child process exit using waitpid(..., WNOHANG) until the child exits
|
||||
// or timeout expires. Returns true if the child exited and status_out was set.
|
||||
// Returns false on timeout or error.
|
||||
static bool WaitPidWithTimeout(int pid, std::chrono::milliseconds timeout, int& status_out)
|
||||
{
|
||||
const auto deadline = std::chrono::steady_clock::now() + timeout;
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
const int r = ::waitpid(pid, &status_out, WNOHANG);
|
||||
if (r == pid) return true;
|
||||
if (r == 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||
continue;
|
||||
}
|
||||
// waitpid error
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
KJ_TEST("SpawnProcess does not run callback in child")
|
||||
{
|
||||
// This test is designed to fail deterministically if fd_to_args is invoked
|
||||
// in the post-fork child: a mutex held by another parent thread at fork
|
||||
// time appears locked forever in the child.
|
||||
std::mutex target_mutex;
|
||||
std::mutex control_mutex;
|
||||
std::condition_variable control_cv;
|
||||
bool locked{false};
|
||||
bool release{false};
|
||||
|
||||
// Holds target_mutex until the releaser thread updates release
|
||||
std::thread locker([&] {
|
||||
std::unique_lock<std::mutex> target_lock(target_mutex);
|
||||
{
|
||||
std::lock_guard<std::mutex> g(control_mutex);
|
||||
locked = true;
|
||||
}
|
||||
control_cv.notify_one();
|
||||
|
||||
std::unique_lock<std::mutex> control_lock(control_mutex);
|
||||
control_cv.wait(control_lock, [&] { return release; });
|
||||
});
|
||||
|
||||
// Wait for target_mutex to be held by the locker thread.
|
||||
{
|
||||
std::unique_lock<std::mutex> l(control_mutex);
|
||||
control_cv.wait(l, [&] { return locked; });
|
||||
}
|
||||
|
||||
// Release the lock shortly after SpawnProcess starts.
|
||||
std::thread releaser([&] {
|
||||
// In the unlikely event a CI machine overshoots this delay, a
|
||||
// regression could be missed. This is preferable to spurious
|
||||
// test failures.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{50});
|
||||
{
|
||||
std::lock_guard<std::mutex> g(control_mutex);
|
||||
release = true;
|
||||
}
|
||||
control_cv.notify_one();
|
||||
});
|
||||
|
||||
int pid{-1};
|
||||
const int fd{mp::SpawnProcess(pid, [&](int child_fd) -> std::vector<std::string> {
|
||||
// If this callback runs in the post-fork child, target_mutex appears
|
||||
// locked forever (the owning thread does not exist), so this deadlocks.
|
||||
std::lock_guard<std::mutex> g(target_mutex);
|
||||
return {"true", std::to_string(child_fd)};
|
||||
})};
|
||||
::close(fd);
|
||||
|
||||
int status{0};
|
||||
// Give the child up to 1 second to exit. If it does not, terminate it and
|
||||
// reap it to avoid leaving a zombie behind.
|
||||
const bool exited{WaitPidWithTimeout(pid, std::chrono::milliseconds{1000}, status)};
|
||||
if (!exited) {
|
||||
::kill(pid, SIGKILL);
|
||||
::waitpid(pid, &status, /*options=*/0);
|
||||
}
|
||||
|
||||
releaser.join();
|
||||
locker.join();
|
||||
|
||||
KJ_EXPECT(exited, "Timeout waiting for child process to exit");
|
||||
KJ_EXPECT(WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <mp/proxy.capnp.h>
|
||||
#include <mp/proxy-io.h>
|
||||
#include <mp/util.h>
|
||||
#include <mp/version.h>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
@@ -32,12 +33,19 @@
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace mp {
|
||||
namespace test {
|
||||
|
||||
/** Check version.h header values */
|
||||
constexpr auto kMP_MAJOR_VERSION{MP_MAJOR_VERSION};
|
||||
constexpr auto kMP_MINOR_VERSION{MP_MINOR_VERSION};
|
||||
static_assert(std::is_integral_v<decltype(kMP_MAJOR_VERSION)>, "MP_MAJOR_VERSION must be an integral constant");
|
||||
static_assert(std::is_integral_v<decltype(kMP_MINOR_VERSION)>, "MP_MINOR_VERSION must be an integral constant");
|
||||
|
||||
/**
|
||||
* Test setup class creating a two way connection between a
|
||||
* ProxyServer<FooInterface> object and a ProxyClient<FooInterface>.
|
||||
|
||||
Reference in New Issue
Block a user