/// identified by PID.
typedef std::map<pid_t, ProcessStatePtr> ProcessStates;
-class ProcessSpawnImpl;
+class processSpawnImpl;
/// @brief ProcessCollection container which stores all ProcessStates for each
/// instance of @ref ProcessSpawnImpl.
const std::string& executable,
const ProcessArgs& args,
const ProcessEnvVars& vars,
- const bool inherit_env);
+ const bool inherit_env,
+ const bool sync);
/// @brief Destructor.
~ProcessSpawnImpl();
if (!io_service) {
isc_throw(ProcessSpawnError, "NULL IOService instance");
}
- io_signal_set_ = boost::make_shared<IOSignalSet>(io_service,
- std::bind(&ProcessSpawnImpl::waitForProcess, ph::_1));
+ io_signal_set_ =
+ boost::make_shared<IOSignalSet>(io_service,
+ std::bind(&ProcessSpawnImpl::waitForProcess, ph::_1,
+ /* pid = */ -1, /* sync = */ false));
io_signal_set_->add(SIGCHLD);
}
/// @brief Signal handler for SIGCHLD.
///
- /// This handler waits for the child process to finish and retrieves
- /// its exit code into the @c status_ member.
- ///
- /// @return true if the processed signal was SIGCHLD or false if it
- /// was a different signal.
- static bool waitForProcess(int signum);
+ /// @param signum the signal number received,
+ /// populated by the signal set in async mode
+ /// @param pid the pid to wait for, -1 by default meaning wait
+ /// for any child process
+ /// @param sync whether this function is called immediately after spawning
+ /// (synchronous) or not (asynchronous, default).:w
+ static void waitForProcess(int signum, pid_t const pid = -1, bool const sync = false);
/// @brief A map holding the status codes of executed processes.
static ProcessCollection process_collection_;
/// @brief The IOService which handles IO operations.
IOServicePtr io_service_;
+
+ /// @brief Whether the process is waited immediately after spawning
+ /// (synchronous) or not (asynchronous, default).
+ bool sync_;
};
ProcessCollection ProcessSpawnImpl::process_collection_;
const std::string& executable,
const ProcessArgs& args,
const ProcessEnvVars& vars,
- const bool inherit_env)
+ const bool inherit_env,
+ const bool sync)
: executable_(executable), args_(new char*[args.size() + 2]),
- store_(false), io_service_(io_service) {
+ store_(false), io_service_(io_service), sync_(sync) {
// Size of vars except the trailing null
size_t vars_size;
store_ = true;
process_collection_[this].insert(std::pair<pid_t, ProcessStatePtr>(pid, ProcessStatePtr(new ProcessState())));
}
+
+ if (sync_) {
+ waitForProcess(SIGCHLD, pid, /* sync = */ true);
+ }
+
return (pid);
}
ProcessStates::const_iterator proc;
if (process_collection_.find(this) == process_collection_.end() ||
(proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
- isc_throw(BadValue, "the process with the pid '" << pid
- << "' hasn't been spawned and it status cannot be"
- " returned");
+ isc_throw(InvalidOperation, "the status of process with pid '" << pid << "' cannot be returned");
}
return (proc->second->running_);
}
ProcessStates::const_iterator proc;
if (process_collection_.find(this) == process_collection_.end() ||
(proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
- isc_throw(InvalidOperation, "the process with the pid '" << pid
- << "' hasn't been spawned and it status cannot be"
- " returned");
+ isc_throw(InvalidOperation, "the status of process with pid '" << pid << "' cannot be returned");
}
return (WEXITSTATUS(proc->second->status_));
}
return (dest);
}
-bool
-ProcessSpawnImpl::waitForProcess(int) {
- lock_guard<std::mutex> lk(mutex_);
+void
+ProcessSpawnImpl::waitForProcess(int /* signum */,
+ pid_t const pid /* = -1 */,
+ bool sync /* = false */) {
+ unique_lock<std::mutex> lk{mutex_, std::defer_lock};
+ if (!sync) {
+ lk.lock();
+ }
for (;;) {
int status = 0;
- pid_t pid = waitpid(-1, &status, WNOHANG);
- if (pid <= 0) {
+ // When called synchronously, this function needs to be blocking.
+ // When called asynchronously, the first IO context event is for
+ // receiving the SIGCHLD signal which itself is blocking,
+ // hence no need to block here too.
+ pid_t wpid = waitpid(pid, &status, sync ? 0 : WNOHANG);
+ if (wpid <= 0) {
break;
}
for (auto const& instance : process_collection_) {
- auto const& proc = instance.second.find(pid);
+ auto const& proc = instance.second.find(wpid);
/// Check that the terminating process was started
/// by our instance of ProcessSpawn
if (proc != instance.second.end()) {
}
}
}
- return (true);
}
void
const ProcessArgs& args,
const ProcessEnvVars& vars,
const bool inherit_env /* = false */)
- : impl_(new ProcessSpawnImpl(io_service, executable, args, vars, inherit_env)) {
+ : impl_(new ProcessSpawnImpl(
+ io_service, executable, args, vars, inherit_env, /* sync = */ false)) {
+}
+
+ProcessSpawn::ProcessSpawn(const std::string& executable,
+ const ProcessArgs& args,
+ const ProcessEnvVars& vars,
+ const bool inherit_env /* = false */)
+ : impl_(new ProcessSpawnImpl(IOServicePtr(new IOService()),
+ executable,
+ args,
+ vars,
+ inherit_env,
+ /* sync = */ true)) {
}
std::string
// Set test fail safe.
setTestTime(1000);
- // The next handler executed is IOSignal's handler.
+ // SIGCHLD signal.
io_service_->runOne();
- // The first handler executed is the IOSignal's internal timer expire
- // callback.
+ // waitForProcess handler.
io_service_->runOne();
- // Polling once to be sure.
+ // ProcessSpawnTest::processSignal handler.
+ io_service_->runOne();
+
+ // Poll to be sure.
io_service_->poll();
ASSERT_EQ(1, processed_signals_.size());
// Set test fail safe.
setTestTime(1000);
- // The next handler executed is IOSignal's handler.
+ // SIGCHLD signal.
io_service_->runOne();
- // The first handler executed is the IOSignal's internal timer expire
- // callback.
+ // waitForProcess handler.
io_service_->runOne();
- // Polling once to be sure.
+ // ProcessSpawnTest::processSignal handler.
+ io_service_->runOne();
+
+ // Poll to be sure.
io_service_->poll();
ASSERT_EQ(1, processed_signals_.size());
// Set test fail safe.
setTestTime(1000);
- // The next handler executed is IOSignal's handler.
+ // SIGCHLD signal.
+ io_service_->runOne();
+
+ // waitForProcess handler.
io_service_->runOne();
- // The first handler executed is the IOSignal's internal timer expire
- // callback.
+ // ProcessSpawnTest::processSignal handler.
io_service_->runOne();
- // Polling once to be sure.
+ // Poll to be sure.
io_service_->poll();
pid_t pid2 = 0;
// Set test fail safe.
setTestTime(1000);
- // The next handler executed is IOSignal's handler.
+ // SIGCHLD signal.
+ io_service_->runOne();
+
+ // waitForProcess handler.
io_service_->runOne();
- // The first handler executed is the IOSignal's internal timer expire
- // callback.
+ // ProcessSpawnTest::processSignal handler.
io_service_->runOne();
- // Polling once to be sure.
+ // Poll to be sure.
io_service_->poll();
ASSERT_EQ(2, processed_signals_.size());
// Set test fail safe.
setTestTime(1000);
- // The next handler executed is IOSignal's handler.
+ // SIGCHLD signal.
+ io_service_->runOne();
+
+ // waitForProcess handler.
io_service_->runOne();
- // The first handler executed is the IOSignal's internal timer expire
- // callback.
+ // ProcessSpawnTest::processSignal handler.
io_service_->runOne();
- // Polling once to be sure.
+ // Poll to be sure.
io_service_->poll();
ASSERT_EQ(1, processed_signals_.size());
// Set test fail safe.
setTestTime(1000);
- // The next handler executed is IOSignal's handler.
+ // SIGCHLD signal.
io_service_->runOne();
- // The first handler executed is the IOSignal's internal timer expire
- // callback.
+ // waitForProcess handler.
io_service_->runOne();
- // Polling once to be sure.
+ // ProcessSpawnTest::processSignal handler.
+ io_service_->runOne();
+
+ // Poll to be sure.
io_service_->poll();
ASSERT_EQ(2, processed_signals_.size());
// Set test fail safe.
setTestTime(1000);
- // The next handler executed is IOSignal's handler.
+ // SIGCHLD signal.
io_service_->runOne();
- // The first handler executed is the IOSignal's internal timer expire
- // callback.
+ // waitForProcess handler.
io_service_->runOne();
- // Polling once to be sure.
+ // ProcessSpawnTest::processSignal handler.
+ io_service_->runOne();
+
+ // Poll to be sure.
io_service_->poll();
ASSERT_EQ(1, processed_signals_.size());
// Set test fail safe.
setTestTime(1000);
- // The next handler executed is IOSignal's handler.
+ // SIGCHLD signal.
+ io_service_->runOne();
+
+ // waitForProcess handler.
io_service_->runOne();
- // The first handler executed is the IOSignal's internal timer expire
- // callback.
+ // ProcessSpawnTest::processSignal handler.
io_service_->runOne();
- // Polling once to be sure.
+ // Poll to be sure.
io_service_->poll();
ASSERT_EQ(1, processed_signals_.size());
// Set test fail safe.
setTestTime(1000);
- // The next handler executed is IOSignal's handler.
+ // SIGCHLD signal.
+ io_service_->runOne();
+
+ // waitForProcess handler.
io_service_->runOne();
- // The first handler executed is the IOSignal's internal timer expire
- // callback.
+ // ProcessSpawnTest::processSignal handler.
io_service_->runOne();
- // Polling once to be sure.
+ // Poll to be sure.
io_service_->poll();
ASSERT_EQ(1, processed_signals_.size());
EXPECT_EQ(34, process.getExitStatus(pid));
}
+// This test verifies that the external application can be ran synchronously
+// with arguments and that the exit code is gathered.
+TEST_F(ProcessSpawnTest, spawnWithArgsSync) {
+ vector<string> args;
+ args.push_back("-e");
+ args.push_back("64");
+
+ ProcessSpawn process(TEST_SCRIPT_SH, args);
+ pid_t pid = 0;
+ ASSERT_NO_THROW(pid = process.spawn());
+
+ // Exit code 64 as requested.
+ EXPECT_EQ(64, process.getExitStatus(pid));
+}
+
+// This test verifies that the external application can be ran synchronously
+// with arguments and environment variables that the exit code is gathered.
+TEST_F(ProcessSpawnTest, spawnWithArgsAndEnvVarsSync) {
+ vector<string> args;
+ vector<string> vars;
+ args.push_back("-v");
+ args.push_back("TEST_VARIABLE_NAME");
+ args.push_back("TEST_VARIABLE_VALUE");
+ vars.push_back("TEST_VARIABLE_NAME=TEST_VARIABLE_VALUE");
+
+ ProcessSpawn process(TEST_SCRIPT_SH, args, vars);
+ pid_t pid = 0;
+ ASSERT_NO_THROW(pid = process.spawn());
+
+ // 56 means successful comparison of env vars.
+ EXPECT_EQ(56, process.getExitStatus(pid));
+}
+
+// This test verifies that the single ProcessSpawn object can be used
+// to start two processes synchronously and that their status codes can be
+// gathered. It also checks that it is possible to clear the status of the
+// process.
+TEST_F(ProcessSpawnTest, spawnTwoProcessesSync) {
+ vector<string> args;
+ args.push_back("-p");
+
+ ProcessSpawn process(TEST_SCRIPT_SH, args);
+ pid_t pid1 = 0;
+ ASSERT_NO_THROW(pid1 = process.spawn());
+
+ pid_t pid2 = 0;
+ ASSERT_NO_THROW(pid2 = process.spawn());
+
+ EXPECT_NE(process.getExitStatus(pid1), process.getExitStatus(pid2));
+
+ // Clear the status of the first process. An attempt to get the status
+ // for the cleared process should result in exception. But, there should
+ // be no exception for the second process.
+ process.clearState(pid1);
+ EXPECT_THROW(process.getExitStatus(pid1), InvalidOperation);
+ EXPECT_NO_THROW(process.getExitStatus(pid2));
+
+ process.clearState(pid2);
+ EXPECT_THROW(process.getExitStatus(pid2), InvalidOperation);
+}
+
+// This test verifies that the external application can be ran synchronously
+// without arguments and that the exit code is gathered.
+TEST_F(ProcessSpawnTest, spawnNoArgsSync) {
+ ProcessSpawn process(TEST_SCRIPT_SH);
+ pid_t pid = 0;
+ ASSERT_NO_THROW(pid = process.spawn());
+
+ // 32 means no args.
+ EXPECT_EQ(32, process.getExitStatus(pid));
+
+ ASSERT_NO_THROW(pid = process.spawn(true));
+
+ EXPECT_THROW(process.getExitStatus(pid), InvalidOperation);
+}
+
+// This test verifies that the EXIT_FAILURE code is returned when
+// application can't be executed synchronously.
+TEST_F(ProcessSpawnTest, invalidExecutableSync) {
+ std::string expected = "File not found: foo";
+ ASSERT_THROW_MSG(ProcessSpawn process("foo"),
+ ProcessSpawnError, expected);
+
+ std::string name = INVALID_TEST_SCRIPT_SH;
+
+ expected = "File not executable: ";
+ expected += name;
+ ASSERT_THROW_MSG(ProcessSpawn process(name),
+ ProcessSpawnError, expected);
+}
+
+// This test verifies that the full command line for the synchronous process is
+// returned.
+TEST_F(ProcessSpawnTest, getCommandLineSync) {
+ // Note that cases below are enclosed in separate scopes to make
+ // sure that the ProcessSpawn object is destroyed before a new
+ // object is created. Current implementation doesn't allow for
+ // having two ProcessSpawn objects simultaneously as they will
+ // both try to allocate a signal handler for SIGCHLD.
+ {
+ // Case 1: arguments present.
+ ProcessArgs args;
+ args.push_back("-x");
+ args.push_back("-y");
+ args.push_back("foo");
+ args.push_back("bar");
+ ProcessSpawn process(TEST_SCRIPT_SH, args);
+ std::string expected = TEST_SCRIPT_SH;
+ expected += " -x -y foo bar";
+ EXPECT_EQ(expected, process.getCommandLine());
+ }
+
+ {
+ // Case 2: no arguments.
+ ProcessSpawn process(TEST_SCRIPT_SH);
+ EXPECT_EQ(TEST_SCRIPT_SH, process.getCommandLine());
+ }
+}
+
+// This test verifies that it is possible to check if the synchronous process is
+// running.
+TEST_F(ProcessSpawnTest, DISABLED_isRunningSync) {
+ // Run the process which sleeps for 10 seconds, so as we have
+ // enough time to check if it is running.
+ vector<string> args;
+ args.push_back("-s");
+ args.push_back("10");
+
+ ProcessSpawn process(TEST_SCRIPT_SH, args);
+ pid_t pid = 0;
+ ASSERT_NO_THROW(pid = process.spawn());
+
+ EXPECT_TRUE(process.isRunning(pid));
+
+ // Kill the process.
+ ASSERT_EQ(0, kill(pid, SIGKILL));
+}
+
+// This test verifies inheritance of environment in a synchronous context.
+TEST_F(ProcessSpawnTest, inheritEnvSync) {
+ // Run the process which sleeps for 10 seconds, so as we have
+ // enough time to check if it is running.
+ vector<string> args{"-v", "VAR", "value"};
+
+ ProcessEnvVars vars{"VAR=value"};
+
+ ProcessSpawn process(TEST_SCRIPT_SH, args, vars,
+ /* inherit_env = */ true);
+ pid_t pid = 0;
+ ASSERT_NO_THROW(pid = process.spawn());
+
+ // 56 means successful comparison of env vars.
+ EXPECT_EQ(56, process.getExitStatus(pid));
+}
+
+// This test verifies inheritance of environment when a variable is inherited
+// from parent in a synchronous context. It assumes PATH is set.
+TEST_F(ProcessSpawnTest, inheritEnvWithParentVarSync) {
+ // Run the process which sleeps for 10 seconds, so as we have
+ // enough time to check if it is running.
+ vector<string> args{"-v", "PATH", "value"};
+
+ ProcessEnvVars vars{"VAR=value"};
+
+ ProcessSpawn process(TEST_SCRIPT_SH, args, vars,
+ /* inherit_env = */ true);
+ pid_t pid = 0;
+ ASSERT_NO_THROW(pid = process.spawn());
+
+ // 34 means failed comparison of env vars.
+ EXPECT_EQ(34, process.getExitStatus(pid));
+}
+
} // end of anonymous namespace