Skip to content

Commit ea27154

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 ea27154

File tree

2 files changed

+38
-6
lines changed

2 files changed

+38
-6
lines changed

fuzztest/internal/subprocess.cc

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ class SubProcess {
7979
static constexpr int kStdErrIdx = 1;
8080
int parent_pipe_[2];
8181
int child_pipe_[2];
82+
// Internal pipe fds for notifying child wait completion.
83+
int child_wait_pipe_fds_[2];
8284
};
8385

8486
// Creates parent/child pipes for piping stdout/stderr from child to parent.
@@ -95,6 +97,13 @@ void SubProcess::CreatePipes() {
9597
fcntl(parent_pipe_[channel], F_SETFL, O_NONBLOCK) != -1,
9698
"Cannot make pipe non-blocking: ", strerror(errno));
9799
}
100+
FUZZTEST_INTERNAL_CHECK(pipe(child_wait_pipe_fds_) == 0,
101+
"Cannot create child wait pipe: ", strerror(errno));
102+
for (int i = 0; i < 2; ++i) {
103+
FUZZTEST_INTERNAL_CHECK(
104+
fcntl(child_wait_pipe_fds_[i], F_SETFD, FD_CLOEXEC) != -1,
105+
"Cannot make child wait pipe fds close on exit: ", strerror(errno));
106+
}
98107
}
99108

100109
void SubProcess::CloseChildPipes() {
@@ -193,25 +202,31 @@ void SubProcess::ReadChildOutput(std::string* stdout_output,
193202
std::string* stderr_output) {
194203
// Set up poll()-ing the pipes.
195204
constexpr int fd_count = 2;
196-
struct pollfd pfd[fd_count];
205+
struct pollfd pfd[fd_count + 1];
197206
std::string* out_str[fd_count];
198207
for (int channel : {kStdOutIdx, kStdErrIdx}) {
199208
pfd[channel].fd = parent_pipe_[channel];
200209
pfd[channel].events = POLLIN;
201210
pfd[channel].revents = 0;
202211
out_str[channel] = channel == kStdOutIdx ? stdout_output : stderr_output;
203212
}
213+
pfd[fd_count].fd = child_wait_pipe_fds_[0];
214+
pfd[fd_count].events = POLLIN;
215+
pfd[fd_count].revents = 0;
204216

205217
// Loop reading stdout and stderr from the child process.
206218
int fd_remain = fd_count;
207219
char buf[4096];
208220
while (fd_remain > 0) {
209-
int ret = poll(pfd, fd_count, -1);
221+
int ret = poll(pfd, fd_count + 1, -1);
210222
if ((ret == -1) && !ShouldRetry(errno)) {
211223
FUZZTEST_INTERNAL_CHECK(false, "Cannot poll(): ", strerror(errno));
212224
} else if (ret == 0) {
213225
FUZZTEST_INTERNAL_CHECK(false, "Impossible timeout: ", strerror(errno));
214226
} else if (ret > 0) {
227+
if (pfd[fd_count].revents) {
228+
break;
229+
}
215230
for (int channel : {kStdOutIdx, kStdErrIdx}) {
216231
if ((pfd[channel].revents & (POLLIN | POLLHUP)) != 0) {
217232
ssize_t n = read(pfd[channel].fd, buf, sizeof(buf));
@@ -247,7 +262,7 @@ int Wait(pid_t pid) {
247262
// TODO(lszekeres): Consider optimizing this further by eliminating polling.
248263
// Could potentially be done using pselect() to wait for SIGCHLD with a timeout.
249264
// I.e., by setting all args to null, except timeout with a sigmask for SIGCHLD.
250-
int WaitWithTimeout(pid_t pid, absl::Duration timeout) {
265+
int WaitWithTimeout(pid_t pid, int fd_to_close, absl::Duration timeout) {
251266
int status;
252267
const absl::Time wait_until = absl::Now() + timeout;
253268
constexpr absl::Duration sleep_duration = absl::Milliseconds(100);
@@ -259,17 +274,21 @@ int WaitWithTimeout(pid_t pid, absl::Duration timeout) {
259274
if (absl::Now() > wait_until) {
260275
FUZZTEST_INTERNAL_CHECK(kill(pid, SIGTERM) == 0,
261276
"Cannot kill(): ", strerror(errno));
262-
return Wait(pid);
277+
status = Wait(pid);
278+
break;
263279
} else {
264280
absl::SleepFor(sleep_duration);
265281
continue;
266282
}
267283
} else if (ret == pid && (WIFEXITED(status) || WIFSIGNALED(status))) {
268-
return status;
284+
break;
269285
} else {
270286
FUZZTEST_INTERNAL_CHECK(false, "wait() error: ", strerror(errno));
271287
}
272288
}
289+
FUZZTEST_INTERNAL_CHECK(close(fd_to_close) != -1,
290+
"Cannot close fd_to_close: ", strerror(errno));
291+
return status;
273292
}
274293

275294
} // anonymous namespace
@@ -282,10 +301,13 @@ RunResults SubProcess::Run(
282301
pid_t child_pid = StartChild(command_line, environment);
283302
CloseChildPipes();
284303
std::future<int> status =
285-
std::async(std::launch::async, &WaitWithTimeout, child_pid, timeout);
304+
std::async(std::launch::async, &WaitWithTimeout, child_pid,
305+
child_wait_pipe_fds_[1], timeout);
286306
std::string stdout_output, stderr_output;
287307
ReadChildOutput(&stdout_output, &stderr_output);
288308
CloseParentPipes();
309+
FUZZTEST_INTERNAL_CHECK(close(child_wait_pipe_fds_[0]) != -1,
310+
"Cannot close child wait pipe: ", strerror(errno));
289311
return {TerminationStatus(status.get()), stdout_output, stderr_output};
290312
}
291313

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)