Linux:fork&execv,等待subprocess挂起

我写了一个辅助函数来启动一个使用fork()和execv()这个答案的过程 。 它用于启动例如mysqldump来进行数据库备份。 代码在不同程序的不同位置工作得很好。

现在我击中了一个失败的星座:这是一个呼叫systemctl来停止一个单位。 运行systemctl工作,单位停止。 但是在中间进程中,等待()进程的时候,wait()挂起,直到超时过程结束。 如果我检查,如果工作进程完成kill(),我可以告诉它。

重要提示:除了wait()不表示工作进程结束之外,程序不会出现错误或故障。 我的代码中有什么(见下文)是不正确的,可能会触发这种行为? 我已经读过Threads和fork():在混合它们之前三思,但是我找不到与我的问题有关的任何东西。

奇怪的是:程序中使用了深度,深度和深度的JSON-RPC。 如果我停用使用JSON-RPC的代码,一切正常!?

环境:使用该function的程序是一个multithreading应用程序。 所有线程的信号都被阻塞。 主线程通过sigtimedwait()处理信号。

代码(通过std :: cout输出日志logging的生产代码)和样本主函数:

#include <iostream> #include <unistd.h> #include <sys/wait.h> namespace { bool checkStatus(const int status) { return( WIFEXITED(status) && ( WEXITSTATUS(status) == 0 ) ); } } bool startProcess(const char* const path, const char* const argv[], const unsigned int timeoutInSeconds, pid_t& processId, const int* const fileDescriptor) { auto result = true; const pid_t intermediatePid = fork(); if(intermediatePid == 0) { // intermediate process std::cout << "Intermediate process: Started (" << getpid() << ")." << std::endl; const pid_t workerPid = fork(); if(workerPid == 0) { // worker process if(fileDescriptor) { std::cout << "Worker process: Redirecting file descriptor to stdin." << std::endl; const auto dupResult = dup2(*fileDescriptor, STDIN_FILENO); if(-1 == dupResult) { std::cout << "Worker process: Duplication of file descriptor failed." << std::endl; _exit(EXIT_FAILURE); } } execv(path, const_cast<char**>(argv)); std::cout << "Intermediate process: Worker failed!" << std::endl; _exit(EXIT_FAILURE); } else if(-1 == workerPid) { std::cout << "Intermediate process: Starting worker failed!" << std::endl; _exit(EXIT_FAILURE); } const pid_t timeoutPid = fork(); if(timeoutPid == 0) { // timeout process std::cout << "Timeout process: Started (" << getpid() << ")." << std::endl; sleep(timeoutInSeconds); std::cout << "Timeout process: Finished." << std::endl; _exit(EXIT_SUCCESS); } else if(-1 == timeoutPid) { std::cout << "Intermediate process: Starting timeout process failed." << std::endl; kill(workerPid, SIGKILL); std::cout << "Intermediate process: Finished." << std::endl; _exit(EXIT_FAILURE); } // --------------------------------------- // This code is only used for double checking if the worker is still running. // The if condition never evaluated to true in my tests. const auto killResult = kill(workerPid, 0); if((-1 == killResult) && (ESRCH == errno)) { std::cout << "Intermediate process: Worker is not running." << std::endl; } // --------------------------------------- std::cout << "Intermediate process: Waiting for child processes." << std::endl; int status = -1; const pid_t exitedPid = wait(&status); // --------------------------------------- // This code is only used for double checking if the worker is still running. // The if condition evaluates to true in the case of an error. const auto killResult2 = kill(workerPid, 0); if((-1 == killResult2) && (ESRCH == errno)) { std::cout << "Intermediate process: Worker is not running." << std::endl; } // --------------------------------------- std::cout << "Intermediate process: Child process finished. Status: " << status << "." << std::endl; if(exitedPid == workerPid) { std::cout << "Intermediate process: Killing timeout process." << std::endl; kill(timeoutPid, SIGKILL); } else { std::cout << "Intermediate process: Killing worker process." << std::endl; kill(workerPid, SIGKILL); std::cout << "Intermediate process: Waiting for worker process to terminate." << std::endl; wait(nullptr); std::cout << "Intermediate process: Finished." << std::endl; _exit(EXIT_FAILURE); } std::cout << "Intermediate process: Waiting for timeout process to terminate." << std::endl; wait(nullptr); std::cout << "Intermediate process: Finished." << std::endl; _exit(checkStatus(status) ? EXIT_SUCCESS : EXIT_FAILURE); } else if(-1 == intermediatePid) { // error std::cout << "Parent process: Error starting intermediate process!" << std::endl; result = false; } else { // parent process std::cout << "Parent process: Intermediate process started. PID: " << intermediatePid << "." << std::endl; processId = intermediatePid; } return(result); } bool waitForProcess(const pid_t processId) { int status = 0; const auto waitResult = waitpid(processId, &status, 0); auto result = false; if(waitResult == processId) { result = checkStatus(status); } return(result); } int main() { pid_t pid = 0; const char* const path = "/bin/ls"; const char* argv[] = { "/bin/ls", "--help", nullptr }; const unsigned int timeoutInS = 5; const auto startResult = startProcess(path, argv, timeoutInS, pid, nullptr); if(startResult) { const auto waitResult = waitForProcess(pid); std::cout << "waitForProcess returned " << waitResult << "." << std::endl; } else { std::cout << "startProcess failed!" << std::endl; } } 

编辑

预期的产出应该包含

  • 中级进程:等待subprocess。
  • 中级进程:subprocess完成。 状态:0。
  • 中级进程:杀死超时进程。

在出错的情况下,输出看起来像这样

  • 中级进程:等待subprocess。
  • 中级进程:subprocess完成。 状态:-1
  • 中间过程:杀死工作进程。

运行示例代码时,您将很可能看到预期的输出。 我不能在一个简单的例子中重现错误的结果。

我发现这个问题:

在函数mg_start的猫鼬(JSON-RPC使用猫鼬)源中,我发现了下面的代码

 #if !defined(_WIN32) && !defined(__SYMBIAN32__) // Ignore SIGPIPE signal, so if browser cancels the request, it // won't kill the whole process. (void) signal(SIGPIPE, SIG_IGN); // Also ignoring SIGCHLD to let the OS to reap zombies properly. (void) signal(SIGCHLD, SIG_IGN); #endif // !_WIN32 

(void) signal(SIGCHLD, SIG_IGN);

造成的

如果父节点执行wait(),则只有当所有子节点都退出时,此调用才会返回,然后返回-1,并将errno设置为ECHILD。

5.5节所述:Voodoo:wait和SIGCHLD

这也在等待(2)的手册页中描述

错误[…]

ECHILD […](如果SIGCHLD的操作设置为SIG_IGN,这可能会发生在自己的孩子身上,另请参阅关于线程的Linux注释部分。

我愚蠢的是不正确地检查返回值。 尝试之前

 if(exitedPid == workerPid) { 

我应该检查exitedPid!= -1

如果我这样做, errno给我ECHILD 。 如果我首先知道,我会阅读手册页,可能会更快地发现问题…

顽皮的猫鼬只是为了处理信号处理,不管应用程序想要做什么。 此外,猫鼬在用mg_stop停止时不会恢复信号处理的改变。

其他信息:导致此问题的代码在2013年9月用猫鼬进行了更改。