// 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)); }
// 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(); }
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)); }
// 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, 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)); }