Bitcoin Core
31.1.0
P2P Digital Currency
Toggle main menu visibility
Loading...
Searching...
No Matches
src
ipc
libmultiprocess
test
mp
test
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
}
util.h
mp::SpawnProcess
int SpawnProcess(int &pid, FdToArgsFn &&fd_to_args)
Definition
util.cpp:119
KJ_TEST
KJ_TEST("SpawnProcess does not run callback in child")
Definition
spawn_tests.cpp:46
ByteUnit::g
@ g
Definition
strencodings.h:50
Generated on
for Bitcoin Core by
1.17.0