diff --git a/include/gz/utils/Subprocess.hh b/include/gz/utils/Subprocess.hh index f7f9fab..ce23773 100644 --- a/include/gz/utils/Subprocess.hh +++ b/include/gz/utils/Subprocess.hh @@ -18,6 +18,10 @@ #ifndef GZ_UTILS__SUBPROCESS_HH_ #define GZ_UTILS__SUBPROCESS_HH_ +#if defined(_WIN32) +#include +#endif // defined(_WIN32) + #include "detail/subprocess.h" #include "gz/utils/Environment.hh" @@ -87,7 +91,7 @@ class Subprocess { } - + /// \brief Create a subprocess private: void Create() { if (this->process != nullptr) @@ -183,6 +187,26 @@ class Subprocess return false; } + public: bool Signal(int signum) + { + if (this->process != nullptr) + return subprocess_signal(this->process, signum) != 0; + else + return false; + + } + + public: bool SendExitSignal() + { + int signal = 0; +#if defined(_WIN32) + signal = static_cast(CTRL_BREAK_EVENT); +#else + signal = static_cast(SIGINT); +#endif + return this->Signal(signal); + } + public: bool Terminate() { if (this->process != nullptr) diff --git a/include/gz/utils/detail/subprocess.h b/include/gz/utils/detail/subprocess.h index 137aa14..d73a232 100644 --- a/include/gz/utils/detail/subprocess.h +++ b/include/gz/utils/detail/subprocess.h @@ -170,6 +170,16 @@ subprocess_weak int subprocess_join(struct subprocess_s *const process, /// the parent process. subprocess_weak int subprocess_destroy(struct subprocess_s *const process); +/// @brief Signal a previously created process. +/// @param process The process to signal. +/// @param signal The signal number to send to the process. +/// @return On success zero is returned. +/// +/// Allows to send signals to the subprocess. For unix-based systems, +/// this includes any signal that appears in `kill -l`. For Windows +/// systems, this includes events available to GenerateConsoleCtrlEvent. +subprocess_weak int subprocess_signal(struct subprocess_s *const process, int signum); + /// @brief Terminate a previously created process. /// @param process The process to terminate. /// @return On success zero is returned. @@ -486,6 +496,7 @@ int subprocess_create_ex(const char *const commandLine[], int options, const unsigned long startFUseStdHandles = 0x00000100; const unsigned long handleFlagInherit = 0x00000001; const unsigned long createNoWindow = 0x08000000; + const unsigned long createNewProcessGroup = 0x00000200; struct subprocess_subprocess_information_s processInfo; struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), SUBPROCESS_NULL, 1}; @@ -512,6 +523,7 @@ int subprocess_create_ex(const char *const commandLine[], int options, startInfo.cb = sizeof(startInfo); startInfo.dwFlags = startFUseStdHandles; + flags |= createNewProcessGroup; if (subprocess_option_no_window == (options & subprocess_option_no_window)) { flags |= createNoWindow; } @@ -1022,6 +1034,18 @@ int subprocess_destroy(struct subprocess_s *const process) { return 0; } +int subprocess_signal(subprocess_s *const process, int signum) +{ + int result; +#if defined(_WIN32) + auto processId = GetProcessId(process->hProcess); + result = GenerateConsoleCtrlEvent(signum, processId); +#else + result = kill(process->child, signum); +#endif + return result; +} + int subprocess_terminate(struct subprocess_s *const process) { #if defined(_WIN32) unsigned int killed_process_exit_code; diff --git a/test/integration/subprocess_TEST.cc b/test/integration/subprocess_TEST.cc index 47c8976..773c837 100644 --- a/test/integration/subprocess_TEST.cc +++ b/test/integration/subprocess_TEST.cc @@ -200,5 +200,45 @@ TEST(Subprocess, Environment) EXPECT_EQ(std::string::npos, cout.find("GZ_BAZ_KEY=GZ_BAZ_VAL")); EXPECT_NE(std::string::npos, cout.find("QUX_KEY=QUX_VAL")); } +} +///////////////////////////////////////////////// +TEST(Subprocess, Signal) +{ + auto start = std::chrono::steady_clock::now(); + auto proc = Subprocess({kExecutablePath, + "--output=cerr", + "--iterations=100", + "--iteration-ms=10"}); + EXPECT_TRUE(proc.Alive()); + + // Sleep for >1 iteration before sending signal + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + // Signal + proc.SendExitSignal(); + + // Block until the executable is done + auto ret = proc.Join(); + +#if defined(_WIN32) + // Windows has a special exit code for Ctrl-C received + EXPECT_EQ(0xC000013A, ret); +#else + EXPECT_EQ(1u, ret); +#endif + + auto end = std::chrono::steady_clock::now(); + auto elapsed = + std::chrono::duration_cast(end - start); + + // Check that process finished in less than half a second + // If the signal failed to send, then the process would run for over 1 second + EXPECT_LT(elapsed.count(), 500); + + EXPECT_FALSE(proc.Alive()); + auto cout = proc.Stdout(); + auto cerr = proc.Stderr(); + EXPECT_TRUE(cout.empty()); + EXPECT_FALSE(cerr.empty()); }