Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to send a signal to a subprocess #127

Open
wants to merge 8 commits into
base: gz-utils2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion include/gz/utils/Subprocess.hh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
#ifndef GZ_UTILS__SUBPROCESS_HH_
#define GZ_UTILS__SUBPROCESS_HH_

#if defined(_WIN32)
#include <windows.h>
#endif // defined(_WIN32)

#include "detail/subprocess.h"
#include "gz/utils/Environment.hh"

Expand Down Expand Up @@ -87,7 +91,7 @@
{
}


/// \brief Create a subprocess
private: void Create()
{
if (this->process != nullptr)
Expand Down Expand Up @@ -183,6 +187,26 @@
return false;
}

public: bool Signal(int signum)
{
if (this->process != nullptr)
return subprocess_signal(this->process, signum) != 0;
else
return false;

Check warning on line 195 in include/gz/utils/Subprocess.hh

View check run for this annotation

Codecov / codecov/patch

include/gz/utils/Subprocess.hh#L195

Added line #L195 was not covered by tests

}

public: bool SendExitSignal()
{
int signal = 0;
#if defined(_WIN32)
signal = static_cast<int>(CTRL_BREAK_EVENT);
#else
signal = static_cast<int>(SIGINT);
#endif
return this->Signal(signal);
}

public: bool Terminate()
{
if (this->process != nullptr)
Expand Down
24 changes: 24 additions & 0 deletions include/gz/utils/detail/subprocess.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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};
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
40 changes: 40 additions & 0 deletions test/integration/subprocess_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::chrono::milliseconds>(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());
}