Bitcoin Core  31.0.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 
21 namespace {
22 
23 constexpr 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.
28 static 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 
46 KJ_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