Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ctrl-C in Windows cmd shell from where git gui was launched kills git gui #1553

Closed
1 task done
LouisStrous opened this issue Mar 13, 2018 · 6 comments
Closed
1 task done
Labels

Comments

@LouisStrous
Copy link

LouisStrous commented Mar 13, 2018

  • I was not able to find an open or closed issue matching what I'm seeing

Setup

  • Which version of Git for Windows are you using? Is it 32-bit or 64-bit?
$ git --version --build-options

git version 2.16.2.windows.1
cpu: x86_64
built from commit: e1848984d1004040ec5199e749b5f282ddf4bb09
sizeof-long: 4
  • Which version of Windows are you running? Vista, 7, 8, 10? Is it 32-bit or 64-bit?

64-bit Windows 7 Professional.

$ cmd.exe /c ver

Microsoft Windows [Version 6.1.7601]
  • What options did you set as part of the installation? Or did you choose the
    defaults?
# One of the following:
> type "C:\Program Files\Git\etc\install-options.txt"
> type "C:\Program Files (x86)\Git\etc\install-options.txt"
> type "%USERPROFILE%\AppData\Local\Programs\Git\etc\install-options.txt"
$ cat /etc/install-options.txt

Editor Option: Notepad++
Path Option: Cmd
SSH Option: OpenSSH
CURL Option: OpenSSL
CRLF Option: CRLFCommitAsIs
Bash Terminal Option: MinTTY
Performance Tweaks FSCache: Enabled
Use Credential Manager: Enabled
Enable Symlinks: Disabled
  • Any other interesting things about your environment that might be related
    to the issue you're seeing?

None that I can think of.

Details

  • Which terminal/shell are you running Git from? e.g Bash/CMD/PowerShell/other

CMD

(start CMD shell, change to directory containing a git repository)
git gui
(click mouse inside CMD shell to give it focus again)
dir
(press Ctrl-C at any time after the dir command has finished)
  • What did you expect to occur after running these commands?
  1. git gui window is displayed
  2. a directory listing is displayed in the CMD shell (showing that the CMD shell really does have the focus at this point)
  3. pressing Ctrl-C displays a new command prompt in the CMD shell, as if I had entered a newline. The git gui windows is not affected.
  • What actually happened instead?

All as expected, except that the git gui window disappeared after pressing Ctrl-C.

  • If the problem was occurring with a specific repository, can you provide the
    URL to that repository to help us with testing?

None.

  • Additional details

I tried the same reproduction scenario also on a 64-bit Windows 10 Home system. With git version 2.9.3.windows.1, the issue did not occur. With git version 2.16.2.windows.1 (same as above), the issue did occur.

The workaround is to start the git gui through

start /b git gui
@dscho
Copy link
Member

dscho commented Mar 13, 2018

When I start git gui in a Git Bash, then the command does not return until the GUI is closed. When I hit Ctrl+C in the Bash window, it interrupts the GUI.

The thing is: git.exe is a console program. And the fact that git gui spawns a GUI does not change that.

I think this is intended behavior...

If you want to start Git GUI as a GUI application, you will have to call git-gui.exe (from C:\Program Files\Git\cmd, which should be in your PATH by default).

@dscho dscho closed this as completed Mar 13, 2018
@dscho dscho added the question label Mar 13, 2018
@LouisStrous
Copy link
Author

I apologize for not making myself clear enough. When I start git gui from the cmd shell (not a git bash), then the GUI appears and can be used. At the same time, the cmd shell displays a new prompt and can be used. For example, I can start another git GUI from the same cmd shell, so that two git GUIs are visible and can be used. So the git GUI and the cmd shell act independently, neither of them waiting for the other one to close.

My reproduction scenario mentions running a dir command in the cmd shell before pressing the Ctrl-C that makes the GUI disappear. I would not be able to run the dir command if the cmd shell was waiting for the git GUI to close. It is precisely the fact that the cmd shell and the GUI can be used simultaneously that makes it surprising and (to me) unexpected that the git GUI disappears when I press Ctrl-C in the cmd window from where the git GUI was started earlier.

Typically, I start a git GUI, and then use the same cmd shell for many other tasks while the GUI remains visible. I usually have several other cmd shells open as well. After some hours, I might run a command in the cmd shell that turns out to take longer than I am willing to wait, and then I press Ctrl-C to interrupt that command. And that doesn't just interrupt that command but also terminates the git GUI, if the cmd shell into which I press Ctrl-C happens to be the one from which I started git GUI hours ago. I typically have several cmd shells open at the same time, so it is a matter of chance whether the cmd shell into which I press Ctrl-C is the one from which I started the git GUI long ago. That's why it took me a while to figure out that this is what was happening.

@dscho
Copy link
Member

dscho commented Mar 14, 2018

When I start git gui from the cmd shell (not a git bash), then the GUI appears and can be used. At the same time, the cmd shell displays a new prompt and can be used.

I have no idea why that is the case, as git gui should wait until the GUI is closed. That is the intended behavior, and if you have any idea what is going wrong and how to fix it, that would be great.

For the record, the git.exe program that you call to execute git gui is a Console program, and it does not return until the GUI closes.

@dscho
Copy link
Member

dscho commented Apr 23, 2018

Okay, I think I figured this one out, as part of my work on #1491. The fix should hit https://wingit.blob.core.windows.net/files/index.html Real Soon Now, so please test it then.

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
c4ba4e3357f. 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:
e9cb332976c

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:
53e5c0313e1

(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:
ca6188a7520

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@c4ba4e3357f. 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@e9cb332976c

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@53e5c0313e1

(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@ca6188a7520

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
Copy link
Member

dscho commented Apr 23, 2018

The newest snapshot at https://wingit.blob.core.windows.net/files/index.html should fix this. Please test.

@LouisStrous
Copy link
Author

I forgot to mention that I get git commands to work from the Windows cmd shell by adding the location of the git commands (C:\Program Files\Git\bin, C:\Program Files\Git\usr\bin) to my PATH environment variable.

I reran the reproduction scenario with Git-prerelease-2.17.0.windows.1.38.g05ca542f78-64-bit.exe, and now pressing Ctrl-C in the cmd shell does not affect the git GUI window anymore. 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
c4ba4e3357f. 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:
e9cb332976c

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:
53e5c0313e1

(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:
ca6188a7520

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-runtime that referenced this issue Feb 16, 2019
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
c4ba4e3357f. 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:
e9cb332976c

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:
53e5c0313e1

(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:
ca6188a7520

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-runtime that referenced this issue Feb 20, 2019
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
c4ba4e3357f. 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:
e9cb332976c

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:
53e5c0313e1

(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:
ca6188a7520

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-runtime that referenced this issue Feb 20, 2019
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
c4ba4e3357f. 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:
e9cb332976c

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:
53e5c0313e1

(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:
ca6188a7520

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>
git-for-windows-ci pushed a commit to git-for-windows/msys2-runtime that referenced this issue Mar 5, 2019
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
c4ba4e3357f. 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:
e9cb332976c

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:
53e5c0313e1

(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:
ca6188a7520

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>
git-for-windows-ci pushed a commit to git-for-windows/msys2-runtime that referenced this issue Mar 9, 2019
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
c4ba4e3357f. 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:
e9cb332976c

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:
53e5c0313e1

(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:
ca6188a7520

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>
git-for-windows-ci pushed a commit to git-for-windows/msys2-runtime that referenced this issue Mar 16, 2019
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
c4ba4e3357f. 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:
e9cb332976c

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:
53e5c0313e1

(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:
ca6188a7520

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>
git-for-windows-ci pushed a commit to git-for-windows/msys2-runtime that referenced this issue Mar 31, 2019
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
c4ba4e3357f. 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:
e9cb332976c

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:
53e5c0313e1

(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:
ca6188a7520

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>
git-for-windows-ci pushed a commit to git-for-windows/msys2-runtime that referenced this issue Apr 6, 2019
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
c4ba4e3357f. 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:
e9cb332976c

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:
53e5c0313e1

(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:
ca6188a7520

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>
naveen521kk pushed a commit to naveen521kk/msys2-runtime that referenced this issue Mar 15, 2021
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@c4ba4e3357f. 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@e9cb332976c

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@53e5c0313e1

(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@ca6188a7520

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants