Skip to content

Commit 53e5c03

Browse files
Adam Smithdscho
Adam Smith
authored andcommittedJan 10, 2018
exit_process.h: fix handling of SIGINT and SIGTERM
Handle SIGINT and SIGTERM by injecting into the process a thread that runs ExitProcess. Use TerminateProcess otherwise. In both cases, enumerate the entire process tree. This fixes git-for-windows/git#1219 Signed-off-by: Adam Smith <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 9f53930 commit 53e5c03

File tree

1 file changed

+56
-38
lines changed

1 file changed

+56
-38
lines changed
 

‎winsup/cygwin/include/cygwin/exit_process.h

+56-38
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,56 @@
1414

1515
#include <tlhelp32.h>
1616

17+
static int
18+
terminate_process_with_remote_thread(HANDLE process, int exit_code)
19+
{
20+
static LPTHREAD_START_ROUTINE exit_process_address;
21+
if (!exit_process_address)
22+
{
23+
HINSTANCE kernel32 = GetModuleHandle ("kernel32");
24+
exit_process_address = (LPTHREAD_START_ROUTINE)
25+
GetProcAddress (kernel32, "ExitProcess");
26+
}
27+
DWORD thread_id;
28+
HANDLE thread = !exit_process_address ? NULL :
29+
CreateRemoteThread (process, NULL, 0, exit_process_address,
30+
(PVOID)exit_code, 0, &thread_id);
31+
32+
if (thread)
33+
{
34+
CloseHandle (thread);
35+
/*
36+
* Wait 10 seconds (arbitrary constant) for the process to
37+
* finish; After that grace period, fall back to terminating
38+
* non-gently.
39+
*/
40+
if (WaitForSingleObject (process, 10000) == WAIT_OBJECT_0)
41+
return 0;
42+
}
43+
44+
return -1;
45+
}
46+
1747
/**
18-
* Terminates the process corresponding to the process ID and all of its
19-
* directly and indirectly spawned subprocesses.
48+
* Terminates the process corresponding to the process ID
2049
*
21-
* This way of terminating the processes is not gentle: the processes get
22-
* no chance of cleaning up after themselves (closing file handles, removing
50+
* This way of terminating the processes is not gentle: the process gets
51+
* no chance of cleaning up after itself (closing file handles, removing
2352
* .lock files, terminating spawned processes (if any), etc).
2453
*/
2554
static int
26-
terminate_process_tree(HANDLE main_process, int exit_code)
55+
terminate_process(HANDLE process, int exit_code)
56+
{
57+
return int(TerminateProcess (process, exit_code));
58+
}
59+
60+
/**
61+
* Terminates the process corresponding to the process ID and all of its
62+
* directly and indirectly spawned subprocesses using the provided
63+
* terminate callback function
64+
*/
65+
static int
66+
terminate_process_tree(HANDLE main_process, int exit_code, int (*terminate)(HANDLE, int))
2767
{
2868
HANDLE snapshot = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0);
2969
PROCESSENTRY32 entry;
@@ -46,6 +86,7 @@ terminate_process_tree(HANDLE main_process, int exit_code)
4686
for (;;)
4787
{
4888
int orig_len = len;
89+
pid_t cyg_pid;
4990

5091
memset (&entry, 0, sizeof (entry));
5192
entry.dwSize = sizeof (entry);
@@ -57,6 +98,12 @@ terminate_process_tree(HANDLE main_process, int exit_code)
5798
{
5899
for (i = len - 1; i >= 0; i--)
59100
{
101+
cyg_pid = cygwin_winpid_to_pid(entry.th32ProcessID);
102+
if (cyg_pid > -1)
103+
{
104+
kill(cyg_pid, exit_code);
105+
continue;
106+
}
60107
if (pids[i] == entry.th32ProcessID)
61108
break;
62109
if (pids[i] == entry.th32ParentProcessID)
@@ -76,7 +123,7 @@ terminate_process_tree(HANDLE main_process, int exit_code)
76123

77124
if (process)
78125
{
79-
if (!TerminateProcess (process, exit_code))
126+
if (!(*terminate) (process, exit_code))
80127
ret = -1;
81128
CloseHandle (process);
82129
}
@@ -129,39 +176,10 @@ exit_process(HANDLE process, int exit_code)
129176

130177
if (GetExitCodeProcess (process, &code) && code == STILL_ACTIVE)
131178
{
132-
/*
133-
* We cannot determine the address of ExitProcess() for a process
134-
* that does not match the current architecture (e.g. for a 32-bit
135-
* process when we're running in 64-bit mode).
136-
*/
137-
if (process_architecture_matches_current (process))
138-
{
139-
static LPTHREAD_START_ROUTINE exit_process_address;
140-
if (!exit_process_address)
141-
{
142-
HINSTANCE kernel32 = GetModuleHandle ("kernel32");
143-
exit_process_address = (LPTHREAD_START_ROUTINE)
144-
GetProcAddress (kernel32, "ExitProcess");
145-
}
146-
DWORD thread_id;
147-
HANDLE thread = !exit_process_address ? NULL :
148-
CreateRemoteThread (process, NULL, 0, exit_process_address,
149-
(PVOID)exit_code, 0, &thread_id);
150-
151-
if (thread)
152-
{
153-
CloseHandle (thread);
154-
/*
155-
* Wait 10 seconds (arbitrary constant) for the process to
156-
* finish; After that grace period, fall back to terminating
157-
* non-gently.
158-
*/
159-
if (WaitForSingleObject (process, 10000) == WAIT_OBJECT_0)
160-
return 0;
161-
}
162-
}
179+
if (process_architecture_matches_current(process) && (exit_code == SIGINT || exit_code == SIGTERM))
180+
return terminate_process_tree (process, exit_code, terminate_process_with_remote_thread);
163181

164-
return terminate_process_tree (process, exit_code);
182+
return terminate_process_tree (process, exit_code, terminate_process);
165183
}
166184

167185
return -1;

16 commit comments

Comments
 (16)

afsmith92 commented on Jan 11, 2018

@afsmith92

@dscho Unfortunately after further investigation it seems the change I added doesn't fix the node ctrl+c issue.

All of the child processes are killed, but processes still can't respond to SIGINT or SIGKILL. It seems that line 179 always evaluates to false, and TerminateProcess is always used. It looks like after this line exit_code === SIGINT and exit_code === SIGTERM always evaluates to false. If I force the use of terminate_process_with_remote_thread. The node child processes don't get closed and this cleanup code doesn't run either:

process.on('SIGTERM', function() {
  console.log( "\nresponding to SIGTERM" );
  // other cleanup code
  process.exit(1);
});

process.on('SIGINT', function() {
  console.log( "\nresponding to SIGINT" );
  // other cleanup code
  process.exit(1);
});

I'm continuing to investigate. I'll see if I can make a change to terminate_process_with_remote_thread that resolves the issue.

dscho commented on Jan 11, 2018

@dscho
Member

The kill utility should not be involved at all, so that line 179 should not be hit at all...

afsmith92 commented on Jan 11, 2018

@afsmith92

Where is exit_process called when responding to ctrl+c? If it's passing the exit code in the same way e.g. sig + 128 then the exit_code == SIGINT check will fail.

dscho commented on Jan 12, 2018

@dscho
Member

It's called via signal() in exceptions.cc. AFAICT you call exit_process() correctly there...

dscho commented on Jan 12, 2018

@dscho
Member

Oh no. I was mistaken!

exit_process (ch_spawn, 128 + (sigExeced = si.si_signo));

dscho commented on Jan 12, 2018

@dscho
Member

Maybe we should pass the real code instead, and let exit_process() decide whether to add 128 or not?

dscho commented on Jan 12, 2018

@dscho
Member

I made up my mind and think that testing exit_code & 0x7f might be better. My proposed fix: dscho@fix-ctrl-c-again

What do you think?

dscho commented on Jan 12, 2018

@dscho
Member

I tested this with Node v6.10.2 as shipped in MSYS2 and with this script:

#!/usr/bin/env node

process.on('SIGTERM', function() {
  console.log( "\nresponding to SIGTERM" );
  // other cleanup code
  process.exit(1);
});

process.on('SIGINT', function() {
  console.log( "\nresponding to SIGINT" );
  // other cleanup code
  process.exit(1);
});

var http = require('http');

http.createServer(function (request, response) {
    response.writeHead(200, {
        'Content-Type': 'text/plain',
        'Access-Control-Allow-Origin' : '*'
    });
    response.end('Hello World\n');
}).listen(1337);

This is the "screenshot":

$ node handle-ctrl-c.js

responding to SIGINT

So I call this success and will push my fix ;-)

dscho commented on Jan 12, 2018

@dscho
Member

Actually... After testing a bit further, it looks like the new code path is not hit in that case, and I cannot get it to work with kill.exe. Will keep digging, even if I do not really have any time to spend on this ;-)

afsmith92 commented on Jan 12, 2018

@afsmith92

@dscho I'm continuing to investigate as well. Would it be best to revert the patch at this point?

Maybe we should pass the real code instead, and let exit_process() decide whether to add 128 or not?

That sounds reasonable to me.

afsmith92 commented on Jan 12, 2018

@afsmith92

Also, I found a different implementation of the safe ExitProcess in this thread -- I've been messing with this.

dscho commented on Jan 12, 2018

@dscho
Member

After chasing down this rabbit hole, I finally have something that really seems to work: dscho@fix-ctrl-c-again

@afsmith92 would you mind testing this? It also requires kill.exe to be rebuilt (cd ../utils && make kill.exe after you built msys0.dll), as it has to spawn kill.exe process so that that one can attach to the Console of the target process and send the Ctrl event.

afsmith92 commented on Jan 12, 2018

@afsmith92

Absolutely. I'll have it tested within the next half hour.

afsmith92 commented on Jan 13, 2018

@afsmith92

Still compiling..

afsmith92 commented on Jan 13, 2018

@afsmith92

Works perfectly. Tested in a PortableGit with node v6.11.2. Killed all child processes and was able to handle SIGINT:

process.on('SIGINT', function() {
  console.log( "\nresponding to SIGINT" );
  // other cleanup code
  process.exit(1);
});

logged responding to SIGINT to the console after ctrl +c

Thank you so much for taking the time to figure this out, and I'm sorry for getting you dragged into this issue. I was only following the issue on git-for-windows -- not the issue logged to node -- and I wasn't aware of the issues with SIGINT and SIGTERM handling (until you tagged me in the node issue) and thus only tested for the child processes issue described in the git-for-windows issue.

I'll keep an eye on the issues list for this repo, and I'll jump in if I see anything I think I can help with in the future.

dscho commented on Jan 13, 2018

@dscho
Member

No worries! You helped. That means a lot to me. And thanks for verifying my latest attempt at a fix!

Please sign in to comment.