Skip to content

Commit 23be287

Browse files
xinhaoyuancopybara-github
authored andcommitted
Stop the child output reading when the child process ends.
Otherwise, the child process could terminate without fully closing the pipes, causing unwanted delays - see the added test. PiperOrigin-RevId: 762172820
1 parent a7e9c58 commit 23be287

File tree

2 files changed

+59
-32
lines changed

2 files changed

+59
-32
lines changed

fuzztest/internal/subprocess.cc

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include <fcntl.h>
2525
#include <poll.h>
2626
#include <spawn.h>
27+
#include <sys/socket.h>
28+
#include <sys/types.h>
2729
#include <sys/wait.h>
2830
#include <unistd.h>
2931
#endif // !defined(_MSC_VER)
@@ -64,9 +66,9 @@ class SubProcess {
6466
absl::Duration timeout);
6567

6668
private:
67-
void CreatePipes();
68-
void CloseChildPipes();
69-
void CloseParentPipes();
69+
void CreateCommunicationFds();
70+
void CloseChildFds();
71+
void CloseParentFds();
7072
posix_spawn_file_actions_t CreateChildFileActions();
7173
void StartWatchdog(absl::Duration timeout);
7274
pid_t StartChild(
@@ -77,36 +79,41 @@ class SubProcess {
7779
// Pipe file descriptors pairs. Index 0 is for stdout, index 1 is for stderr.
7880
static constexpr int kStdOutIdx = 0;
7981
static constexpr int kStdErrIdx = 1;
80-
int parent_pipe_[2];
81-
int child_pipe_[2];
82+
int parent_fds_[2];
83+
int child_fds_[2];
8284
};
8385

84-
// Creates parent/child pipes for piping stdout/stderr from child to parent.
85-
void SubProcess::CreatePipes() {
86+
// Creates communication fds for passing through stdout/stderr from child to
87+
// parent.
88+
void SubProcess::CreateCommunicationFds() {
8689
for (int channel : {kStdOutIdx, kStdErrIdx}) {
87-
int pipe_fds[2];
88-
FUZZTEST_INTERNAL_CHECK(pipe(pipe_fds) == 0,
89-
"Cannot create pipe: ", strerror(errno));
90+
int fds[2];
91+
FUZZTEST_INTERNAL_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0,
92+
"Cannot create socketpair: ", strerror(errno));
9093

91-
parent_pipe_[channel] = pipe_fds[0];
92-
child_pipe_[channel] = pipe_fds[1];
94+
parent_fds_[channel] = fds[0];
95+
child_fds_[channel] = fds[1];
96+
FUZZTEST_INTERNAL_CHECK(shutdown(fds[0], SHUT_WR) == 0,
97+
"Cannot shutdown fds: ", strerror(errno));
98+
FUZZTEST_INTERNAL_CHECK(shutdown(fds[1], SHUT_RD) == 0,
99+
"Cannot shutdown fds: ", strerror(errno));
93100

94101
FUZZTEST_INTERNAL_CHECK(
95-
fcntl(parent_pipe_[channel], F_SETFL, O_NONBLOCK) != -1,
96-
"Cannot make pipe non-blocking: ", strerror(errno));
102+
fcntl(parent_fds_[channel], F_SETFL, O_NONBLOCK) != -1,
103+
"Cannot make communication fds non-blocking: ", strerror(errno));
97104
}
98105
}
99106

100-
void SubProcess::CloseChildPipes() {
107+
void SubProcess::CloseChildFds() {
101108
for (int channel : {kStdOutIdx, kStdErrIdx}) {
102-
FUZZTEST_INTERNAL_CHECK(close(child_pipe_[channel]) != -1,
109+
FUZZTEST_INTERNAL_CHECK(close(child_fds_[channel]) != -1,
103110
"Cannot close pipe: ", strerror(errno));
104111
}
105112
}
106113

107-
void SubProcess::CloseParentPipes() {
114+
void SubProcess::CloseParentFds() {
108115
for (int channel : {kStdOutIdx, kStdErrIdx}) {
109-
FUZZTEST_INTERNAL_CHECK(close(parent_pipe_[channel]) != -1,
116+
FUZZTEST_INTERNAL_CHECK(close(parent_fds_[channel]) != -1,
110117
"Cannot close pipe: ", strerror(errno));
111118
}
112119
}
@@ -128,16 +135,16 @@ posix_spawn_file_actions_t SubProcess::CreateChildFileActions() {
128135

129136
for (int channel : {kStdOutIdx, kStdErrIdx}) {
130137
// Close parent-side pipes.
131-
err = posix_spawn_file_actions_addclose(&actions, parent_pipe_[channel]);
138+
err = posix_spawn_file_actions_addclose(&actions, parent_fds_[channel]);
132139
FUZZTEST_INTERNAL_CHECK(err == 0,
133140
"Cannot add close() action: ", strerror(err));
134141

135142
// Replace stdout/stderr file descriptors with the pipes.
136143
int fd = channel == kStdOutIdx ? STDOUT_FILENO : STDERR_FILENO;
137-
err = posix_spawn_file_actions_adddup2(&actions, child_pipe_[channel], fd);
144+
err = posix_spawn_file_actions_adddup2(&actions, child_fds_[channel], fd);
138145
FUZZTEST_INTERNAL_CHECK(err == 0,
139146
"Cannot add dup2() action: ", strerror(err));
140-
err = posix_spawn_file_actions_addclose(&actions, child_pipe_[channel]);
147+
err = posix_spawn_file_actions_addclose(&actions, child_fds_[channel]);
141148
FUZZTEST_INTERNAL_CHECK(err == 0,
142149
"Cannot add close() action: ", strerror(err));
143150
}
@@ -193,10 +200,10 @@ void SubProcess::ReadChildOutput(std::string* stdout_output,
193200
std::string* stderr_output) {
194201
// Set up poll()-ing the pipes.
195202
constexpr int fd_count = 2;
196-
struct pollfd pfd[fd_count];
203+
struct pollfd pfd[fd_count + 1];
197204
std::string* out_str[fd_count];
198205
for (int channel : {kStdOutIdx, kStdErrIdx}) {
199-
pfd[channel].fd = parent_pipe_[channel];
206+
pfd[channel].fd = parent_fds_[channel];
200207
pfd[channel].events = POLLIN;
201208
pfd[channel].revents = 0;
202209
out_str[channel] = channel == kStdOutIdx ? stdout_output : stderr_output;
@@ -247,7 +254,7 @@ int Wait(pid_t pid) {
247254
// TODO(lszekeres): Consider optimizing this further by eliminating polling.
248255
// Could potentially be done using pselect() to wait for SIGCHLD with a timeout.
249256
// I.e., by setting all args to null, except timeout with a sigmask for SIGCHLD.
250-
int WaitWithTimeout(pid_t pid, absl::Duration timeout) {
257+
int WaitWithTimeout(pid_t pid, int fds_to_shutdown[2], absl::Duration timeout) {
251258
int status;
252259
const absl::Time wait_until = absl::Now() + timeout;
253260
constexpr absl::Duration sleep_duration = absl::Milliseconds(100);
@@ -259,17 +266,25 @@ int WaitWithTimeout(pid_t pid, absl::Duration timeout) {
259266
if (absl::Now() > wait_until) {
260267
FUZZTEST_INTERNAL_CHECK(kill(pid, SIGTERM) == 0,
261268
"Cannot kill(): ", strerror(errno));
262-
return Wait(pid);
269+
status = Wait(pid);
270+
break;
263271
} else {
264272
absl::SleepFor(sleep_duration);
265273
continue;
266274
}
267275
} else if (ret == pid && (WIFEXITED(status) || WIFSIGNALED(status))) {
268-
return status;
276+
break;
269277
} else {
270278
FUZZTEST_INTERNAL_CHECK(false, "wait() error: ", strerror(errno));
271279
}
272280
}
281+
FUZZTEST_INTERNAL_CHECK(
282+
shutdown(fds_to_shutdown[0], SHUT_RD) == 0,
283+
"Cannot shutdown fds_to_shutdown: ", fds_to_shutdown[0], strerror(errno));
284+
FUZZTEST_INTERNAL_CHECK(
285+
shutdown(fds_to_shutdown[1], SHUT_RD) == 0,
286+
"Cannot shutdown fds_to_shutdown: ", fds_to_shutdown[1], strerror(errno));
287+
return status;
273288
}
274289

275290
} // anonymous namespace
@@ -278,15 +293,17 @@ RunResults SubProcess::Run(
278293
const std::vector<std::string>& command_line,
279294
const absl::flat_hash_map<std::string, std::string>& environment,
280295
absl::Duration timeout) {
281-
CreatePipes();
296+
CreateCommunicationFds();
282297
pid_t child_pid = StartChild(command_line, environment);
283-
CloseChildPipes();
284-
std::future<int> status =
285-
std::async(std::launch::async, &WaitWithTimeout, child_pid, timeout);
298+
CloseChildFds();
299+
std::future<int> status = std::async(std::launch::async, &WaitWithTimeout,
300+
child_pid, parent_fds_, timeout);
286301
std::string stdout_output, stderr_output;
287302
ReadChildOutput(&stdout_output, &stderr_output);
288-
CloseParentPipes();
289-
return {TerminationStatus(status.get()), stdout_output, stderr_output};
303+
RunResults result = {TerminationStatus(status.get()), stdout_output,
304+
stderr_output};
305+
CloseParentFds();
306+
return result;
290307
}
291308

292309
#endif // !defined(_MSC_VER) && !(defined(__ANDROID_MIN_SDK_VERSION__) &&

fuzztest/internal/subprocess_test.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "gmock/gmock.h"
2222
#include "gtest/gtest.h"
2323
#include "absl/strings/str_cat.h"
24+
#include "absl/time/clock.h"
2425
#include "absl/time/time.h"
2526

2627
namespace fuzztest::internal {
@@ -79,5 +80,14 @@ TEST(SubProcessTest, TimeoutIsEnforced) {
7980
EXPECT_EQ(std_err.size(), 0);
8081
}
8182

83+
TEST(SubProcessTest, ReturnsAfterChildProcessEnds) {
84+
const auto before_time = absl::Now();
85+
auto [status, std_out, std_err] = RunCommand({"bash", "-c", "sleep 4&"});
86+
const auto after_time = absl::Now();
87+
EXPECT_TRUE(status.Exited());
88+
EXPECT_EQ(status, ExitCode(0));
89+
EXPECT_LT(after_time - before_time, absl::Seconds(2));
90+
}
91+
8292
} // namespace
8393
} // namespace fuzztest::internal

0 commit comments

Comments
 (0)