I have a program running (let's call it A), and inside this, I'm calling child_process from NodeJs to run another application (let's call it C). Now I can't change the structure of this, because we already have lots of features implemented in this pattern. So tweaking anything may cause to broken pipeline.
Now I want to terminate that C application, using CTRL+C. As that is a binary, I can't modify the source, so I need to send Ctrl + C to that program. Which I'm doing and it's happily terminating.
Now the issue is, it's killing C along with A. So that's the main problem as of now. I've tried creating another intermediate program, as B. But then it terminates C, then B, and finally A. So the Ctrl + C is propagating upwards. How can I prevent Ctrl + C from propagating up?
Here goes the pseudo-code:
FUNCTION SigintWindows(args):
INITIALIZE isolate from args
Create new handle scope with isolate
GET processId from args[0]
OPEN process with processId If fails:
THROW Error("Failed to open process Error code: " + error code)
EXIT
TRY attaching to console If fails:
THROW Error("Failed to attach to console Error code: " + error code)
TRY sending Ctrl-C event directly If fails:
THROW Error("Failed to send Ctrl-C event Error code: " + error code)
CLOSE process handle
EXIT
ELSE:
SET return value to true
EXIT
ELSE:
DISABLE Ctrl-C handling for our program If fails:
THROW Error("Failed to disable Ctrl-C handling Error code: " + error code)
CLOSE process handle
EXIT
SEND Ctrl-C event If fails:
THROW Error("Failed to send Ctrl-C event Error code: " + error code)
RE-ENABLE Ctrl-C handling If fails:
THROW Error("Failed to re-enable Ctrl-C handling Error code: " + error code)
FREE the console
CLOSE process handle
EXIT
ELSE:
WAIT for the process to exit (max 2 seconds) If process doesn't exit:
THROW Error("Process did not exit within 2 seconds")
RE-ENABLE Ctrl-C handling If fails:
THROW Error("Failed to re-enable Ctrl-C handling Error code: " + error code)
FREE the console
CLOSE process handle
EXIT
END IF
RE-ENABLE Ctrl-C handling If fails:
THROW Error("Failed to re-enable Ctrl-C handling")
CLOSE process handle
EXIT
FREE the console
CLOSE process handle
SET return value to true
And here goes the whole C++ implementation:
#include <node.h>
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
#include <windows.h>
#endif
namespace ctrlc {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
void SigintWindows(const v8::FunctionCallbackInfo < v8::Value > & args) {
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
v8::Isolate * isolate = args.GetIsolate();
v8::HandleScope scope(isolate);
// Check the number of arguments passed
if (args.Length() != 1) {
v8::Local < v8::String > v8String = v8::String::NewFromUtf8(isolate, "Invalid arguments").ToLocalChecked();
isolate -> ThrowException(v8::Exception::TypeError(v8String));
return;
}
// Check the argument types
if (!args[0] -> IsUint32()) {
v8::Local < v8::String > v8String = v8::String::NewFromUtf8(isolate, "Argument must be a number").ToLocalChecked();
isolate -> ThrowException(v8::Exception::TypeError(v8String));
return;
}
DWORD processId = args[0] -> Uint32Value(isolate -> GetCurrentContext()).ToChecked();
HANDLE hProcess = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, processId);
if (hProcess == NULL) {
v8::Local<v8::String> v8String = v8::String::NewFromUtf8(isolate, ("Failed to open process. Error code: " + std::to_string(GetLastError())).c_str()).ToLocalChecked();
isolate->ThrowException(v8::Exception::Error(v8String));
return;
}
// Try to attach to console
if (!AttachConsole(processId)) {
v8::Local<v8::String> v8String = v8::String::NewFromUtf8(isolate, ("Failed to attach to console. Error code: " + std::to_string(GetLastError())).c_str()).ToLocalChecked();
isolate->ThrowException(v8::Exception::Error(v8String));
// If attaching to console fails, try sending Ctrl-C event directly
if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT, processId)) {
v8::Local<v8::String> v8String = v8::String::NewFromUtf8(isolate, ("Failed to send Ctrl-C event. Error code: " + std::to_string(GetLastError())).c_str()).ToLocalChecked();
isolate->ThrowException(v8::Exception::Error(v8String));
CloseHandle(hProcess);
return;
} else {
args.GetReturnValue().Set(true);
return;
}
} else {
// Disable Ctrl-C handling for our program
if (!SetConsoleCtrlHandler(NULL, TRUE)) {
v8::Local<v8::String> v8String = v8::String::NewFromUtf8(isolate, ("Failed to disable Ctrl-C handling. Error code: " + std::to_string(GetLastError())).c_str()).ToLocalChecked();
isolate->ThrowException(v8::Exception::Error(v8String));
CloseHandle(hProcess);
return;
}
// Send Ctrl-C event
if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
v8::Local<v8::String> v8String = v8::String::NewFromUtf8(isolate, ("Failed to send Ctrl-C event. Error code: " + std::to_string(GetLastError())).c_str()).ToLocalChecked();
isolate->ThrowException(v8::Exception::Error(v8String));
// Re-enable Ctrl-C handling
if (!SetConsoleCtrlHandler(NULL, FALSE)) {
v8::Local<v8::String> v8String = v8::String::NewFromUtf8(isolate, ("Failed to re-enable Ctrl-C handling. Error code: " + std::to_string(GetLastError())).c_str()).ToLocalChecked();
isolate->ThrowException(v8::Exception::Error(v8String));
}
FreeConsole();
CloseHandle(hProcess);
return;
} else {
// Wait for process to exit
if (WaitForSingleObject(hProcess, 2000) != WAIT_OBJECT_0) {
v8::Local<v8::String> v8String = v8::String::NewFromUtf8(isolate, "Process did not exit within 2 seconds.").ToLocalChecked();
isolate->ThrowException(v8::Exception::Error(v8String));
}
// Re-enable Ctrl-C handling
if (!SetConsoleCtrlHandler(NULL, FALSE)) {
v8::Local<v8::String> v8String = v8::String::NewFromUtf8(isolate, ("Failed to re-enable Ctrl-C handling. Error code: " + std::to_string(GetLastError())).c_str()).ToLocalChecked();
isolate->ThrowException(v8::Exception::Error(v8String));
}
FreeConsole();
CloseHandle(hProcess);
return;
}
}
// Re-enable Ctrl-C handling
if (!SetConsoleCtrlHandler(NULL, FALSE)) {
v8::Local<v8::String> v8String = v8::String::NewFromUtf8(isolate, "Failed to re-enable Ctrl-C handling").ToLocalChecked();
isolate->ThrowException(v8::Exception::Error(v8String));
CloseHandle(hProcess);
return;
}
FreeConsole();
CloseHandle(hProcess);
args.GetReturnValue().Set(True(isolate));
#endif
}
void Init(Local < Object > exports) {
NODE_SET_METHOD(exports, "sigintWindows", SigintWindows);
}
NODE_MODULE(ctrlc, Init)
} // namespace ctrlc
Feel free to provide any solutions or suggestions.
NB: I've tried assigning an handler, instead of a none, but that doesn't work either.
CTRL+Csignal is sent by the terminal, and is received by all the processesABCandDconnected to that terminal at the exact same time.you should kill the child process
Cby its handle using TerminateProcess, and not by sending aCTRL+Cevent, this way your processAis not affected, needless to say this will preventCfrom doing any resources cleanup, so be careful of that.another option is to launch
BandCin a separate new terminal, this wayCTRL+Cis sent to the terminal containing onlyBandC, thereforeAwill be unaffected.