Example #1
0
// 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();
}
Example #2
0
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));
}
Example #3
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();
}
Example #4
0
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));
}
Example #5
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();
}
Example #6
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));
}
Example #7
0
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));
}