fix(process): fork_detached created zombie processes

Since the forked processes are still our children, we need to wait on
them, otherwise they become zombie processes.

We now fork twice, let the first fork immediately return and wait on it.
This reparents the second fork, which runs the actual code, to the init
process which then collects it.

Ref #770
This commit is contained in:
patrick96 2020-12-12 02:21:36 +01:00 committed by Patrick Ziegler
parent ccf14d9816
commit 47483a94f1
4 changed files with 65 additions and 13 deletions

View File

@ -12,11 +12,14 @@ namespace process_util {
void redirect_stdio_to_dev_null();
pid_t fork_detached(std::function<void()> const& lambda);
pid_t spawn_async(std::function<void()> const& lambda);
void fork_detached(std::function<void()> const& lambda);
void exec(char* cmd, char** args);
void exec_sh(const char* cmd);
int wait(pid_t pid);
pid_t wait_for_completion(pid_t process_id, int* status_addr = nullptr, int waitflags = 0);
pid_t wait_for_completion(int* status_addr, int waitflags = 0);
pid_t wait_for_completion_nohang(pid_t process_id, int* status);

View File

@ -33,7 +33,7 @@ command<output_policy::IGNORED>::~command() {
* Execute the command
*/
int command<output_policy::IGNORED>::exec(bool wait_for_completion) {
m_forkpid = process_util::fork_detached([m_cmd = m_cmd] { process_util::exec_sh(m_cmd.c_str()); });
m_forkpid = process_util::spawn_async([m_cmd = m_cmd] { process_util::exec_sh(m_cmd.c_str()); });
if (wait_for_completion) {
auto status = wait();
m_forkpid = -1;

View File

@ -45,15 +45,11 @@ namespace process_util {
}
/**
* Forks a child process and completely detaches it.
* Forks a child process and executes the given lambda function in it.
*
* In the child process, the given lambda function is executed.
*
* Use this if you want to run a command and just forget about it.
*
* \returns The PID of the child process
* Processes spawned this way need to be waited on by the caller.
*/
pid_t fork_detached(std::function<void()> const& lambda) {
pid_t spawn_async(std::function<void()> const& lambda) {
pid_t pid = fork();
switch (pid) {
case -1:
@ -71,6 +67,50 @@ namespace process_util {
}
}
/**
* Forks a child process and completely detaches it.
*
* In the child process, the given lambda function is executed.
* We fork twice so that the first forked process can exit and it's child is
* reparented to the init process.
*
* Ref: https://web.archive.org/web/20120914180018/http://www.steve.org.uk/Reference/Unix/faq_2.html#SEC16
*
* Use this if you want to run a command and just forget about it.
*
* \returns The PID of the child process
*/
void fork_detached(std::function<void()> const& lambda) {
pid_t pid = fork();
switch (pid) {
case -1:
throw runtime_error("fork_detached: Unable to fork: " + string(strerror(errno)));
case 0:
// Child
setsid();
pid = fork();
switch (pid) {
case -1:
throw runtime_error("fork_detached: Unable to fork: " + string(strerror(errno)));
case 0:
// Child
umask(0);
redirect_stdio_to_dev_null();
lambda();
_Exit(0);
}
_Exit(0);
default:
/*
* The first fork immediately exits and we have to collect its exit
* status
*/
wait(pid);
}
}
/**
* Execute command
*/
@ -92,6 +132,15 @@ namespace process_util {
}
}
int wait(pid_t pid) {
int forkstatus;
do {
process_util::wait_for_completion(pid, &forkstatus, WCONTINUED | WUNTRACED);
} while (!WIFEXITED(forkstatus) && !WIFSIGNALED(forkstatus));
return WEXITSTATUS(forkstatus);
}
/**
* Wait for child process
*/

View File

@ -12,8 +12,8 @@
using namespace polybar;
using namespace process_util;
TEST(ForkDetached, is_detached) {
pid_t pid = fork_detached([] { exec_sh("sleep 0.1"); });
TEST(SpawnAsync, is_async) {
pid_t pid = spawn_async([] { exec_sh("sleep 0.1"); });
int status;
pid_t res = process_util::wait_for_completion_nohang(pid, &status);
@ -23,8 +23,8 @@ TEST(ForkDetached, is_detached) {
EXPECT_FALSE(WIFEXITED(status));
}
TEST(ForkDetached, exit_code) {
pid_t pid = fork_detached([] { exec_sh("exit 42"); });
TEST(SpawnAsync, exit_code) {
pid_t pid = spawn_async([] { exec_sh("exit 42"); });
int status = 0;
pid_t res = waitpid(pid, &status, 0);