Skip to content
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
53 changes: 38 additions & 15 deletions src/process/ProcessManagerImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,38 @@ enum ProcessLifecycle
namespace stellar
{

// Custom error category for process exit codes
class process_exit_category_impl : public std::error_category
{
public:
virtual const char* name() const noexcept override
{
return "process_exit";
}

virtual std::string message(int value) const override
{
if (value == 0)
{
return "Process exited successfully";
}
else if (value == -1)
Copy link

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number -1 for signal termination should be defined as a named constant to improve code readability and maintainability.

Suggested change
else if (value == -1)
else if (value == PROCESS_TERMINATED_BY_SIGNAL)

Copilot uses AI. Check for mistakes.
{
return "Process terminated by signal";
}
else
{
return fmt::format("Process exited with code {}", value);
}
}
};

const std::error_category& process_exit_category()
{
static process_exit_category_impl instance;
return instance;
}

static const asio::error_code ABORT_ERROR_CODE(asio::error::operation_aborted,
asio::system_category());

Expand All @@ -66,7 +98,7 @@ mapExitStatusToErrorCode(std::string const& cmdLine, int pid, int status)
// On windows, an exit status is just an exit status. On unix it's
// got some flags incorporated into it.
#ifdef _WIN32
return asio::error_code(status, asio::system_category());
return asio::error_code(status, process_exit_category());
#else
if (WIFEXITED(status))
{
Expand Down Expand Up @@ -98,23 +130,14 @@ mapExitStatusToErrorCode(std::string const& cmdLine, int pid, int status)
CLOG_WARNING(Process, "");
}
#endif
// FIXME: this doesn't _quite_ do the right thing; it conveys
// the exit status back to the caller but it puts it in "system
// category" which on POSIX means if you call .message() on it
// you'll get perror(value()), which is not correct. Errno has
// nothing to do with process exit values. We could make a new
// error_category to tighten this up, but it's a bunch of work
// just to convey the meaningless string "exited" to the user.
return asio::error_code(WEXITSTATUS(status), asio::system_category());
// Use our custom process_exit_category for proper error messages
return asio::error_code(WEXITSTATUS(status), process_exit_category());
}
else
{
// FIXME: for now we also collapse all non-WIFEXITED exits on
// posix into a single "exit 1" error_code. This is enough
// for most callers; we can enrich it if anyone really wants
// to differentiate various signals that might have killed
// the child.
return asio::error_code(1, asio::system_category());
// Process was terminated by a signal rather than normal exit
// Use -1 to indicate signal termination in our custom category
return asio::error_code(-1, process_exit_category());
Comment on lines +139 to +140
Copy link

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number -1 for signal termination should be defined as a named constant to match the usage in the error message function and improve consistency.

Suggested change
// Use -1 to indicate signal termination in our custom category
return asio::error_code(-1, process_exit_category());
// Use PROCESS_TERMINATED_BY_SIGNAL to indicate signal termination in our custom category
return asio::error_code(PROCESS_TERMINATED_BY_SIGNAL, process_exit_category());

Copilot uses AI. Check for mistakes.
}
#endif
}
Expand Down
74 changes: 74 additions & 0 deletions src/process/test/ProcessTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,80 @@ TEST_CASE("subprocess fails", "[process]")
REQUIRE(failed);
}

TEST_CASE("subprocess error messages", "[process]")
{
VirtualClock clock;
Config const& cfg = getTestConfig();
Application::pointer app = createTestApplication(clock, cfg);

SECTION("successful exit")
{
bool exited = false;
asio::error_code capturedEc;
auto evt = app->getProcessManager().runProcess("true", "").lock();
REQUIRE(evt);
evt->async_wait([&](asio::error_code ec) {
capturedEc = ec;
exited = true;
});

while (!exited && !clock.getIOContext().stopped())
{
clock.crank(true);
}

// Process should exit with code 0
REQUIRE(capturedEc.value() == 0);
REQUIRE(capturedEc.message() == "Process exited successfully");
REQUIRE(std::string(capturedEc.category().name()) == "process_exit");
}

SECTION("failed exit")
{
bool exited = false;
asio::error_code capturedEc;
auto evt = app->getProcessManager().runProcess("false", "").lock();
REQUIRE(evt);
evt->async_wait([&](asio::error_code ec) {
capturedEc = ec;
exited = true;
});

while (!exited && !clock.getIOContext().stopped())
{
clock.crank(true);
}

// Process should exit with non-zero code
REQUIRE(capturedEc.value() != 0);
REQUIRE(capturedEc.message().find("Process exited with code") != std::string::npos);
REQUIRE(std::string(capturedEc.category().name()) == "process_exit");
}

SECTION("custom exit code")
{
bool exited = false;
asio::error_code capturedEc;
// Use sh -c "exit 42" to exit with specific code
auto evt = app->getProcessManager().runProcess("sh -c 'exit 42'", "").lock();
Comment on lines +132 to +133
Copy link

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using shell command with single quotes inside double quotes may cause issues on Windows. Consider using a more portable approach or adding platform-specific test sections.

Suggested change
// Use sh -c "exit 42" to exit with specific code
auto evt = app->getProcessManager().runProcess("sh -c 'exit 42'", "").lock();
// Use platform-specific command to exit with code 42
#ifdef _WIN32
std::string exitCmd = "cmd /C exit 42";
#else
std::string exitCmd = "sh -c \"exit 42\"";
#endif
auto evt = app->getProcessManager().runProcess(exitCmd, "").lock();

Copilot uses AI. Check for mistakes.
REQUIRE(evt);
evt->async_wait([&](asio::error_code ec) {
capturedEc = ec;
exited = true;
});

while (!exited && !clock.getIOContext().stopped())
{
clock.crank(true);
}

// Process should exit with code 42
REQUIRE(capturedEc.value() == 42);
REQUIRE(capturedEc.message() == "Process exited with code 42");
REQUIRE(std::string(capturedEc.category().name()) == "process_exit");
}
}

TEST_CASE("subprocess redirect to new file", "[process]")
{
VirtualClock clock;
Expand Down