Author Topic: Windows how to send Ctrl-C instead of Ctrl-Break for "stop build" alternative  (Read 388 times)

rowbearto

  • Senior Community Member
  • Posts: 1541
  • Hero Points: 113
At a Windows command prompt when I run a Java program, its behavior is different when pressing Ctrl-C vs pressing Ctrl-Break. Ctrl-Break generates a "thread dump" (https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr019.html), while Ctrl-C allows my shutdown handler to run and for my java code to gracefully exit according to my Java code.

When I execute my gradle java program inside of SlickEdit and I do stop_build or press Ctrl-C in the process buffer, it seems to be sending a Ctrl-Break to my Java program which is not what I want as I'm seeing a thread dump instead of my shutdown handler executed. I would rather have it send a Ctrl-C, as my desired behavior is to make my code terminate early and cleanup, not get a thread dump.

How to get SE to send a Ctrl-C and not Ctrl-Break?

Clark

  • SlickEdit Team Member
  • Senior Community Member
  • *
  • Posts: 4964
  • Hero Points: 409
ntpinit.exe is using this call:

GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,process_id);

When I use this:

GenerateConsoleCtrlEvent(CTRL_C_EVENT,process_id);

Nothing gets stopped. Seems to have no effect at all. I don't know a work around for this. That's why SlickEdit uses Ctrl_BREAK_EVENT. It could be that this event doesn't get passed along to the child process (only CMD.EXE). Also could be it just doesn't work.


rowbearto

  • Senior Community Member
  • Posts: 1541
  • Hero Points: 113
Is it possible that SlickEdit inside of ntpinit.exe has disabled (or never enabled) the passing of Ctrl-C down to child processes?

See: https://docs.microsoft.com/en-us/windows/console/setconsolectrlhandler

If ntpinit.exe did SetConsoleCtrlHandler(null, FALSE), it would ensure that Ctrl-C gets sent to child processes?

Maybe there is a reason why ntpinit.exe disables the sending of Ctrl-C to child processes?

I found a utility that allows me to send Ctrl-C in a similar way one can send SIGINT in Linux, "win-kill": https://github.com/alirdn/windows-kill .

When I open a command window outside of SlickEdit and run my java code, I am able to send it a Ctrl-C via win-kill from another window and it is indeed received and aborts my Java code in the way I expect (and I'm able to catch this in my java code).

But when I run the java program in the process buffer of SE by directly invoking "java ...." at the prompt in the process buffer, using win-kill to the PID of that java code doesn't seem to work.

When I look at the process tree in Process Explorer/Task Manager, I see SlickEdit invokes ntpinit.exe, which the invokes cmd.exe, and under that is java.exe.

So I suspect that it is ntpinit.exe that is blocking the sending of Ctrl-C to child processes somehow.

Also very interesting: https://stackoverflow.com/questions/813086/can-i-send-a-ctrl-c-sigint-to-an-application-on-windows

I implemented a java version of "win-kill" based on the "In Java, using JNA ..." solution on that java page, and it also is able to interrupt my java code in my own cmd, however that does not work when the Java code is launched inside SlickEdit.

So I suspect it is SlickEdit itself, possibly ntpinit.exe, that is blocking the Ctrl-C from getting to child processes.
« Last Edit: November 29, 2019, 08:35:31 pm by rowbearto »

rowbearto

  • Senior Community Member
  • Posts: 1541
  • Hero Points: 113
Another question, when ntpinit.exe its child cmd.exe is spawned, is either one of them made the root of a new process group?

https://docs.microsoft.com/en-us/windows/console/console-process-groups

I remember a while back when there were similar issues in Linux it was required to create a new sessionid/process group. Maybe similar issue needed in windows too?

https://community.slickedit.com/index.php?topic=15406.0

Also interesting, from here: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa?redirectedfrom=MSDN

Quote
When a process is created with CREATE_NEW_PROCESS_GROUP specified, an implicit call to SetConsoleCtrlHandler(NULL,TRUE) is made on behalf of the new process; this means that the new process has CTRL+C disabled. This lets shells handle CTRL+C themselves, and selectively pass that signal on to sub-processes. CTRL+BREAK is not disabled, and may be used to interrupt the process/process group.

« Last Edit: November 29, 2019, 08:59:20 pm by rowbearto »

rowbearto

  • Senior Community Member
  • Posts: 1541
  • Hero Points: 113
Clark: Another question, you said ntpinit.exe is doing:

GenerateConsoleCtrlEvent(CTRL_C_EVENT,process_id);

What is the process_id that it is using? I'm assuming it is the process id of cmd.exe?

According to generateconsolectrlevent documentation (https://docs.microsoft.com/en-us/windows/console/generateconsolectrlevent):

Quote
dwProcessGroupId [in]
The identifier of the process group to receive the signal. A process group is created when the CREATE_NEW_PROCESS_GROUP flag is specified in a call to the CreateProcess function.

So I think it means that when ntpinit.exe spawns cmd.exe that cmd.exe is in a new process group? Otherwise you would need to send 0 as the process group as per the docs:

Quote
If this parameter is zero, the signal is generated in all processes that share the console of the calling process.

But then in my last post, we also saw:

Quote
When a process is created with CREATE_NEW_PROCESS_GROUP specified, an implicit call to SetConsoleCtrlHandler(NULL,TRUE) is made on behalf of the new process; this means that the new process has CTRL+C disabled. This lets shells handle CTRL+C themselves, and selectively pass that signal on to sub-processes. CTRL+BREAK is not disabled, and may be used to interrupt the process/process group.

So it is very possible that this cmd.exe is getting its ctrl-c disabled implicitly?

Maybe what is needed is a new process between ntpinit.exe and cmd.exe that is the process group leader, and this inbetween process calls
SetConsoleCtrlHandler(NULL,FALSE) to enable Ctrl-C handling and then spawns cmd.exe?

Or without this intermediate process, perhaps ntpinit.exe can be the process group leader but then when it sends the Ctrl-C event it can ignore it somehow while all other child processes get it?

This particular stack overflow answer also talks about an intermediate process: https://stackoverflow.com/questions/813086/can-i-send-a-ctrl-c-sigint-to-an-application-on-windows/2445728#2445728
« Last Edit: November 29, 2019, 09:26:21 pm by rowbearto »

Clark

  • SlickEdit Team Member
  • Senior Community Member
  • *
  • Posts: 4964
  • Hero Points: 409
Thanks for all this info. I'm still looking some of it over.

ntpinit.exe spawns CMD.EXE with the CREATE_NEW_PROCESS_GROUP flag.

I've trying messing with a number of things but no luck. I've even tried traversing the processes invoked by CMD.exe with no luck. TerminateProcess works but GenerateConsoleCtrlEvent doesn't work. Doesn't even work for CTRL_BREAK_EVENT. There must be some weird stuff going on here since it works when it's sent to CMD.EXE

rowbearto

  • Senior Community Member
  • Posts: 1541
  • Hero Points: 113
Thanks Clark!

I just stumbled upon a few more interesting tidbits of info.

https://ss64.com/nt/start.html

When ntpinit spawns cmd.exe, is it using start /b? If /b flag used then:

Quote
Start application without creating a new window. In this case Ctrl-C will be ignored - leaving Ctrl-Break as the only way to interrupt the application.

I discovered about /b flag from the 2015-12-05 19:13 entry on this post: https://bugs.python.org/issue11361

Are you using start /b for cmd.exe?

Clark

  • SlickEdit Team Member
  • Senior Community Member
  • *
  • Posts: 4964
  • Hero Points: 409
ntpinit.exe doesn't use the start command. That way, a new window is not created. Also, if a new window is created, then ntpinit.exe can't get redirected output.

rowbearto

  • Senior Community Member
  • Posts: 1541
  • Hero Points: 113
OK, that is good to know.

Based on all I've written and seen then, the most promising approach could be to put an intermediate process between ntpinit.exe and cmd.exe, where this intermediate process is the process group leader, it enables control-c for all children - SetConsoleCtrlHandler(null, FALSE), and then launches cmd.exe (not as new process group).

Only caveat is whether cmd.exe will create children that ignore Ctrl-C, then these children would be required to perform SetConsoleCtrlHandler(null, FALSE). If that is the case then would need another (or move) intermediate process between cmd.exe and the child that will call SetConsoleCtrlHandler(null, FALSE) and then the desired child.

rowbearto

  • Senior Community Member
  • Posts: 1541
  • Hero Points: 113
Or if ntpinit.exe could be spawned as a new process group, and it does SetConsoleCtrlHandler(null, FALSE), and does not make cmd.exe a process group leader. Then need a way for ntpinit.exe to ignore Ctrl-C (possibly by installing some other handler via SetConsoleCtrlHandler() and returning TRUE from it), see: https://docs.microsoft.com/en-us/windows/console/registering-a-control-handler-function
« Last Edit: November 29, 2019, 10:05:47 pm by rowbearto »

Clark

  • SlickEdit Team Member
  • Senior Community Member
  • *
  • Posts: 4964
  • Hero Points: 409
I tried the above. That didn't work well. Added an actual signal handler for ntpinit.exe did have an effect but didn't work for Ctrl+C. It's pretty clear to me that Ctrl+C can't work unless there's a terminal window (at a minimum).

I'm tying to get a hybrid solution to work.

Send Ctrl+Break signal
Wait .1 seconds.
Terminate all child process

right now, the terminate all child processes isn't working quite right. Works on a simple program but not on a complex one. Hopefully, I'm just not traversing the tree correctly.

rowbearto

  • Senior Community Member
  • Posts: 1541
  • Hero Points: 113
OK, well it seems that using ntpinit as the process leader (or a new intermediate process) you may need to do many special things as in this post: https://stackoverflow.com/questions/813086/can-i-send-a-ctrl-c-sigint-to-an-application-on-windows/2445728#2445728

Quote
Create a new helper application "Helper.exe". This application will sit between your application (parent) and the child process you want to be able to close. It will also create the actual child process. You must have this "middle man" process or GenerateConsoleCtrlEvent() will fail.

Use some kind of IPC mechanism to communicate from the parent to the helper process that the helper should close the child process. When the helper get this event it calls "GenerateConsoleCtrlEvent(CTRL_BREAK, 0)" which closes down itself and the child process. I used an event object for this myself which the parent completes when it wants to cancel the child process.

To create your Helper.exe create it with CREATE_NO_WINDOW and CREATE_NEW_PROCESS_GROUP. And when creating the child process create it with no flags (0) meaning it will derive the console from its parent. Failing to do this will cause it to ignore the event.

It is very important that each step is done like this. I've been trying all different kinds of combinations but this combination is the only one that works. You can't send a CTRL_C event. It will return success but will be ignored by the process. CTRL_BREAK is the only one that works. Doesn't really matter since they will both call ExitProcess() in the end.

You also can't call GenerateConsoleCtrlEvent() with a process groupd id of the child process id directly allowing the helper process to continue living. This will fail as well.

Now it does say that they failed to get Ctrl-C to work, but I suspect is that he didn't call SetConsoleCtrlHandler(null, FALSE), as that is not mentioned.

Clark

  • SlickEdit Team Member
  • Senior Community Member
  • *
  • Posts: 4964
  • Hero Points: 409
I tried every combination of SetConsoleCtrlHandler. It didn't help enough. Only adding an actual event handler allowed the BREAK signal to work.

I've got my hybrid solution working. Had to gather the child_pid list before sending the break signal. Works for java apps.

rowbearto

  • Senior Community Member
  • Posts: 1541
  • Hero Points: 113
Did you try making a new process inbetween ntpinit.exe and cmd.exe? Or did you just try making ntpinit.exe as process leader and cmd.exe as not process leader?

Clark

  • SlickEdit Team Member
  • Senior Community Member
  • *
  • Posts: 4964
  • Hero Points: 409
I just made ntpinit.exe the leader and CMD.EXE not the leader. Anything that could be done in an additional process can be done in ntpinit. Also, ntpinit can not be allowed to terminated on Ctrl+BREAK or Ctrl+C since even CMD.EXE would end up terminating. That would be true for an additional process under ntpinit.