Bitcoin Core 30.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
23// Poll for child process exit using waitpid(..., WNOHANG) until the child exits
24// or timeout expires. Returns true if the child exited and status_out was set.
25// Returns false on timeout or error.
26static bool WaitPidWithTimeout(int pid, std::chrono::milliseconds timeout, int& status_out)
27{
28 const auto deadline = std::chrono::steady_clock::now() + timeout;
29 while (std::chrono::steady_clock::now() < deadline) {
30 const int r = ::waitpid(pid, &status_out, WNOHANG);
31 if (r == pid) return true;
32 if (r == 0) {
33 std::this_thread::sleep_for(std::chrono::milliseconds{1});
34 continue;
35 }
36 // waitpid error
37 return false;
38 }
39 return false;
40}
41
42} // namespace
43
44KJ_TEST("SpawnProcess does not run callback in child")
45{
46 // This test is designed to fail deterministically if fd_to_args is invoked
47 // in the post-fork child: a mutex held by another parent thread at fork
48 // time appears locked forever in the child.
49 std::mutex target_mutex;
50 std::mutex control_mutex;
51 std::condition_variable control_cv;
52 bool locked{false};
53 bool release{false};
54
55 // Holds target_mutex until the releaser thread updates release
56 std::thread locker([&] {
57 std::unique_lock<std::mutex> target_lock(target_mutex);
58 {
59 std::lock_guard<std::mutex> g(control_mutex);
60 locked = true;
61 }
62 control_cv.notify_one();
63
64 std::unique_lock<std::mutex> control_lock(control_mutex);
65 control_cv.wait(control_lock, [&] { return release; });
66 });
67
68 // Wait for target_mutex to be held by the locker thread.
69 {
70 std::unique_lock<std::mutex> l(control_mutex);
71 control_cv.wait(l, [&] { return locked; });
72 }
73
74 // Release the lock shortly after SpawnProcess starts.
75 std::thread releaser([&] {
76 // In the unlikely event a CI machine overshoots this delay, a
77 // regression could be missed. This is preferable to spurious
78 // test failures.
79 std::this_thread::sleep_for(std::chrono::milliseconds{50});
80 {
81 std::lock_guard<std::mutex> g(control_mutex);
82 release = true;
83 }
84 control_cv.notify_one();
85 });
86
87 int pid{-1};
88 const int fd{mp::SpawnProcess(pid, [&](int child_fd) -> std::vector<std::string> {
89 // If this callback runs in the post-fork child, target_mutex appears
90 // locked forever (the owning thread does not exist), so this deadlocks.
91 std::lock_guard<std::mutex> g(target_mutex);
92 return {"true", std::to_string(child_fd)};
93 })};
94 ::close(fd);
95
96 int status{0};
97 // Give the child up to 1 second to exit. If it does not, terminate it and
98 // reap it to avoid leaving a zombie behind.
99 const bool exited{WaitPidWithTimeout(pid, std::chrono::milliseconds{1000}, status)};
100 if (!exited) {
101 ::kill(pid, SIGKILL);
102 ::waitpid(pid, &status, /*options=*/0);
103 }
104
105 releaser.join();
106 locker.join();
107
108 KJ_EXPECT(exited, "Timeout waiting for child process to exit");
109 KJ_EXPECT(WIFEXITED(status) && WEXITSTATUS(status) == 0);
110}
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:44