Bitcoin Core 31.99.0
P2P Digital Currency
spawn_tests.cpp
Go to the documentation of this file.
1// Copyright (c) The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <mp/util.h>
6
7#include <kj/test.h>
8
9#include <chrono>
10#include <compare>
11#include <condition_variable>
12#include <csignal>
13#include <cstdlib>
14#include <mutex>
15#include <string>
16#include <sys/wait.h>
17#include <thread>
18#include <unistd.h>
19#include <vector>
20
21namespace {
22
23constexpr auto FAILURE_TIMEOUT = std::chrono::seconds{30};
24
25// Poll for child process exit using waitpid(..., WNOHANG) until the child exits
26// or timeout expires. Returns true if the child exited and status_out was set.
27// Returns false on timeout or error.
28static bool WaitPidWithTimeout(int pid, std::chrono::milliseconds timeout, int& status_out)
29{
30 const auto deadline = std::chrono::steady_clock::now() + timeout;
31 while (std::chrono::steady_clock::now() < deadline) {
32 const int r = ::waitpid(pid, &status_out, WNOHANG);
33 if (r == pid) return true;
34 if (r == 0) {
35 std::this_thread::sleep_for(std::chrono::milliseconds{1});
36 continue;
37 }
38 // waitpid error
39 return false;
40 }
41 return false;
42}
43
44} // namespace
45
46KJ_TEST("SpawnProcess does not run callback in child")
47{
48 // This test is designed to fail deterministically if fd_to_args is invoked
49 // in the post-fork child: a mutex held by another parent thread at fork
50 // time appears locked forever in the child.
51 std::mutex target_mutex;
52 std::mutex control_mutex;
53 std::condition_variable control_cv;
54 bool locked{false};
55 bool release{false};
56
57 // Holds target_mutex until the releaser thread updates release
58 std::thread locker([&] {
59 std::unique_lock<std::mutex> target_lock(target_mutex);
60 {
61 std::lock_guard<std::mutex> g(control_mutex);
62 locked = true;
63 }
64 control_cv.notify_one();
65
66 std::unique_lock<std::mutex> control_lock(control_mutex);
67 control_cv.wait(control_lock, [&] { return release; });
68 });
69
70 // Wait for target_mutex to be held by the locker thread.
71 {
72 std::unique_lock<std::mutex> l(control_mutex);
73 control_cv.wait(l, [&] { return locked; });
74 }
75
76 // Release the lock shortly after SpawnProcess starts.
77 std::thread releaser([&] {
78 // In the unlikely event a CI machine overshoots this delay, a
79 // regression could be missed. This is preferable to spurious
80 // test failures.
81 std::this_thread::sleep_for(std::chrono::milliseconds{50});
82 {
83 std::lock_guard<std::mutex> g(control_mutex);
84 release = true;
85 }
86 control_cv.notify_one();
87 });
88
89 int pid{-1};
90 const int fd{mp::SpawnProcess(pid, [&](int child_fd) -> std::vector<std::string> {
91 // If this callback runs in the post-fork child, target_mutex appears
92 // locked forever (the owning thread does not exist), so this deadlocks.
93 std::lock_guard<std::mutex> g(target_mutex);
94 return {"true", std::to_string(child_fd)};
95 })};
96 ::close(fd);
97
98 int status{0};
99 // Give the child some time to exit. If it does not, terminate it and
100 // reap it to avoid leaving a zombie behind.
101 const bool exited{WaitPidWithTimeout(pid, FAILURE_TIMEOUT, status)};
102 if (!exited) {
103 ::kill(pid, SIGKILL);
104 ::waitpid(pid, &status, /*options=*/0);
105 }
106
107 releaser.join();
108 locker.join();
109
110 KJ_EXPECT(exited, "Timeout waiting for child process to exit");
111 KJ_EXPECT(WIFEXITED(status) && WEXITSTATUS(status) == 0);
112}
int SpawnProcess(int &pid, FdToArgsFn &&fd_to_args)
Spawn a new process that communicates with the current process over a socket pair.
Definition: util.cpp:119
KJ_TEST("SpawnProcess does not run callback in child")
Definition: spawn_tests.cpp:46