Try to kill the process tree gracefully.

Before calling TerminateProcess() on all processes assocatiated with
the monitored application, enumerate all windows and threads and
post appropriate messages to them.  If the application bothers to
listen for such messages it has a chance to shut itself down gracefully.

If the application ignores this chance to do the right thing it's
fair game for termination.
master
Iain Patterson 14 years ago
parent 5d8129f70e
commit fdf66ebb17

@ -181,7 +181,7 @@ kill_process_tree %1 %2 %3
.
MessageId = +1
SymbolicName = NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_FAILED
SymbolicName = NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED
Severity = Error
Language = English
Failed to create snapshot of running processes when terminating service %1:
@ -243,3 +243,19 @@ Registry key %1 is unset for service %2.
Additionally, ExpandEnvironmentStrings("%%SYSTEMROOT%%") failed when trying to choose a fallback startup directory.
.
MessageId = +1
SymbolicName = NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED
Severity = Error
Language = English
Failed to create snapshot of running threads when terminating service %1:
%2
.
MessageId = +1
SymbolicName = NSSM_EVENT_THREAD_ENUMERATE_FAILED
Severity = Error
Language = English
Failed to enumerate running threads when terminating service %1:
%2
.

@ -32,4 +32,15 @@ int str_equiv(const char *, const char *);
#define VALUE_LENGTH 16383
#define SERVICE_NAME_LENGTH KEY_LENGTH - 55
/*
How many milliseconds to wait for the application to die after posting to
its windows' message queues.
*/
#define NSSM_KILL_WINDOW_GRACE_PERIOD 1500
/*
How many milliseconds to wait for the application to die after posting to
its threads' message queues.
*/
#define NSSM_KILL_THREADS_GRACE_PERIOD 1500
#endif

@ -1,14 +1,113 @@
#include "nssm.h"
/* Send some window messages and hope the window respects one or more. */
int CALLBACK kill_window(HWND window, LPARAM arg) {
kill_t *k = (kill_t *) arg;
unsigned long pid;
if (! GetWindowThreadProcessId(window, &pid)) return 1;
if (pid != k->pid) return 1;
/* First try sending WM_CLOSE to request that the window close. */
k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);
/*
Then tell the window that the user is logging off and it should exit
without worrying about saving any data.
*/
k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);
return 1;
}
/*
Try to post a message to the message queues of threads associated with the
given process ID. Not all threads have message queues so there's no
guarantee of success, and we don't want to be left waiting for unsignalled
processes so this function returns only true if at least one thread was
successfully prodded.
*/
int kill_threads(char *service_name, kill_t *k) {
int ret = 0;
/* Get a snapshot of all threads in the system. */
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (! snapshot) {
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, service_name, GetLastError(), 0);
return 0;
}
THREADENTRY32 te;
ZeroMemory(&te, sizeof(te));
te.dwSize = sizeof(te);
if (! Thread32First(snapshot, &te)) {
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, service_name, GetLastError(), 0);
return 0;
}
/* This thread belongs to the doomed process so signal it. */
if (te.th32OwnerProcessID == k->pid) {
ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
}
while (true) {
/* Try to get the next thread. */
if (! Thread32Next(snapshot, &te)) {
unsigned long error = GetLastError();
if (error == ERROR_NO_MORE_FILES) break;
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, service_name, GetLastError(), 0);
return ret;
}
if (te.th32OwnerProcessID == k->pid) {
ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
}
}
return ret;
}
/* Give the process a chance to die gracefully. */
int kill_process(char *service_name, HANDLE process_handle, unsigned long pid, unsigned long exitcode) {
/* Shouldn't happen. */
if (! pid) return 1;
kill_t k = { pid, exitcode, 0 };
/*
Try to post messages to the windows belonging to the given process ID.
If the process is a console application it won't have any windows so there's
no guarantee of success.
*/
EnumWindows((WNDENUMPROC) kill_window, (LPARAM) &k);
if (k.signalled) {
if (! WaitForSingleObject(process_handle, NSSM_KILL_WINDOW_GRACE_PERIOD)) return 1;
}
/*
Try to post messages to any thread message queues associated with the
process. Console applications might have them (but probably won't) so
there's still no guarantee of success.
*/
if (kill_threads(service_name, &k)) {
if (! WaitForSingleObject(process_handle, NSSM_KILL_THREADS_GRACE_PERIOD)) return 1;
}
/* We tried being nice. Time for extreme prejudice. */
return TerminateProcess(process_handle, exitcode);
}
void kill_process_tree(char *service_name, unsigned long pid, unsigned long exitcode, unsigned long ppid) {
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, service_name, pid, exitcode, 0);
/* Shouldn't happen. */
if (! pid) return;
/* Get a snapshot of all processes in the system. */
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (! snapshot) {
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_FAILED, service_name, GetLastError(), 0);
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, service_name, GetLastError(), 0);
return;
}
@ -21,6 +120,7 @@ void kill_process_tree(char *service_name, unsigned long pid, unsigned long exit
return;
}
/* This is a child of the doomed process so kill it. */
if (pe.th32ParentProcessID == pid) kill_process_tree(service_name, pe.th32ProcessID, exitcode, ppid);
while (true) {
@ -35,6 +135,7 @@ void kill_process_tree(char *service_name, unsigned long pid, unsigned long exit
if (pe.th32ParentProcessID == pid) kill_process_tree(service_name, pe.th32ProcessID, exitcode, ppid);
}
/* We will need a process handle in order to call TerminateProcess() later. */
HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
if (! process_handle) {
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid, service_name, GetLastError(), 0);
@ -42,7 +143,7 @@ void kill_process_tree(char *service_name, unsigned long pid, unsigned long exit
}
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid, ppid, service_name, 0);
if (! TerminateProcess(process_handle, exitcode)) {
if (! kill_process(service_name, process_handle, pid, exitcode)) {
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid, service_name, GetLastError(), 0);
return;
}

@ -3,6 +3,15 @@
#include <tlhelp32.h>
typedef struct {
unsigned long pid;
unsigned long exitcode;
int signalled;
} kill_t;
int CALLBACK kill_window(HWND, LPARAM);
int kill_threads(char *, kill_t *);
int kill_process(char *, HANDLE, unsigned long, unsigned long);
void kill_process_tree(char *, unsigned long, unsigned long, unsigned long);
#endif

@ -288,7 +288,7 @@ int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
if (pid) {
/* Shut down server */
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);
TerminateProcess(process_handle, 0);
kill_process(service_name, process_handle, pid, 0);
process_handle = 0;
}
else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);

Loading…
Cancel
Save