/*
- * Copyright 2015 Facebook, Inc.
+ * Copyright 2016 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* make sure to serialize your signals (i.e. kill()) with the waits --
* either wait & signal from the same thread, or use a mutex.
*/
-#ifndef FOLLY_SUBPROCESS_H_
-#define FOLLY_SUBPROCESS_H_
+
+#pragma once
#include <sys/types.h>
#include <signal.h>
#include <boost/container/flat_map.hpp>
#include <boost/operators.hpp>
+#include <folly/Exception.h>
#include <folly/File.h>
#include <folly/FileUtil.h>
+#include <folly/Function.h>
#include <folly/gen/String.h>
#include <folly/io/IOBufQueue.h>
#include <folly/MapUtil.h>
friend class Subprocess;
public:
enum State {
+ // Subprocess starts in the constructor, so this state designates only
+ // default-initialized or moved-out ProcessReturnCodes.
NOT_STARTED,
RUNNING,
EXITED,
KILLED
};
+ // Default-initialized for convenience. Subprocess::returnCode() will
+ // never produce this value.
+ ProcessReturnCode() : ProcessReturnCode(RV_NOT_STARTED) {}
+
// Trivially copyable
ProcessReturnCode(const ProcessReturnCode& p) = default;
ProcessReturnCode& operator=(const ProcessReturnCode& p) = default;
class CalledProcessError : public SubprocessError {
public:
explicit CalledProcessError(ProcessReturnCode rc);
- ~CalledProcessError() throw() { }
- const char* what() const throw() FOLLY_OVERRIDE { return what_.c_str(); }
+ ~CalledProcessError() throw() = default;
+ const char* what() const throw() override { return what_.c_str(); }
ProcessReturnCode returnCode() const { return returnCode_; }
private:
ProcessReturnCode returnCode_;
class SubprocessSpawnError : public SubprocessError {
public:
SubprocessSpawnError(const char* executable, int errCode, int errnoValue);
- ~SubprocessSpawnError() throw() {}
- const char* what() const throw() FOLLY_OVERRIDE { return what_.c_str(); }
+ ~SubprocessSpawnError() throw() = default;
+ const char* what() const throw() override { return what_.c_str(); }
int errnoValue() const { return errnoValue_; }
private:
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); }
ProcessReturnCode returnCode() const { return returnCode_; }
/**
- * Poll the child's status and return it, return -1 if the process
- * is still running. NOTE that it is illegal to call poll again after
- * poll indicated that the process has terminated, or to call poll on a
- * process that hasn't been successfully started (the constructor threw an
- * exception).
+ * Poll the child's status and return it. Return the exit status if the
+ * subprocess had quit, or RUNNING otherwise. Throws an std::logic_error
+ * if called on a Subprocess whose status is no longer RUNNING. No other
+ * exceptions are possible. Aborts on egregious violations of contract,
+ * e.g. if you wait for the underlying process without going through this
+ * Subprocess instance.
*/
ProcessReturnCode poll();
bool pollChecked();
/**
- * Wait for the process to terminate and return its status.
- * Similarly to poll, it is illegal to call wait after the process
- * has already been reaped or if the process has not successfully started.
+ * Wait for the process to terminate and return its status. Like poll(),
+ * the only exception this can throw is std::logic_error if you call this
+ * on a Subprocess whose status is RUNNING. Aborts on egregious
+ * violations of contract, like an out-of-band waitpid(p.pid(), 0, 0).
*/
ProcessReturnCode wait();
* expensive implementation choice, in order to make closeParentFd()
* thread-safe.
*/
- typedef std::function<bool(int, int)> FdCallback;
+ using FdCallback = folly::Function<bool(int, int)>;
void communicate(FdCallback readCallback, FdCallback writeCallback);
/**
* descriptors. Use the readLinesCallback() helper to get template
* deduction. For example:
*
- * auto read_cb = Subprocess::readLinesCallback(
- * [](int fd, folly::StringPiece s) {
- * std::cout << fd << " said: " << s;
- * return false; // Keep reading from the child
- * }
- * );
* subprocess.communicate(
- * // ReadLinesCallback contains StreamSplitter contains IOBuf, making
- * // it noncopyable, whereas std::function must be copyable. So, we
- * // keep the callback in a local, and instead pass a reference.
- * std::ref(read_cb),
+ * Subprocess::readLinesCallback(
+ * [](int fd, folly::StringPiece s) {
+ * std::cout << fd << " said: " << s;
+ * return false; // Keep reading from the child
+ * }
+ * ),
* [](int pdf, int cfd){ return true; } // Don't write to the child
* );
*
uint64_t maxLineLength = 0, // No line length limit by default
char delimiter = '\n',
uint64_t bufSize = 1024
- ) : fdLineCb_(std::move(fdLineCb)),
+ ) : fdLineCb_(std::forward<Callback>(fdLineCb)),
maxLineLength_(maxLineLength),
delimiter_(delimiter),
bufSize_(bufSize) {}
if (ret == -1 && errno == EAGAIN) { // No more data for now
return false;
}
+ checkUnixError(ret, "read");
if (ret == 0) { // Reached end-of-file
splitter.flush(); // Ignore return since the file is over anyway
return true;
// Helper to enable template deduction
template <class Callback>
- static ReadLinesCallback<Callback> readLinesCallback(
+ static auto readLinesCallback(
Callback&& fdLineCb,
- uint64_t maxLineLength = 0, // No line length limit by default
+ uint64_t maxLineLength = 0, // No line length limit by default
char delimiter = '\n',
- uint64_t bufSize = 1024) {
- return ReadLinesCallback<Callback>(
- std::move(fdLineCb), maxLineLength, delimiter, bufSize
- );
+ uint64_t bufSize = 1024)
+ -> ReadLinesCallback<typename std::decay<Callback>::type> {
+ return ReadLinesCallback<typename std::decay<Callback>::type>(
+ std::forward<Callback>(fdLineCb), maxLineLength, delimiter, bufSize);
}
/**
}
} // namespace folly
-
-#endif /* FOLLY_SUBPROCESS_H_ */