In a Linux application I'm spawning multiple programs via fork
/execvp
and redirect the standard IO streams to a pipe for IPC. I spawn a child process, write some data into the child stdin pipe, close stdin, and then read the child response from the stdout pipe. This worked fine, until I've executed multiple child processes at the same time, using independent threads per child process.
As soon I increase the number of threads, I often find that the child processes hang while reading from stdin – although read
should immediately exit with EOF because the stdin pipe has been closed by the parent process.
I've managed to reproduce this behaviour in the following test program. On my systems (Fedora 23, Ubuntu 14.04; g++
4.9, 5, 6 and clang 3.7) the program often simply hangs after three or four child processes have exited. Child processes that have not exited are hanging at read()
. Killing any child process that has not exited causes all other child processes to magically wake up from read()
and the program continues normally.
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <sys/fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
#define HANDLE_ERR(CODE) \
{ \
if ((CODE) < 0) { \
perror("error"); \
quick_exit(1); \
} \
}
int main()
{
std::mutex stdout_mtx;
std::vector<std::thread> threads;
for (size_t i = 0; i < 8; i++) {
threads.emplace_back([&stdout_mtx] {
int pfd[2]; // Create the communication pipe
HANDLE_ERR(pipe(pfd));
pid_t pid; // Fork this process
HANDLE_ERR(pid = fork());
if (pid == 0) {
HANDLE_ERR(close(pfd[1])); // Child, close write end of pipe
for (;;) { // Read data from pfd[0] until EOF or other error
char buffer;
ssize_t bytes;
HANDLE_ERR(bytes = read(pfd[0], &buffer, 1));
if (bytes < 1) {
break;
}
// Allow time for thread switching
std::this_thread::sleep_for(std::chrono::milliseconds(
100)); // This sleep is crucial for the bug to occur
}
quick_exit(0); // Exit, do not call C++ destructors
}
else {
{ // Some debug info
std::lock_guard<std::mutex> lock(stdout_mtx);
std::cout << "Created child " << pid << std::endl;
}
// Close the read end of the pipe
HANDLE_ERR(close(pfd[0]));
// Send some data to the child process
HANDLE_ERR(write(pfd[1], "abcdef\n", 7));
// Close the write end of the pipe, wait for the process to exit
int status;
HANDLE_ERR(close(pfd[1]));
HANDLE_ERR(waitpid(pid, &status, 0));
{ // Some debug info
std::lock_guard<std::mutex> lock(stdout_mtx);
std::cout << "Child " << pid << " exited with status "
<< status << std::endl;
}
}
});
}
// Wait for all threads to complete
for (auto &thread : threads) {
thread.join();
}
return 0;
}
Compile using
g++ test.cpp -o test -lpthread --std=c++11
Note that I'm perfectly aware that mixing fork
and threads is potentially dangerous, but please keep in mind that in the original code I'm immediately calling execvp
after forking, and that I don't have any shared state between the child child process and the main program, except for the pipes specifically created for IPC. My original code (without the threading part) can be found here.
To me this almost seems like a bug in the Linux kernel, since the program continues correctly as soon as I kill any of the hanging child processes.
fork
ed off has that pipe open too. The pipe will only close when the last reference to its other end is closed. – David Schwartz