This comment has been minimized.

I'm was running 2.14.2.windows.1 and in the whole MINGW64 Git Bash MinTTY shell on Windows 10 and running into the same or similar issue where CTRL+C didn't seem to be sending the correct signal.

In my case I'm starting up an http server using Node and closing it with CTRL+C. If I start the server using just node index.js, one process is spawned and can be closed with CTRL+C and the used port will be released.

If I start the same server using npm run demo (which itself is actually calling node index.js) two processes are spawned and CTRL+C will only close one of them, orphaning the server process and causing an EADDRINUSE error the next time I try to run npm run demo.

This bug doesn't show up on a Windows 7 box or on a Linux Mint box running Git 2.7.4. In both cases, two processes are spawned, but CTRL+C from the command line will close both and free up the port.

Installing 2.15.1.windows.2 didn't solve the problem. My installation of Git was in C:/Users/<username>/AppData/Local/Programs/Git originally. After uninstalling all of those files were removed and there was no C:/Program Files/Git folder to manually clear out. After reinstalling, Git is now installed at C:/Program Files/Git, but I'm still encountering the bug and still digging for a solution.

Oh wow. This elicits quite a negative reaction here. Probably not what you expected.

If you want anybody to work for you, to solve your problem, you might want to take a different approach.

As to the original report: this should be addressed by our recent change in how processes are killed when the user presses Ctrl+C.

If it still does not work for you in Git for Windows v2.16.2, you may want to consider selecting the "ConHost" option rather than the "MinTTY" option as terminal for the Git Bash. That should fix the issue, too.

dscho
added a commit
to git-for-windows/msys2-runtime
that referenced
this issue
Apr 23, 2018

This thing again...
Background: when you hit Ctrl+C on Linux or macOS, a signal (SIGINT) is
sent to the foreground process and its child processes. This signal can
be intercepted by installing a signal handler for this specific signal.
On Windows, there is no precise equivalent for this system.
Instead, the Ctrl+C is translated by the current ConHost (i.e. the
container running the Console processes) to a ConsoleCtrl event that is
sent to all processes attached to that Console. If any of these
processes installed a handler via SetConsoleCtrlHandler(), they can
intercept that event (and avoid exiting or doing some cleanup work).
On Linux and macOS (and every Unix flavor, really), processes can also
be killed via the `kill` executable, which really just sends a signal to
the process, typically SIGTERM. Processes can intercept that signal,
too. To force processes to terminate, without giving them any chance to
prevent that, SIGKILL can be sent. There is no equivalent for SIGTERM on
Windows. To emulate SIGKILL on Windows, TerminateProcess() can be used,
but it only kills one process (unlike SIGKILL, which is sent also to the
child processes).
In Git for Windows, we struggled with emulating SIGINT, SIGTERM and
SIGKILL handling essentially since the beginning of the efforts to port
Git to Windows.
At least the SIGINT part of the problem becomes a lot worse when using a
terminal window other than cmd.exe's: as long as using cmd.exe (AKA
"ConHost"), Ctrl+C is handled entirely outside of our code. But with the
big jump from v1.x to v2.x, Git for Windows not only switched from MSys
to MSYS2, but also started using MinTTY as the default terminal window,
which uses the MSYS2 runtime-provided pseudo terminals (inherited from
Cygwin thanks to the MSYS2 runtime being a "friendly fork" of Cygwin).
When Ctrl+C is pressed in MinTTY, all of the signaling has to be done by
our code.
The original code to handle Ctrl+C comes straight from Cygwin. It simply
ignores the entire conundrum for non-Cygwin processes and simply calls
TerminateProcess() on them, leaving spawned child processes running.
The first attempt at fixing "the Ctrl+C problem" (with the symptom that
interrupting `git clone ...` would not stop the actual download of the
Git objects that was still running in a child process) was
c4ba4e3. It
simply enumerated all the processes' process IDs and parent process IDs
and extracted the tree of (possibly transitive) child processes of the
process to kill, then called TerminateProcess() on them.
This solved the problem with interrupting `git clone`, but it did not
address the problem that Git typically wants to "clean up" when being
interrupted. In particular, Git installs atexit() and signal handlers to
remove .lock files. The most common symptom was that a stale
.git/index.lock file was still present after interrupting a Git process.
Based on the idea presented in Dr Dobb's Journal in the article "A Safer
Alternative to TerminateProcess()" by Andrew Tucker (July 1, 1999)
http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547
we changed our handling to inject a remote thread calling ExitProcess()
first, and fall back to TerminateProcess() the process tree instead:
e9cb332
This change was a little misguided in hindsight, as it only called
TerminateProcess() on the process tree, but expected the atexit()
handler of Git to take care of the child processes when killing the
process via the remote ExitProcess() method.
Therefore, we changed the strategy once again, to inject ExitProcess()
threads into the child processes of the process to kill, too:
53e5c03
(That commit also tries to handle Cygwin process among the child
processes by sending Cygwin signals, but unfortunately that part of the
commit was buggy.)
This worked well for Git processes. However, Git Bash is used in all
kinds of circumstances, including launching Maven, or node.js scripts
that want to intercept SIGINT. Naturally, these callees have no idea
that Git for Windows injects an ExitProcess() with exit code 130
(corresponding to 0x100 + SIGINT). Therefore, they never "got" the
signal.
So what is it that happens when ConHost generates a ConsoleCtrl event?
This question was asked and answered in the excellent blog post at:
http://stanislavs.org/stopping-command-line-applications-programatically-with-ctrl-c-events-from-net/#comment-2880
Essentially, the same happens as what we did with ExitProcess(): a
remote thread gets injected, with the event type as parameter. Of course
it is not ExitProcess() that is called, but CtrlRoutine(). This function
lives in kernel32.dll, too, but it is not exported, i.e.
GetProcAddress() won't find it. The trick proposed in the blog post (to
send a test ConsoleCtrl event to the process itself, using a special
handler that then inspects the stack trace to figure out the address of
the caller) does not work for us, however: it would send a
CTRL_BREAK_EVENT to *all* processes attached to the same Console,
essentially killing MinTTY.
But could we make this still work somehow? Yes, we could. We came up
with yet another trick up our sleeves: instead of determining the
address of kernel32!CtrlRoutine in our own process, we spawn a new one,
with a new Console, to avoid killing MinTTY. To do that, we need a
helper .exe, of course, which we put into /usr/libexec/. If this helper
is not found, we fall back to the previous methods of injecting
ExitProcess() or calling TerminateProcess().
This method (to spawn a helper .exe) has a further incidental benefit:
by compiling 32-bit *and* 64-bit helpers and providing them as
getprocaddr32.exe and getprocaddr64.exe, we can now also handle 32-bit
processes in a 64-bit Git for Windows. Sadly not vice versa: calling
CreateRemoteThread() on a 64-bit process from a 32-bit process seems to
fail all the time (and require a lot of assembly hackery to fix that I
am not really willing to include in Git for Windows' MSYS2 runtime).
The current method was implemented in this commit:
ca6188a
This is the hopeful final fix for
git-for-windows/git#1491,
git-for-windows/git#1470,
git-for-windows/git#1248,
git-for-windows/git#1239,
git-for-windows/git#227,
git-for-windows/git#1553,
nodejs/node#16103, and plenty other tickets
that petered out mostly due to a willingness of community members to
leave all the hard work to a single, already overworked person.
This fix also partially helps
git-for-windows/git#1629 (only partially
because the user wanted to quit the pager using Ctrl+C, which is not the
intended consequence of a Ctrl+C: it should stop the Git process, but
not the pager).
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>

dscho
added a commit
to git-for-windows/MSYS2-packages
that referenced
this issue
Apr 23, 2018

This thing again...
Background: when you hit Ctrl+C on Linux or macOS, a signal (SIGINT) is
sent to the foreground process and its child processes. This signal can
be intercepted by installing a signal handler for this specific signal.
On Windows, there is no precise equivalent for this system.
Instead, the Ctrl+C is translated by the current ConHost (i.e. the
container running the Console processes) to a ConsoleCtrl event that is
sent to all processes attached to that Console. If any of these
processes installed a handler via SetConsoleCtrlHandler(), they can
intercept that event (and avoid exiting or doing some cleanup work).
On Linux and macOS (and every Unix flavor, really), processes can also
be killed via the `kill` executable, which really just sends a signal to
the process, typically SIGTERM. Processes can intercept that signal,
too. To force processes to terminate, without giving them any chance to
prevent that, SIGKILL can be sent. There is no equivalent for SIGTERM on
Windows. To emulate SIGKILL on Windows, TerminateProcess() can be used,
but it only kills one process (unlike SIGKILL, which is sent also to the
child processes).
In Git for Windows, we struggled with emulating SIGINT, SIGTERM and
SIGKILL handling essentially since the beginning of the efforts to port
Git to Windows.
At least the SIGINT part of the problem becomes a lot worse when using a
terminal window other than cmd.exe's: as long as using cmd.exe (AKA
"ConHost"), Ctrl+C is handled entirely outside of our code. But with the
big jump from v1.x to v2.x, Git for Windows not only switched from MSys
to MSYS2, but also started using MinTTY as the default terminal window,
which uses the MSYS2 runtime-provided pseudo terminals (inherited from
Cygwin thanks to the MSYS2 runtime being a "friendly fork" of Cygwin).
When Ctrl+C is pressed in MinTTY, all of the signaling has to be done by
our code.
The original code to handle Ctrl+C comes straight from Cygwin. It simply
ignores the entire conundrum for non-Cygwin processes and simply calls
TerminateProcess() on them, leaving spawned child processes running.
The first attempt at fixing "the Ctrl+C problem" (with the symptom that
interrupting `git clone ...` would not stop the actual download of the
Git objects that was still running in a child process) was
git-for-windows/msys2-runtime@c4ba4e3. It
simply enumerated all the processes' process IDs and parent process IDs
and extracted the tree of (possibly transitive) child processes of the
process to kill, then called TerminateProcess() on them.
This solved the problem with interrupting `git clone`, but it did not
address the problem that Git typically wants to "clean up" when being
interrupted. In particular, Git installs atexit() and signal handlers to
remove .lock files. The most common symptom was that a stale
.git/index.lock file was still present after interrupting a Git process.
Based on the idea presented in Dr Dobb's Journal in the article "A Safer
Alternative to TerminateProcess()" by Andrew Tucker (July 1, 1999)
http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547
we changed our handling to inject a remote thread calling ExitProcess()
first, and fall back to TerminateProcess() the process tree instead:
git-for-windows/msys2-runtime@e9cb332
This change was a little misguided in hindsight, as it only called
TerminateProcess() on the process tree, but expected the atexit()
handler of Git to take care of the child processes when killing the
process via the remote ExitProcess() method.
Therefore, we changed the strategy once again, to inject ExitProcess()
threads into the child processes of the process to kill, too:
git-for-windows/msys2-runtime@53e5c03
(That commit also tries to handle Cygwin process among the child
processes by sending Cygwin signals, but unfortunately that part of the
commit was buggy.)
This worked well for Git processes. However, Git Bash is used in all
kinds of circumstances, including launching Maven, or node.js scripts
that want to intercept SIGINT. Naturally, these callees have no idea
that Git for Windows injects an ExitProcess() with exit code 130
(corresponding to 0x100 + SIGINT). Therefore, they never "got" the
signal.
So what is it that happens when ConHost generates a ConsoleCtrl event?
This question was asked and answered in the excellent blog post at:
http://stanislavs.org/stopping-command-line-applications-programatically-with-ctrl-c-events-from-net/#comment-2880
Essentially, the same happens as what we did with ExitProcess(): a
remote thread gets injected, with the event type as parameter. Of course
it is not ExitProcess() that is called, but CtrlRoutine(). This function
lives in kernel32.dll, too, but it is not exported, i.e.
GetProcAddress() won't find it. The trick proposed in the blog post (to
send a test ConsoleCtrl event to the process itself, using a special
handler that then inspects the stack trace to figure out the address of
the caller) does not work for us, however: it would send a
CTRL_BREAK_EVENT to *all* processes attached to the same Console,
essentially killing MinTTY.
But could we make this still work somehow? Yes, we could. We came up
with yet another trick up our sleeves: instead of determining the
address of kernel32!CtrlRoutine in our own process, we spawn a new one,
with a new Console, to avoid killing MinTTY. To do that, we need a
helper .exe, of course, which we put into /usr/libexec/. If this helper
is not found, we fall back to the previous methods of injecting
ExitProcess() or calling TerminateProcess().
This method (to spawn a helper .exe) has a further incidental benefit:
by compiling 32-bit *and* 64-bit helpers and providing them as
getprocaddr32.exe and getprocaddr64.exe, we can now also handle 32-bit
processes in a 64-bit Git for Windows. Sadly not vice versa: calling
CreateRemoteThread() on a 64-bit process from a 32-bit process seems to
fail all the time (and require a lot of assembly hackery to fix that I
am not really willing to include in Git for Windows' MSYS2 runtime).
The current method was implemented in this commit:
git-for-windows/msys2-runtime@ca6188a
This is the hopeful final fix for
git-for-windows/git#1491,
git-for-windows/git#1470,
git-for-windows/git#1248,
git-for-windows/git#1239,
git-for-windows/git#227,
git-for-windows/git#1553,
nodejs/node#16103, and plenty other tickets
that petered out mostly due to a willingness of community members to
leave all the hard work to a single, already overworked person.
This fix also partially helps
git-for-windows/git#1629 (only partially
because the user wanted to quit the pager using Ctrl+C, which is not the
intended consequence of a Ctrl+C: it should stop the Git process, but
not the pager).
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>

This comment has been minimized.

Wow... I've been trying to figure out why ctrl-C was taking forever to register in my git bash for windows when running webpack-dev-server (a problem I've had for the past year) and found this issue, and as luck would have it find that it was fixed two days ago! I'll have to wait for it to get to a 'maintained' release state but I'm glad it's been fixed. I've been going through the comments as well as the author's (Johannes) summary of the history of this issue and want to thank him for taking this challenge and fixing it when others in the community would not. @dscho, perhaps an alternative perspective on this would help... perhaps nobody else was up to the task, and you are the only one in this community with the technical expertise to fix it; the Linus Torvalds or Richard Stallman of Git for Windows, so to speak. Whatever the reason, you are indeed the man... thank you!

dscho
added a commit
to git-for-windows/msys2-runtime
that referenced
this issue
Nov 9, 2018

This thing again...
Background: when you hit Ctrl+C on Linux or macOS, a signal (SIGINT) is
sent to the foreground process and its child processes. This signal can
be intercepted by installing a signal handler for this specific signal.
On Windows, there is no precise equivalent for this system.
Instead, the Ctrl+C is translated by the current ConHost (i.e. the
container running the Console processes) to a ConsoleCtrl event that is
sent to all processes attached to that Console. If any of these
processes installed a handler via SetConsoleCtrlHandler(), they can
intercept that event (and avoid exiting or doing some cleanup work).
On Linux and macOS (and every Unix flavor, really), processes can also
be killed via the `kill` executable, which really just sends a signal to
the process, typically SIGTERM. Processes can intercept that signal,
too. To force processes to terminate, without giving them any chance to
prevent that, SIGKILL can be sent. There is no equivalent for SIGTERM on
Windows. To emulate SIGKILL on Windows, TerminateProcess() can be used,
but it only kills one process (unlike SIGKILL, which is sent also to the
child processes).
In Git for Windows, we struggled with emulating SIGINT, SIGTERM and
SIGKILL handling essentially since the beginning of the efforts to port
Git to Windows.
At least the SIGINT part of the problem becomes a lot worse when using a
terminal window other than cmd.exe's: as long as using cmd.exe (AKA
"ConHost"), Ctrl+C is handled entirely outside of our code. But with the
big jump from v1.x to v2.x, Git for Windows not only switched from MSys
to MSYS2, but also started using MinTTY as the default terminal window,
which uses the MSYS2 runtime-provided pseudo terminals (inherited from
Cygwin thanks to the MSYS2 runtime being a "friendly fork" of Cygwin).
When Ctrl+C is pressed in MinTTY, all of the signaling has to be done by
our code.
The original code to handle Ctrl+C comes straight from Cygwin. It simply
ignores the entire conundrum for non-Cygwin processes and simply calls
TerminateProcess() on them, leaving spawned child processes running.
The first attempt at fixing "the Ctrl+C problem" (with the symptom that
interrupting `git clone ...` would not stop the actual download of the
Git objects that was still running in a child process) was
c4ba4e3. It
simply enumerated all the processes' process IDs and parent process IDs
and extracted the tree of (possibly transitive) child processes of the
process to kill, then called TerminateProcess() on them.
This solved the problem with interrupting `git clone`, but it did not
address the problem that Git typically wants to "clean up" when being
interrupted. In particular, Git installs atexit() and signal handlers to
remove .lock files. The most common symptom was that a stale
.git/index.lock file was still present after interrupting a Git process.
Based on the idea presented in Dr Dobb's Journal in the article "A Safer
Alternative to TerminateProcess()" by Andrew Tucker (July 1, 1999)
http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547
we changed our handling to inject a remote thread calling ExitProcess()
first, and fall back to TerminateProcess() the process tree instead:
e9cb332
This change was a little misguided in hindsight, as it only called
TerminateProcess() on the process tree, but expected the atexit()
handler of Git to take care of the child processes when killing the
process via the remote ExitProcess() method.
Therefore, we changed the strategy once again, to inject ExitProcess()
threads into the child processes of the process to kill, too:
53e5c03
(That commit also tries to handle Cygwin process among the child
processes by sending Cygwin signals, but unfortunately that part of the
commit was buggy.)
This worked well for Git processes. However, Git Bash is used in all
kinds of circumstances, including launching Maven, or node.js scripts
that want to intercept SIGINT. Naturally, these callees have no idea
that Git for Windows injects an ExitProcess() with exit code 130
(corresponding to 0x100 + SIGINT). Therefore, they never "got" the
signal.
So what is it that happens when ConHost generates a ConsoleCtrl event?
This question was asked and answered in the excellent blog post at:
http://stanislavs.org/stopping-command-line-applications-programatically-with-ctrl-c-events-from-net/#comment-2880
Essentially, the same happens as what we did with ExitProcess(): a
remote thread gets injected, with the event type as parameter. Of course
it is not ExitProcess() that is called, but CtrlRoutine(). This function
lives in kernel32.dll, too, but it is not exported, i.e.
GetProcAddress() won't find it. The trick proposed in the blog post (to
send a test ConsoleCtrl event to the process itself, using a special
handler that then inspects the stack trace to figure out the address of
the caller) does not work for us, however: it would send a
CTRL_BREAK_EVENT to *all* processes attached to the same Console,
essentially killing MinTTY.
But could we make this still work somehow? Yes, we could. We came up
with yet another trick up our sleeves: instead of determining the
address of kernel32!CtrlRoutine in our own process, we spawn a new one,
with a new Console, to avoid killing MinTTY. To do that, we need a
helper .exe, of course, which we put into /usr/libexec/. If this helper
is not found, we fall back to the previous methods of injecting
ExitProcess() or calling TerminateProcess().
This method (to spawn a helper .exe) has a further incidental benefit:
by compiling 32-bit *and* 64-bit helpers and providing them as
getprocaddr32.exe and getprocaddr64.exe, we can now also handle 32-bit
processes in a 64-bit Git for Windows. Sadly not vice versa: calling
CreateRemoteThread() on a 64-bit process from a 32-bit process seems to
fail all the time (and require a lot of assembly hackery to fix that I
am not really willing to include in Git for Windows' MSYS2 runtime).
The current method was implemented in this commit:
ca6188a
This is the hopeful final fix for
git-for-windows/git#1491,
git-for-windows/git#1470,
git-for-windows/git#1248,
git-for-windows/git#1239,
git-for-windows/git#227,
git-for-windows/git#1553,
nodejs/node#16103, and plenty other tickets
that petered out mostly due to a willingness of community members to
leave all the hard work to a single, already overworked person.
This fix also partially helps
git-for-windows/git#1629 (only partially
because the user wanted to quit the pager using Ctrl+C, which is not the
intended consequence of a Ctrl+C: it should stop the Git process, but
not the pager).
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>