Skip to content

Commit

Permalink
Fix CPU usage on macOS wait_until.
Browse files Browse the repository at this point in the history
Related (I believe) to boostorg/process#55

With the fix from klemens-morgenstern#197, multiple processes can still at times be spawned, and they take extremely high CPU usage. This PR basically uses the same approach used for boost 1.68.0 (which was the last time wait_for seemed to work reliably) but adds a 1ms sleep to each iteration of the loop so as not to spike the CPU.
  • Loading branch information
toonetown committed Apr 26, 2019
1 parent 61fa15f commit 306578b
Show file tree
Hide file tree
Showing 2 changed files with 10 additions and 73 deletions.
40 changes: 2 additions & 38 deletions include/boost/process/detail/posix/wait_for_exit.hpp
Expand Up @@ -102,54 +102,18 @@ inline bool wait_until(
(((ret == -1) && errno == EINTR) ||
((ret != -1) && !WIFEXITED(status) && !WIFSIGNALED(status))));
#else
//if we do not have sigtimedwait, we fork off a child process to get the signal in time
pid_t timeout_pid = ::fork();
if (timeout_pid == -1)
{
ec = boost::process::detail::get_last_error();
return true;
}
else if (timeout_pid == 0)
{
auto ts = get_timespec(time_out - Clock::now());
::timespec rem;
::nanosleep(&ts, &rem);
while (rem.tv_sec > 0 || rem.tv_nsec > 0)
::nanosleep(&rem, &rem);
::exit(0);
}

struct child_cleaner_t
{
pid_t pid;
~child_cleaner_t()
{
int res;
::kill(pid, SIGTERM);
::waitpid(pid, &res, WNOHANG);
}
};
child_cleaner_t child_cleaner{timeout_pid};

//if we do not have sigtimedwait, we just loop and sleep for a millisecond to prevent CPU spike
do
{
int ret_sig = 0;
if ((::waitpid(timeout_pid, &status, WNOHANG) != 0)
&& (WIFEXITED(status) || WIFSIGNALED(status)))
ret_sig = ::sigwait(&sigset, nullptr);
errno = 0;

ret = ::waitpid(p.pid, &status, WNOHANG);

if ((ret_sig == SIGCHLD) &&
(old_sig.sa_handler != SIG_DFL) && (old_sig.sa_handler != SIG_IGN))
old_sig.sa_handler(ret);

if (ret <= 0)
{
timed_out = Clock::now() >= time_out;
if (timed_out)
return false;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
while ((ret == 0) ||
Expand Down
43 changes: 8 additions & 35 deletions include/boost/process/detail/posix/wait_group.hpp
Expand Up @@ -108,42 +108,10 @@ inline bool wait_until(
}
while (((ret != -1) || (errno != ECHILD)) && !(timed_out = (Clock::now() > time_out)));
#else
//if we do not have sigtimedwait, we fork off a child process to get the signal in time
pid_t timeout_pid = ::fork();
if (timeout_pid == -1)
{
ec = boost::process::detail::get_last_error();
return true;
}
else if (timeout_pid == 0)
{
auto ts = get_timespec(time_out - Clock::now());
::setpgid(0, p.grp);
::nanosleep(&ts, nullptr);
::exit(0);
}

struct child_cleaner_t
{
pid_t pid;
~child_cleaner_t()
{
int res;
::kill(pid, SIGTERM);
::waitpid(pid, &res, WNOHANG);
}
};
child_cleaner_t child_cleaner{timeout_pid};

//if we do not have sigtimedwait, we just loop and sleep for a millisecond to prevent CPU spike
do
{
int status;
if ((::waitpid(timeout_pid, &status, WNOHANG) != 0)
&& (WIFEXITED(status) || WIFSIGNALED(status)))
ret = ::sigwait(&sigset, nullptr);
errno = 0;
if ((ret == SIGCHLD) && (old_sig.sa_handler != SIG_DFL) && (old_sig.sa_handler != SIG_IGN))
old_sig.sa_handler(ret);

ret = ::waitpid(-p.grp, &siginfo.si_status, 0); //so in case it exited, we wanna reap it first
if (ret == -1)
Expand All @@ -154,9 +122,14 @@ inline bool wait_until(

//check if we're done
ret = ::waitid(P_PGID, p.grp, &siginfo, WEXITED | WNOHANG);

if (ret == 0)
{
timed_out = (Clock::now() > time_out);
if (!timed_out)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
while (((ret != -1) || (errno != ECHILD)) && !(timed_out = (Clock::now() > time_out)));
while (((ret != -1) || (errno != ECHILD)) && !timed_out);

#endif

Expand Down

0 comments on commit 306578b

Please sign in to comment.