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:
parent
ccf14d9816
commit
47483a94f1
@ -12,11 +12,14 @@ namespace process_util {
|
|||||||
|
|
||||||
void redirect_stdio_to_dev_null();
|
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(char* cmd, char** args);
|
||||||
void exec_sh(const char* cmd);
|
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(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(int* status_addr, int waitflags = 0);
|
||||||
pid_t wait_for_completion_nohang(pid_t process_id, int* status);
|
pid_t wait_for_completion_nohang(pid_t process_id, int* status);
|
||||||
|
@ -33,7 +33,7 @@ command<output_policy::IGNORED>::~command() {
|
|||||||
* Execute the command
|
* Execute the command
|
||||||
*/
|
*/
|
||||||
int command<output_policy::IGNORED>::exec(bool wait_for_completion) {
|
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) {
|
if (wait_for_completion) {
|
||||||
auto status = wait();
|
auto status = wait();
|
||||||
m_forkpid = -1;
|
m_forkpid = -1;
|
||||||
|
@ -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.
|
* Processes spawned this way need to be waited on by the caller.
|
||||||
*
|
|
||||||
* Use this if you want to run a command and just forget about it.
|
|
||||||
*
|
|
||||||
* \returns The PID of the child process
|
|
||||||
*/
|
*/
|
||||||
pid_t fork_detached(std::function<void()> const& lambda) {
|
pid_t spawn_async(std::function<void()> const& lambda) {
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
switch (pid) {
|
switch (pid) {
|
||||||
case -1:
|
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
|
* 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
|
* Wait for child process
|
||||||
*/
|
*/
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
using namespace polybar;
|
using namespace polybar;
|
||||||
using namespace process_util;
|
using namespace process_util;
|
||||||
|
|
||||||
TEST(ForkDetached, is_detached) {
|
TEST(SpawnAsync, is_async) {
|
||||||
pid_t pid = fork_detached([] { exec_sh("sleep 0.1"); });
|
pid_t pid = spawn_async([] { exec_sh("sleep 0.1"); });
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
pid_t res = process_util::wait_for_completion_nohang(pid, &status);
|
pid_t res = process_util::wait_for_completion_nohang(pid, &status);
|
||||||
@ -23,8 +23,8 @@ TEST(ForkDetached, is_detached) {
|
|||||||
EXPECT_FALSE(WIFEXITED(status));
|
EXPECT_FALSE(WIFEXITED(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ForkDetached, exit_code) {
|
TEST(SpawnAsync, exit_code) {
|
||||||
pid_t pid = fork_detached([] { exec_sh("exit 42"); });
|
pid_t pid = spawn_async([] { exec_sh("exit 42"); });
|
||||||
int status = 0;
|
int status = 0;
|
||||||
pid_t res = waitpid(pid, &status, 0);
|
pid_t res = waitpid(pid, &status, 0);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user