Index: llvm/include/llvm/Support/Program.h =================================================================== --- llvm/include/llvm/Support/Program.h +++ llvm/include/llvm/Support/Program.h @@ -205,22 +205,27 @@ /// \li 0 if the child process has not changed state. /// \note Users of this function should always check the ReturnCode member of /// the \see ProcessInfo returned from this function. - ProcessInfo Wait( - const ProcessInfo &PI, ///< The child process that should be waited on. - std::optional SecondsToWait, ///< If std::nullopt, waits until - ///< child has terminated. - ///< If a value, this specifies the amount of time to wait for the child - ///< process to exit. If the time expires, the child is killed and this - ///< function returns. If zero, this function will perform a non-blocking - ///< wait on the child process. - std::string *ErrMsg = nullptr, ///< If non-zero, provides a pointer to a - ///< string instance in which error messages will be returned. If the - ///< string is non-empty upon return an error occurred while invoking the - ///< program. - std::optional *ProcStat = - nullptr ///< If non-zero, provides - /// a pointer to a structure in which process execution statistics will - /// be stored. + ProcessInfo + Wait(const ProcessInfo &PI, ///< The child process that should be waited on. + std::optional SecondsToWait, ///< If std::nullopt, waits until + ///< child has terminated. + ///< If a value, this specifies the amount of time to wait for the child + ///< process. If the time expires, and \p Polling is false, the child is + ///< killed and this < function returns. If the time expires and \p + ///< Polling is true, the child is resumed. + ///< + ///< If zero, this function will perform a non-blocking + ///< wait on the child process. + std::string *ErrMsg = nullptr, ///< If non-zero, provides a pointer to a + ///< string instance in which error messages will be returned. If the + ///< string is non-empty upon return an error occurred while invoking the + ///< program. + std::optional *ProcStat = + nullptr, ///< If non-zero, provides + /// a pointer to a structure in which process execution statistics will + /// be stored. + + bool Polling = false ///< If true, do not kill the process on timeout. ); /// Print a command argument, and optionally quote it. Index: llvm/lib/Support/Unix/Program.inc =================================================================== --- llvm/lib/Support/Unix/Program.inc +++ llvm/lib/Support/Unix/Program.inc @@ -385,7 +385,8 @@ ProcessInfo llvm::sys::Wait(const ProcessInfo &PI, std::optional SecondsToWait, std::string *ErrMsg, - std::optional *ProcStat) { + std::optional *ProcStat, + bool Polling) { struct sigaction Act, Old; assert(PI.Pid && "invalid pid to wait on, process not started?"); @@ -403,7 +404,6 @@ sigemptyset(&Act.sa_mask); sigaction(SIGALRM, &Act, &Old); // FIXME The alarm signal may be delivered to another thread. - alarm(*SecondsToWait); } else WaitPidOptions = WNOHANG; @@ -425,22 +425,28 @@ return WaitResult; } else { if (SecondsToWait && errno == EINTR) { - // Kill the child. - kill(PI.Pid, SIGKILL); - - // Turn off the alarm and restore the signal handler - alarm(0); - sigaction(SIGALRM, &Old, nullptr); - - // Wait for child to die - // FIXME This could grab some other child process out from another - // waiting thread and then leave a zombie anyway. - if (wait(&status) != ChildPid) - MakeErrMsg(ErrMsg, "Child timed out but wouldn't die"); - else - MakeErrMsg(ErrMsg, "Child timed out", 0); - - WaitResult.ReturnCode = -2; // Timeout detected + if (!Polling) { + // Kill the child. + kill(PI.Pid, SIGKILL); + + // Turn off the alarm and restore the signal handler + alarm(0); + sigaction(SIGALRM, &Old, nullptr); + + // Wait for child to die + // FIXME This could grab some other child process out from another + // waiting thread and then leave a zombie anyway. + if (wait(&status) != ChildPid) + MakeErrMsg(ErrMsg, "Child timed out but wouldn't die"); + else + MakeErrMsg(ErrMsg, "Child timed out", 0); + + WaitResult.ReturnCode = -2; // Timeout detected + } else { + // Child's not done yet, resume it. + kill(PI.Pid, SIGCONT); + } + return WaitResult; } else if (errno != EINTR) { MakeErrMsg(ErrMsg, "Error waiting for child process"); Index: llvm/lib/Support/Windows/Program.inc =================================================================== --- llvm/lib/Support/Windows/Program.inc +++ llvm/lib/Support/Windows/Program.inc @@ -409,7 +409,8 @@ } ProcessInfo sys::Wait(const ProcessInfo &PI, std::optional SecondsToWait, - std::string *ErrMsg, std::optional *ProcStat) { + std::string *ErrMsg, std::optional *ProcStat, + bool Polling) { assert(PI.Pid && "invalid pid to wait on, process not started?"); assert((PI.Process && PI.Process != INVALID_HANDLE_VALUE) && "invalid process handle to wait on, process not started?"); @@ -420,7 +421,7 @@ ProcStat->reset(); DWORD WaitStatus = WaitForSingleObject(PI.Process, milliSecondsToWait); if (WaitStatus == WAIT_TIMEOUT) { - if (*SecondsToWait > 0) { + if (!Polling && *SecondsToWait > 0) { if (!TerminateProcess(PI.Process, 1)) { if (ErrMsg) MakeErrMsg(ErrMsg, "Failed to terminate timed-out program"); Index: llvm/unittests/Support/ProgramTest.cpp =================================================================== --- llvm/unittests/Support/ProgramTest.cpp +++ llvm/unittests/Support/ProgramTest.cpp @@ -284,6 +284,45 @@ ASSERT_EQ(-2, RetCode); } +TEST_F(ProgramEnvTest, TestExecuteNoWaitTimeoutNoKill) { + using namespace llvm::sys; + + if (getenv("LLVM_PROGRAM_TEST_TIMEOUT")) { + sleep_for(/*seconds*/ 5); + exit(0); + } + + std::string Executable = + sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); + StringRef argv[] = { + Executable, + "--gtest_filter=ProgramEnvTest.TestExecuteNoWaitTimeoutNoKill"}; + + // Add LLVM_PROGRAM_TEST_TIMEOUT to the environment of the child. + addEnvVar("LLVM_PROGRAM_TEST_TIMEOUT=1"); + + std::string Error; + bool ExecutionFailed; + ProcessInfo PI0 = ExecuteNoWait(Executable, argv, getEnviron(), + /*Redirects*/ {}, /*MemoryLimit*/ 0, &Error, + &ExecutionFailed); + ASSERT_FALSE(ExecutionFailed) << Error; + ASSERT_NE(PI0.Pid, ProcessInfo::InvalidPid) << "Invalid process id"; + + // Check that we don't kill the process with a non-0 SecondsToWait if Polling. + unsigned LoopCount = 0; + ProcessInfo WaitResult; + do { + ++LoopCount; + WaitResult = llvm::sys::Wait(PI0, /*SecondsToWait*/ 1, &Error, + /*ProcStats*/ nullptr, + /*Polling*/ true); + ASSERT_TRUE(Error.empty()) << Error; + } while (WaitResult.Pid != PI0.Pid); + + ASSERT_GT(LoopCount, 1u) << "LoopCount should be >1"; +} + TEST(ProgramTest, TestExecuteNegative) { std::string Executable = "i_dont_exist"; StringRef argv[] = {Executable};