static const int PIPE_IN = -3;
static const int PIPE_OUT = -4;
+ /**
+ * See Subprocess::Options::dangerousPostForkPreExecCallback() for usage.
+ * Every derived class should include the following warning:
+ *
+ * DANGER: This class runs after fork in a child processes. Be fast, the
+ * parent thread is waiting, but remember that other parent threads are
+ * running and may mutate your state. Avoid mutating any data belonging to
+ * the parent. Avoid interacting with non-POD data that originated in the
+ * parent. Avoid any libraries that may internally reference non-POD data.
+ * Especially beware parent mutexes -- for example, glog's LOG() uses one.
+ */
+ struct DangerousPostForkPreExecCallback {
+ virtual ~DangerousPostForkPreExecCallback() {}
+ // This must return 0 on success, or an `errno` error code.
+ virtual int operator()() = 0;
+ };
+
/**
* Class representing various options: file descriptor behavior, and
* whether to use $PATH for searching for the executable,
return *this;
}
+ /**
+ * *** READ THIS WHOLE DOCBLOCK BEFORE USING ***
+ *
+ * Run this callback in the child after the fork, just before the
+ * exec(), and after the child's state has been completely set up:
+ * - signal handlers have been reset to default handling and unblocked
+ * - the working directory was set
+ * - closed any file descriptors specified via Options()
+ * - set child process flags (see code)
+ *
+ * This is EXTREMELY DANGEROUS. For example, this innocuous-looking code
+ * can cause a fraction of your Subprocess launches to hang forever:
+ *
+ * LOG(INFO) << "Hello from the child";
+ *
+ * The reason is that glog has an internal mutex. If your fork() happens
+ * when the parent has the mutex locked, the child will wait forever.
+ *
+ * == GUIDELINES ==
+ *
+ * - Be quick -- the parent thread is blocked until you exit.
+ * - Remember that other parent threads are running, and may mutate your
+ * state.
+ * - Avoid mutating any data belonging to the parent.
+ * - Avoid interacting with non-POD data that came from the parent.
+ * - Avoid any libraries that may internally reference non-POD state.
+ * - Especially beware parent mutexes, e.g. LOG() uses a global mutex.
+ * - Avoid invoking the parent's destructors (you can accidentally
+ * delete files, terminate network connections, etc).
+ * - Read http://ewontfix.com/7/
+ */
+ Options& dangerousPostForkPreExecCallback(
+ DangerousPostForkPreExecCallback* cob) {
+ dangerousPostForkPreExecCallback_ = cob;
+ return *this;
+ }
+
/**
* Helpful way to combine Options.
*/
int parentDeathSignal_{0};
#endif
bool processGroupLeader_{false};
+ DangerousPostForkPreExecCallback*
+ dangerousPostForkPreExecCallback_{nullptr};
};
static Options pipeStdin() { return Options().stdin(PIPE); }
proc.waitChecked();
}
+// DANGER: This class runs after fork in a child processes. Be fast, the
+// parent thread is waiting, but remember that other parent threads are
+// running and may mutate your state. Avoid mutating any data belonging to
+// the parent. Avoid interacting with non-POD data that originated in the
+// parent. Avoid any libraries that may internally reference non-POD data.
+// Especially beware parent mutexes -- for example, glog's LOG() uses one.
+struct WriteFileAfterFork
+ : public Subprocess::DangerousPostForkPreExecCallback {
+ explicit WriteFileAfterFork(std::string filename)
+ : filename_(std::move(filename)) {}
+ virtual ~WriteFileAfterFork() {}
+ int operator()() override {
+ return writeFile(std::string("ok"), filename_.c_str()) ? 0 : errno;
+ }
+ const std::string filename_;
+};
+
+TEST(AfterForkCallbackSubprocessTest, TestAfterForkCallbackSuccess) {
+ test::ChangeToTempDir td;
+ // Trigger a file write from the child.
+ WriteFileAfterFork write_cob("good_file");
+ Subprocess proc(
+ std::vector<std::string>{"/bin/echo"},
+ Subprocess::Options().dangerousPostForkPreExecCallback(&write_cob)
+ );
+ // The file gets written immediately.
+ std::string s;
+ EXPECT_TRUE(readFile(write_cob.filename_.c_str(), s));
+ EXPECT_EQ("ok", s);
+ proc.waitChecked();
+}
+
+TEST(AfterForkCallbackSubprocessTest, TestAfterForkCallbackError) {
+ test::ChangeToTempDir td;
+ // The child will try to write to a file, whose directory does not exist.
+ WriteFileAfterFork write_cob("bad/file");
+ EXPECT_THROW(
+ Subprocess proc(
+ std::vector<std::string>{"/bin/echo"},
+ Subprocess::Options().dangerousPostForkPreExecCallback(&write_cob)
+ ),
+ SubprocessSpawnError
+ );
+ EXPECT_FALSE(fs::exists(write_cob.filename_));
+}
+
TEST(CommunicateSubprocessTest, SimpleRead) {
Subprocess proc(std::vector<std::string>{ "/bin/echo", "-n", "foo", "bar"},
Subprocess::pipeStdout());