2

As part of my automated test suite, I have a C++ Program (A) that executes a command line Process (B) using CreateProcess().

The process only terminates when it receives a SIGINT signal (for reasons outside of my control).

I can terminate the process (B) from (A) using CloseHandle() and/or TerminateProcess(), however, this does not call the destructor of (B), preventing it from closing gracefully (writing stuff to disk and closing DB connections) and causing the tests to fail.

What is the best approach to gracefully close (B), allowing it clean up after itself? Should I be using a helper executable with IPC, a remote thread...?

I have tried the solutions in these SA questions:

Edit: @Remy Lebeau is right I should have posted some code:

Current Approach:

Close the process handle. This kills the process immediately.

PROCESS_INFORMATION process_info;
... // CreateProcess()
CloseHandle(process.hProcess);
CloseHandle(process.hThread);

Approach 2:

Detach the current console and then re-attach. This causes the initial test suite to fail.

PROCESS_INFORMATION process_info;
... // CreateProcess
DWORD thisConsoleId = GetCurrentProcessId();
bool consoleDetached = (FreeConsole() != FALSE);
if (AttachConsole(process_info.dwProcessId)) {
  std::cout << "Attached process to console" << std::endl;
  SetConsoleCtrlHandler(NULL, true);
  if (GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
    std::cout << "Ctrl-c sent to process" << std::endl;
  } else {
    std::cout << "Could not send ctrl-c (" << GetLastError() << ")" << std::endl;
  }
  FreeConsole();
} else {
    std::cout << "Unable to attach process to console (" << GetLastError() << ")" << std::endl;
}

if (consoleDetached) {
  // Create a new console if previous was deleted by OS
  if (AttachConsole(thisConsoleId)) {
    int errorCode = GetLastError();
    // 31=ERROR_GEN_FAILURE
    if (errorCode == 31) {
      AllocConsole();
    }
  }
}

Approach 3:

Attach to console without freeing. This kills everything including the test suite.

PROCESS_INFORMATION process_info;
... // CreateProcess
AttachConsole(process_info.dwProcessId);
SetConsoleCtrlHandler(NULL, TRUE);
GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
Anas Yousef
  • 93
  • 1
  • 7
  • What exactly have you already tried that is not working for you? Linking to other people's questions does it show us the actual code you are using. – Remy Lebeau Feb 05 '20 at 04:59
  • Using `SIGINT` for graceful shutdown of a child process that shares the parent's console requires either the child to enable Ctrl+C via `SetConsoleCtrlHandler`, or for the parent to disable it just before sending Ctrl+C. `GenerateConsoleCtrlEvent` sends either to a console process group or to all of the console's processes (group 0). To target just the child, it has to be created in a new group via the creation flag `CREATE_NEW_PROCESS_GROUP`. The group ID is the child's process ID. However, this also disables Ctrl+C in the child, so it has to manually enable it. – Eryk Sun Feb 05 '20 at 06:09
  • It's simpler and more reliable if the child process instead handles C `SIGBREAK` or WINAPI `CTRL_BREAK_EVENT`. The Ctrl+Break event cannot be disabled, so it always works, even when creating the child as a new group via `CREATE_NEW_PROCESS_GROUP`. – Eryk Sun Feb 05 '20 at 06:11
  • If the parent is a GUI process that doesn't initially share the console of the child process, it will first need to attach to the child's console via `AttachConsole(childProcessId)`. After attaching it can call `GenerateConsoleCtrlEvent(ctrlEvent, childProcessId)` -- assuming the child was created as the leader of a new console group. Again, in this case `CTRL_BREAK_EVENT` will always be enabled in the child, and `CTRL_C_EVENT` will not be unless the child manually enables it. – Eryk Sun Feb 05 '20 at 06:14
  • @ErykSun does this mean I need a wrapper process for to send a ctrl-c and handle it? I can't use ctrl-break. Also this is a console process. – Anas Yousef Feb 05 '20 at 18:02
  • Closing a handle to a Process or Thread object does not terminate and destroy it. They're not like temporary reference-counted objects in that regard. They are internally referenced by their PID or TID in the system handle table that stores these identifiers. They terminate via `TerminateProcess` or `TerminateThread`. Even when they exit naturally, system runtime library functions within the process ultimately call those functions, or rather the underlying NT system calls, `NtTerminateProcess` and `NtTerminateThread`. – Eryk Sun Feb 05 '20 at 22:37
  • If the parent process has a console and the child is not created with the flag `CREATE_NEW_CONSOLE` or `CREATE_NO_WINDOW`, then it should be attached to the current console, and there's no need to call `FreeConsole` and `AttachConsole`. If you have to call `FreeConsole` within a console process, it's a delicate operation -- with regard to resetting standard I/O `FILE` streams and keeping the original console referenced if you need to reattach to it. – Eryk Sun Feb 05 '20 at 22:43
  • As I said, `GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)` sends the event to all processes that are attached to the console. You're disabling Ctrl+C for the current process via `SetConsoleCtrlHandler(NULL, TRUE)`, but I don't know what else is attached to the console that shouldn't be sent Ctrl+C. This flag is inheritable, i.e. if the parent has Ctrl+C disabled, then child processes will also have it disabled -- in case that makes it easier for you to orchestrate which processes will respond to Ctrl+C. – Eryk Sun Feb 05 '20 at 22:52
  • Ultimately if it must be Ctrl+C instead of Ctrl+Break, and the child doesn't manually enable it, then you cannot use a new process group to selectively target the control event. You'll have to send it to all attached processes. Orchestrating this may require starting the child process with a new console if you have other processes attached to the current console that shouldn't get Ctrl+C. Then you can free/attach temporarily, or use a worker process that spawns the child and sends Ctrl+C after getting some signal, such as from an inherited Event object that you set to trigger sending Ctrl+C. – Eryk Sun Feb 05 '20 at 22:56

0 Answers0