// This test checks that the we can reap a child process and obtain // the correct exit status. TEST(Reap, ChildProcess) { ASSERT_TRUE(GTEST_IS_THREADSAFE); // The child process sleeps and will be killed by the parent. Try<ProcessTree> tree = Fork(None(), Exec("sleep 10"))(); ASSERT_SOME(tree); pid_t child = tree.get(); // Reap the child process. Future<Option<int> > status = process::reap(child); // Now kill the child. EXPECT_EQ(0, kill(child, SIGKILL)); Clock::pause(); // Now advance time until the reaper reaps the child. while (status.isPending()) { Clock::advance(Seconds(1)); Clock::settle(); } AWAIT_READY(status); // Check if the status is correct. ASSERT_SOME(status.get()); int status_ = status.get().get(); ASSERT_TRUE(WIFSIGNALED(status_)); ASSERT_EQ(SIGKILL, WTERMSIG(status_)); Clock::resume(); }
// This test checks that we can reap a non-child process, in terms // of receiving the termination notification. TEST(Reap, NonChildProcess) { // The child process creates a grandchild and then exits. The // grandchild sleeps for 10 seconds. The process tree looks like: // -+- child exit 0 // \-+- grandchild sleep 10 // After the child exits, the grandchild is going to be re-parented // by 'init', like this: // -+- child (exit 0) // -+- grandchild sleep 10 Try<ProcessTree> tree = Fork(None(), Fork(Exec("sleep 10")), Exec("exit 0"))(); ASSERT_SOME(tree); ASSERT_EQ(1u, tree.get().children.size()); pid_t grandchild = tree.get().children.front(); // Reap the grandchild process. Future<Option<int> > status = process::reap(grandchild); EXPECT_TRUE(status.isPending()); // Now kill the grandchild. // NOTE: We send a SIGKILL here because sometimes the grandchild // process seems to be in a hung state and not responding to // SIGTERM/SIGINT. EXPECT_EQ(0, kill(grandchild, SIGKILL)); Clock::pause(); // Now advance time until the Reaper reaps the grandchild. while (status.isPending()) { Clock::advance(Seconds(1)); Clock::settle(); } AWAIT_READY(status); // The status is None because pid is not an immediate child. ASSERT_NONE(status.get()) << status.get().get(); // Reap the child as well to clean up after ourselves. status = process::reap(tree.get().process.pid); // Now advance time until the child is reaped. while (status.isPending()) { Clock::advance(Seconds(1)); Clock::settle(); } // Check if the status is correct. ASSERT_SOME(status.get()); int status_ = status.get().get(); ASSERT_TRUE(WIFEXITED(status_)); ASSERT_EQ(0, WEXITSTATUS(status_)); Clock::resume(); }
TEST_F(OsTest, Children) { Try<set<pid_t> > children = os::children(getpid()); ASSERT_SOME(children); EXPECT_EQ(0u, children.get().size()); Try<ProcessTree> tree = Fork(None(), // Child. Fork(Exec("sleep 10")), // Grandchild. Exec("sleep 10"))(); ASSERT_SOME(tree); ASSERT_EQ(1u, tree.get().children.size()); pid_t child = tree.get().process.pid; pid_t grandchild = tree.get().children.front().process.pid; // Ensure the non-recursive children does not include the // grandchild. children = os::children(getpid(), false); ASSERT_SOME(children); EXPECT_EQ(1u, children.get().size()); EXPECT_EQ(1u, children.get().count(child)); children = os::children(getpid()); ASSERT_SOME(children); // Depending on whether or not the shell has fork/exec'ed in each // above 'Exec', we could have 2 or 4 children. That is, some shells // might simply for exec the command above (i.e., 'sleep 10') while // others might fork/exec the command, keeping around a 'sh -c' // process as well. EXPECT_LE(2u, children.get().size()); EXPECT_GE(4u, children.get().size()); EXPECT_EQ(1u, children.get().count(child)); EXPECT_EQ(1u, children.get().count(grandchild)); // Cleanup by killing the descendant processes. EXPECT_EQ(0, kill(grandchild, SIGKILL)); EXPECT_EQ(0, kill(child, SIGKILL)); // We have to reap the child for running the tests in repetition. ASSERT_EQ(child, waitpid(child, NULL, 0)); }
// Check that we can reap a child process that is already exited. TEST(Reap, TerminatedChildProcess) { ASSERT_TRUE(GTEST_IS_THREADSAFE); // The child process immediately exits. Try<ProcessTree> tree = Fork(None(), Exec("exit 0"))(); ASSERT_SOME(tree); pid_t child = tree.get(); ASSERT_SOME(os::process(child)); // Make sure the process is transitioned into the zombie // state before we reap it. while (true) { const Result<os::Process>& process = os::process(child); ASSERT_SOME(process) << "Process " << child << " reaped unexpectedly"; if (process.get().zombie) { break; } os::sleep(Milliseconds(1)); } // Now that it's terminated, attempt to reap it. Future<Option<int> > status = process::reap(child); // Advance time until the reaper sends the notification. Clock::pause(); while (status.isPending()) { Clock::advance(Seconds(1)); Clock::settle(); } AWAIT_READY(status); // Expect to get the correct status. ASSERT_SOME(status.get()); int status_ = status.get().get(); ASSERT_TRUE(WIFEXITED(status_)); ASSERT_EQ(0, WEXITSTATUS(status_)); Clock::resume(); }
TEST_F(OsTest, Pstree) { Try<ProcessTree> tree = os::pstree(getpid()); ASSERT_SOME(tree); EXPECT_EQ(0u, tree.get().children.size()) << stringify(tree.get()); tree = Fork(None(), // Child. Fork(Exec("sleep 10")), // Grandchild. Exec("sleep 10"))(); ASSERT_SOME(tree); // Depending on whether or not the shell has fork/exec'ed, // we could have 1 or 2 direct children. That is, some shells // might simply exec the command above (i.e., 'sleep 10') while // others might fork/exec the command, keeping around a 'sh -c' // process as well. ASSERT_LE(1u, tree.get().children.size()); ASSERT_GE(2u, tree.get().children.size()); pid_t child = tree.get().process.pid; pid_t grandchild = tree.get().children.front().process.pid; // Now check pstree again. tree = os::pstree(child); ASSERT_SOME(tree); EXPECT_EQ(child, tree.get().process.pid); ASSERT_LE(1u, tree.get().children.size()); ASSERT_GE(2u, tree.get().children.size()); // Cleanup by killing the descendant processes. EXPECT_EQ(0, kill(grandchild, SIGKILL)); EXPECT_EQ(0, kill(child, SIGKILL)); // We have to reap the child for running the tests in repetition. ASSERT_EQ(child, waitpid(child, NULL, 0)); }
TEST_F(OsTest, KilltreeNoRoot) { Try<ProcessTree> tree = Fork(&dosetsid, // Child. Fork(None(), // Grandchild. Fork(None(), Exec("sleep 100")), Exec("sleep 100")), Exec("exit 0"))(); ASSERT_SOME(tree); // The process tree we instantiate initially looks like this: // // -+- child exit 0 [new session and process group leader] // \-+- grandchild sleep 100 // \-+- great grandchild sleep 100 // // But becomes the following tree after the child exits: // // -+- child (exited 0) // \-+- grandchild sleep 100 // \-+- great grandchild sleep 100 // // And gets reparented when we reap the child: // // -+- new parent // \-+- grandchild sleep 100 // \-+- great grandchild sleep 100 // Grab the pids from the instantiated process tree. ASSERT_EQ(1u, tree.get().children.size()); ASSERT_EQ(1u, tree.get().children.front().children.size()); pid_t child = tree.get(); pid_t grandchild = tree.get().children.front(); pid_t greatGrandchild = tree.get().children.front().children.front(); // Wait for the child to exit. Duration elapsed = Duration::zero(); while (true) { Result<os::Process> process = os::process(child); ASSERT_FALSE(process.isError()); if (process.get().zombie) { break; } if (elapsed > Seconds(1)) { FAIL() << "Child process " << stringify(child) << " did not terminate"; } os::sleep(Milliseconds(5)); elapsed += Milliseconds(5); } // Ensure we reap our child now. EXPECT_SOME(os::process(child)); EXPECT_TRUE(os::process(child).get().zombie); ASSERT_EQ(child, waitpid(child, NULL, 0)); // Check the grandchild and great grandchild are still running. ASSERT_TRUE(os::exists(grandchild)); ASSERT_TRUE(os::exists(greatGrandchild)); // Check the subtree has been reparented: the parent is no longer // child (the root of the tree), and that the process is not a // zombie. This is done because some systems run a secondary init // process for the user (init --user) that does not have pid 1, // meaning we can't just check that the parent pid == 1. Result<os::Process> _grandchild = os::process(grandchild); ASSERT_SOME(_grandchild); ASSERT_NE(child, _grandchild.get().parent); ASSERT_FALSE(_grandchild.get().zombie); // Check to see if we're in a jail on FreeBSD in case we've been // reparented to pid 1 #if __FreeBSD__ if (!isJailed()) { #endif // Check that grandchild's parent is also not a zombie. Result<os::Process> currentParent = os::process(_grandchild.get().parent); ASSERT_SOME(currentParent); ASSERT_FALSE(currentParent.get().zombie); #ifdef __FreeBSD__ } #endif // Kill the process tree. Even though the root process has exited, // we specify to follow sessions and groups which should kill the // grandchild and greatgrandchild. Try<list<ProcessTree>> trees = os::killtree(child, SIGKILL, true, true); ASSERT_SOME(trees); EXPECT_FALSE(trees.get().empty()); // All processes should be reparented and reaped by init. elapsed = Duration::zero(); while (true) { if (os::process(grandchild).isNone() && os::process(greatGrandchild).isNone()) { break; } if (elapsed > Seconds(10)) { FAIL() << "Processes were not reaped after killtree invocation"; } os::sleep(Milliseconds(5)); elapsed += Milliseconds(5); } EXPECT_NONE(os::process(grandchild)); EXPECT_NONE(os::process(greatGrandchild)); }
TEST_F(OsTest, Killtree) { Try<ProcessTree> tree = Fork(&dosetsid, // Child. Fork(None(), // Grandchild. Fork(None(), // Great-grandchild. Fork(&dosetsid, // Great-great-granchild. Exec("sleep 10")), Exec("sleep 10")), Exec("exit 0")), Exec("sleep 10"))(); ASSERT_SOME(tree); // The process tree we instantiate initially looks like this: // // -+- child sleep 10 // \-+- grandchild exit 0 // \-+- greatGrandchild sleep 10 // \--- greatGreatGrandchild sleep 10 // // But becomes two process trees after the grandchild exits: // // -+- child sleep 10 // \--- grandchild (exit 0) // // -+- greatGrandchild sleep 10 // \--- greatGreatGrandchild sleep 10 // Grab the pids from the instantiated process tree. ASSERT_EQ(1u, tree.get().children.size()); ASSERT_EQ(1u, tree.get().children.front().children.size()); ASSERT_EQ(1u, tree.get().children.front().children.front().children.size()); pid_t child = tree.get(); pid_t grandchild = tree.get().children.front(); pid_t greatGrandchild = tree.get().children.front().children.front(); pid_t greatGreatGrandchild = tree.get().children.front().children.front().children.front(); // Now wait for the grandchild to exit splitting the process tree. Duration elapsed = Duration::zero(); while (true) { Result<os::Process> process = os::process(grandchild); ASSERT_FALSE(process.isError()); if (process.isNone() || process.get().zombie) { break; } if (elapsed > Seconds(10)) { FAIL() << "Granchild process '" << process.get().pid << "' " << "(" << process.get().command << ") did not terminate"; } os::sleep(Milliseconds(5)); elapsed += Milliseconds(5); } // Kill the process tree and follow sessions and groups to make sure // we cross the broken link due to the grandchild. Try<list<ProcessTree> > trees = os::killtree(child, SIGKILL, true, true); ASSERT_SOME(trees); EXPECT_EQ(2u, trees.get().size()) << stringify(trees.get()); foreach (const ProcessTree& tree, trees.get()) { if (tree.process.pid == child) { // The 'grandchild' _might_ still be in the tree, just zombied, // unless the 'child' reaps the 'grandchild', which may happen // if the shell "sticks around" (i.e., some invocations of 'sh // -c' will 'exec' the command which will likely not do any // reaping, but in other cases an invocation of 'sh -c' will not // 'exec' the command, for example when the command is a // sequence of commands separated by ';'). EXPECT_FALSE(tree.contains(greatGrandchild)) << tree; EXPECT_FALSE(tree.contains(greatGreatGrandchild)) << tree; } else if (tree.process.pid == greatGrandchild) { EXPECT_TRUE(tree.contains(greatGreatGrandchild)) << tree; } else { FAIL() << "Not expecting a process tree rooted at " << tree.process.pid << "\n" << tree; } } // All processes should be reaped since we've killed everything. // The direct child must be reaped by us below. elapsed = Duration::zero(); while (true) { Result<os::Process> _child = os::process(child); ASSERT_SOME(_child); if (os::process(greatGreatGrandchild).isNone() && os::process(greatGrandchild).isNone() && os::process(grandchild).isNone() && _child.get().zombie) { break; } if (elapsed > Seconds(10)) { FAIL() << "Processes were not reaped after killtree invocation"; } os::sleep(Milliseconds(5)); elapsed += Milliseconds(5); } // Expect the pids to be wiped! EXPECT_NONE(os::process(greatGreatGrandchild)); EXPECT_NONE(os::process(greatGrandchild)); EXPECT_NONE(os::process(grandchild)); EXPECT_SOME(os::process(child)); EXPECT_TRUE(os::process(child).get().zombie); // We have to reap the child for running the tests in repetition. ASSERT_EQ(child, waitpid(child, NULL, 0)); }
bool LyXComm::pipeServer() { DWORD i; DWORD error; for (i = 0; i < MAX_PIPES; ++i) { bool const is_outpipe = i >= MAX_CLIENTS; DWORD const open_mode = is_outpipe ? PIPE_ACCESS_OUTBOUND : PIPE_ACCESS_INBOUND; string const pipename = external_path(pipeName(i)); // Manual-reset event, initial state = signaled event_[i] = CreateEvent(NULL, TRUE, TRUE, NULL); if (!event_[i]) { error = GetLastError(); lyxerr << "LyXComm: Could not create event for pipe " << pipename << "\nLyXComm: " << errormsg(error) << endl; return false; } pipe_[i].overlap.hEvent = event_[i]; pipe_[i].iobuf.erase(); pipe_[i].handle = CreateNamedPipe(pipename.c_str(), open_mode | FILE_FLAG_OVERLAPPED, PIPE_WAIT, MAX_CLIENTS, PIPE_BUFSIZE, PIPE_BUFSIZE, PIPE_TIMEOUT, NULL); if (pipe_[i].handle == INVALID_HANDLE_VALUE) { error = GetLastError(); lyxerr << "LyXComm: Could not create pipe " << pipename << "\nLyXComm: " << errormsg(error) << endl; return false; } if (!startPipe(i)) return false; pipe_[i].state = pipe_[i].pending_io ? CONNECTING_STATE : (is_outpipe ? WRITING_STATE : READING_STATE); } // Add the stopserver_ event event_[MAX_PIPES] = stopserver_; // We made it! LYXERR(Debug::LYXSERVER, "LyXComm: Connection established"); ready_ = true; outbuf_.erase(); DWORD status; bool success; while (!checkStopServer()) { // Indefinitely wait for the completion of an overlapped // read, write, or connect operation. DWORD wait = WaitForMultipleObjects(MAX_PIPES + 1, event_, FALSE, INFINITE); // Determine which pipe instance completed the operation. i = wait - WAIT_OBJECT_0; LASSERT(i >= 0 && i <= MAX_PIPES, /**/); // Check whether we were waked up for stopping the pipe server. if (i == MAX_PIPES) break; bool const is_outpipe = i >= MAX_CLIENTS; // Get the result if the operation was pending. if (pipe_[i].pending_io) { success = GetOverlappedResult(pipe_[i].handle, &pipe_[i].overlap, &status, FALSE); switch (pipe_[i].state) { case CONNECTING_STATE: // Pending connect operation if (!success) { error = GetLastError(); lyxerr << "LyXComm: " << errormsg(error) << endl; if (!resetPipe(i, true)) return false; continue; } pipe_[i].state = is_outpipe ? WRITING_STATE : READING_STATE; break; case READING_STATE: // Pending read operation LASSERT(!is_outpipe, /**/); if (!success || status == 0) { if (!resetPipe(i, !success)) return false; continue; } pipe_[i].nbytes = status; pipe_[i].state = WRITING_STATE; break; case WRITING_STATE: // Pending write operation LASSERT(is_outpipe, /**/); // Let's see whether we have a reply if (!outbuf_.empty()) { // Yep. Deliver it to all pipe // instances if we get ownership // of the mutex, otherwise we'll // try again the next round. DWORD result = WaitForSingleObject( outbuf_mutex_, 200); if (result == WAIT_OBJECT_0) { DWORD j = MAX_CLIENTS; while (j < MAX_PIPES) { pipe_[j].iobuf = outbuf_; ++j; } outbuf_.erase(); } ReleaseMutex(outbuf_mutex_); } if (pipe_[i].iobuf.empty()) pipe_[i].pending_io = false; break; } } // Operate according to the pipe state switch (pipe_[i].state) { case READING_STATE: // The pipe instance is connected to a client // and is ready to read a request. LASSERT(!is_outpipe, /**/); success = ReadFile(pipe_[i].handle, pipe_[i].readbuf, PIPE_BUFSIZE - 1, &pipe_[i].nbytes, &pipe_[i].overlap); if (success && pipe_[i].nbytes != 0) { // The read operation completed successfully. pipe_[i].pending_io = false; pipe_[i].state = WRITING_STATE; continue; } error = GetLastError(); if (!success && error == ERROR_IO_PENDING) { // The read operation is still pending. pipe_[i].pending_io = true; continue; } success = error == ERROR_BROKEN_PIPE; // Client closed connection (ERROR_BROKEN_PIPE) or // an error occurred; in either case, reset the pipe. if (!success) { lyxerr << "LyXComm: " << errormsg(error) << endl; if (!pipe_[i].iobuf.empty()) { lyxerr << "LyXComm: truncated command: " << pipe_[i].iobuf << endl; pipe_[i].iobuf.erase(); } } if (!resetPipe(i, !success)) return false; break; case WRITING_STATE: if (!is_outpipe) { // The request was successfully read // from the client; commit it. ReadReadyEvent * event = new ReadReadyEvent(i); QCoreApplication::postEvent(this, static_cast<QEvent *>(event)); // Wait for completion while (pipe_[i].nbytes && !checkStopServer(100)) ; pipe_[i].pending_io = false; pipe_[i].state = READING_STATE; continue; } // This is an output pipe instance. Initiate the // overlapped write operation or monitor its progress. if (pipe_[i].pending_io) { success = WriteFile(pipe_[i].handle, pipe_[i].iobuf.c_str(), pipe_[i].iobuf.length(), &status, &pipe_[i].overlap); } if (success && !pipe_[i].iobuf.empty() && status == pipe_[i].iobuf.length()) { // The write operation completed successfully. pipe_[i].iobuf.erase(); pipe_[i].pending_io = false; if (!resetPipe(i)) return false; continue; } error = GetLastError(); if (success && error == ERROR_IO_PENDING) { // The write operation is still pending. // We get here when a reader is started // well before a reply is ready, so delay // a bit in order to not burden the cpu. checkStopServer(100); pipe_[i].pending_io = true; continue; } success = error == ERROR_NO_DATA; // Client closed connection (ERROR_NO_DATA) or // an error occurred; in either case, reset the pipe. if (!success) { lyxerr << "LyXComm: Error sending message: " << pipe_[i].iobuf << "\nLyXComm: " << errormsg(error) << endl; } if (!resetPipe(i, !success)) return false; break; } }