Optionally, kill subprocess when parent dies
authorTudor Bosman <tudorb@fb.com>
Thu, 28 Mar 2013 23:48:51 +0000 (16:48 -0700)
committerJordan DeLong <jdelong@fb.com>
Sun, 21 Apr 2013 20:19:31 +0000 (13:19 -0700)
Summary: Non-portable.

Test Plan: test added

Reviewed By: lucian@fb.com

FB internal diff: D755528

folly/Subprocess.cpp
folly/Subprocess.h
folly/test/SubprocessTest.cpp
folly/test/SubprocessTestParentDeathHelper.cpp [new file with mode: 0644]

index e2d5f7bd818a9ac55b12ee14e68b0c03408c7385..8421bd9eadeff9ba3b938752e9d96dc0a015685a 100644 (file)
@@ -16,6 +16,7 @@
 
 #include "folly/Subprocess.h"
 
 
 #include "folly/Subprocess.h"
 
+#include <sys/prctl.h>
 #include <fcntl.h>
 #include <poll.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <poll.h>
 #include <unistd.h>
@@ -351,6 +352,14 @@ void Subprocess::runChild(const char* executable,
     }
   }
 
     }
   }
 
+  // Opt to receive signal on parent death, if requested
+  if (options.parentDeathSignal_ != 0) {
+    int r = prctl(PR_SET_PDEATHSIG, options.parentDeathSignal_, 0, 0, 0);
+    if (r == -1) {
+      abort();
+    }
+  }
+
   // Now, finally, exec.
   int r;
   if (options.usePath_) {
   // Now, finally, exec.
   int r;
   if (options.usePath_) {
index 47ec42601133649a8b7cc38169ff5f613bd7ab9c..5c53a319073b8a1747f9806d5dd915aa2fece643 100644 (file)
@@ -179,7 +179,11 @@ class Subprocess : private boost::noncopyable {
   class Options : private boost::orable<Options> {
     friend class Subprocess;
    public:
   class Options : private boost::orable<Options> {
     friend class Subprocess;
    public:
-    Options() : closeOtherFds_(false), usePath_(false) { }
+    Options()
+      : closeOtherFds_(false),
+        usePath_(false),
+        parentDeathSignal_(0) {
+    }
 
     /**
      * Change action for file descriptor fd.
 
     /**
      * Change action for file descriptor fd.
@@ -233,6 +237,14 @@ class Subprocess : private boost::noncopyable {
      */
     Options& usePath() { usePath_ = true; return *this; }
 
      */
     Options& usePath() { usePath_ = true; return *this; }
 
+    /**
+     * Child will receive a signal when the parent exits.
+     */
+    Options& parentDeathSignal(int sig) {
+      parentDeathSignal_ = sig;
+      return *this;
+    }
+
     /**
      * Helpful way to combine Options.
      */
     /**
      * Helpful way to combine Options.
      */
@@ -243,6 +255,7 @@ class Subprocess : private boost::noncopyable {
     FdMap fdActions_;
     bool closeOtherFds_;
     bool usePath_;
     FdMap fdActions_;
     bool closeOtherFds_;
     bool usePath_;
+    int parentDeathSignal_;
   };
 
   static Options pipeStdin() { return Options().stdin(PIPE); }
   };
 
   static Options pipeStdin() { return Options().stdin(PIPE); }
index ef425068e33f7c832830cea281876ebdb5aa71cc..5d3fd3a8deb9d592b11edc81957672e2baf5a161 100644 (file)
@@ -16,6 +16,8 @@
 
 #include "folly/Subprocess.h"
 
 
 #include "folly/Subprocess.h"
 
+#include <unistd.h>
+
 #include <glog/logging.h>
 #include <gtest/gtest.h>
 
 #include <glog/logging.h>
 #include <gtest/gtest.h>
 
@@ -23,6 +25,7 @@
 #include "folly/experimental/Gen.h"
 #include "folly/experimental/FileGen.h"
 #include "folly/experimental/StringGen.h"
 #include "folly/experimental/Gen.h"
 #include "folly/experimental/FileGen.h"
 #include "folly/experimental/StringGen.h"
+#include "folly/experimental/io/FsUtil.h"
 
 using namespace folly;
 
 
 using namespace folly;
 
@@ -56,6 +59,35 @@ TEST(SimpleSubprocessTest, ShellExitsWithError) {
   EXPECT_EQ(1, proc.wait().exitStatus());
 }
 
   EXPECT_EQ(1, proc.wait().exitStatus());
 }
 
+TEST(ParentDeathSubprocessTest, ParentDeathSignal) {
+  // Find out where we are.
+  static constexpr size_t pathLength = 2048;
+  char buf[pathLength];
+  int r = readlink("/proc/self/exe", buf, pathLength);
+  CHECK_ERR(r >= 0);
+  buf[r] = '\0';
+
+  fs::path helper(buf);
+  helper.remove_filename();
+  helper /= "subprocess_test_parent_death_helper";
+
+  fs::path tempFile(fs::temp_directory_path() / fs::unique_path());
+
+  std::vector<std::string> args {helper.string(), tempFile.string()};
+  Subprocess proc(args);
+  // The helper gets killed by its child, see details in
+  // SubprocessTestParentDeathHelper.cpp
+  ASSERT_EQ(SIGKILL, proc.wait().killSignal());
+
+  // Now wait for the file to be created, see details in
+  // SubprocessTestParentDeathHelper.cpp
+  while (!fs::exists(tempFile)) {
+    usleep(20000);  // 20ms
+  }
+
+  fs::remove(tempFile);
+}
+
 TEST(PopenSubprocessTest, PopenRead) {
   Subprocess proc("ls /", Subprocess::pipeStdout());
   int found = 0;
 TEST(PopenSubprocessTest, PopenRead) {
   Subprocess proc("ls /", Subprocess::pipeStdout());
   int found = 0;
diff --git a/folly/test/SubprocessTestParentDeathHelper.cpp b/folly/test/SubprocessTestParentDeathHelper.cpp
new file mode 100644 (file)
index 0000000..2eae7a3
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This is a helper for the parentDeathSignal test in SubprocessTest.cpp.
+//
+// Basically, we create two processes, a parent and a child, and set the
+// child to receive SIGUSR1 when the parent exits.  We set the child to
+// create a file when that happens.  The child then kills the parent; the test
+// will verify that the file actually gets created, which means that everything
+// worked as intended.
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <gflags/gflags.h>
+#include <glog/logging.h>
+
+#include "folly/Conv.h"
+#include "folly/Subprocess.h"
+
+using folly::Subprocess;
+
+DEFINE_bool(child, false, "");
+
+namespace {
+constexpr int kSignal = SIGUSR1;
+volatile bool caught = false;
+
+void signalHandler(int sig) {
+  if (sig != kSignal) {
+    abort();
+  }
+  caught = true;
+}
+
+}  // namespace
+
+void runChild(const char* file) {
+  struct sigaction sa;
+  sa.sa_handler = signalHandler;
+  sigemptyset(&sa.sa_mask);
+  sa.sa_flags = 0;
+  CHECK_ERR(sigaction(kSignal, &sa, nullptr));
+
+  // Kill the parent, wait for our signal.
+  CHECK_ERR(kill(getppid(), SIGKILL));
+
+  while (!caught) {
+    pause();
+  }
+
+  // Signal completion by creating the file
+  CHECK_ERR(creat(file, 0600));
+}
+
+void runParent(const char* file) {
+  std::vector<std::string> args {"/proc/self/exe", "--child", file};
+  Subprocess proc(
+      args,
+      Subprocess::Options().parentDeathSignal(kSignal));
+  CHECK(proc.poll().running());
+
+  // The child will kill us.
+  for (;;) {
+    pause();
+  }
+}
+
+int main(int argc, char *argv[]) {
+  google::ParseCommandLineFlags(&argc, &argv, true);
+  CHECK_EQ(argc, 2);
+  if (FLAGS_child) {
+    runChild(argv[1]);
+  } else {
+    runParent(argv[1]);
+  }
+  return 0;
+}
+