Commit 1f528c76 authored by Tudor Bosman's avatar Tudor Bosman Committed by Jordan DeLong

Optionally, kill subprocess when parent dies

Summary: Non-portable.

Test Plan: test added

Reviewed By: lucian@fb.com

FB internal diff: D755528
parent 4ace1217
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "folly/Subprocess.h" #include "folly/Subprocess.h"
#include <sys/prctl.h>
#include <fcntl.h> #include <fcntl.h>
#include <poll.h> #include <poll.h>
#include <unistd.h> #include <unistd.h>
...@@ -351,6 +352,14 @@ void Subprocess::runChild(const char* executable, ...@@ -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. // Now, finally, exec.
int r; int r;
if (options.usePath_) { if (options.usePath_) {
......
...@@ -179,7 +179,11 @@ class Subprocess : private boost::noncopyable { ...@@ -179,7 +179,11 @@ class Subprocess : private boost::noncopyable {
class Options : private boost::orable<Options> { class Options : private boost::orable<Options> {
friend class Subprocess; friend class Subprocess;
public: 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 { ...@@ -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 { ...@@ -243,6 +255,7 @@ class Subprocess : private boost::noncopyable {
FdMap fdActions_; FdMap fdActions_;
bool closeOtherFds_; bool closeOtherFds_;
bool usePath_; bool usePath_;
int parentDeathSignal_;
}; };
static Options pipeStdin() { return Options().stdin(PIPE); } static Options pipeStdin() { return Options().stdin(PIPE); }
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
#include "folly/Subprocess.h" #include "folly/Subprocess.h"
#include <unistd.h>
#include <glog/logging.h> #include <glog/logging.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
...@@ -23,6 +25,7 @@ ...@@ -23,6 +25,7 @@
#include "folly/experimental/Gen.h" #include "folly/experimental/Gen.h"
#include "folly/experimental/FileGen.h" #include "folly/experimental/FileGen.h"
#include "folly/experimental/StringGen.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) { ...@@ -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) { TEST(PopenSubprocessTest, PopenRead) {
Subprocess proc("ls /", Subprocess::pipeStdout()); Subprocess proc("ls /", Subprocess::pipeStdout());
int found = 0; int found = 0;
......
/*
* 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;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment