If only the default action is set to Suicide NSSM will instead exit gracefully.\r
\r
\r
+Stopping the service\r
+--------------------\r
+When stopping a service NSSM will attempt several different methods of killing\r
+the monitored application, each of which can be disabled if necessary.\r
+\r
+First NSSM will attempt to generate a Control-C event and send it to the\r
+application's console. Batch scripts or console applications may intercept\r
+the event and shut themselves down gracefully. GUI applications do not have\r
+consoles and will not respond to this method.\r
+\r
+Secondly NSSM will enumerate all windows created by the application and send\r
+them a WM_CLOSE message, requesting a graceful exit.\r
+\r
+Thirdly NSSM will enumerate all threads created by the application and send\r
+them a WM_QUIT message, requesting a graceful exit. Not all applications'\r
+threads have message queues; those which do not will not respond to this\r
+method.\r
+\r
+Finally NSSM will call TerminateProcess() to request that the operating\r
+system forcibly terminate the application. TerminateProcess() cannot be\r
+trapped or ignored, so in most circumstances the application will be killed.\r
+However, there is no guarantee that it will have a chance to perform any\r
+tidyup operations before it exits.\r
+\r
+Any or all of the methods above may be disabled. NSSM will look for the\r
+HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppStopMethodSkip\r
+registry value which should be of type REG_DWORD set to a bit field describing\r
+which methods should not be applied.\r
+\r
+ If AppStopMethodSkip includes 1, Control-C events will not be generated.\r
+ If AppStopMethodSkip includes 2, WM_CLOSE messages will not be posted.\r
+ If AppStopMethodSkip includes 4, WM_QUIT messages will not be posted.\r
+ If AppStopMethodSkip includes 8, TerminateProcess() will not be called.\r
+\r
+If, for example, you knew that an application did not respond to Control-C\r
+events and did not have a thread message queue, you could set AppStopMethodSkip\r
+to 5 and NSSM would not attempt to use those methods to stop the application.\r
+\r
+Take great care when including 8 in the value of AppStopMethodSkip. If NSSM\r
+does not call TerminateProcess() it is possible that the application will not\r
+exit when the service stops.\r
+\r
+\r
I/O redirection\r
---------------\r
NSSM can redirect the managed application's I/O to any path capable of being\r
Language = Italian
Error setting up one or more I/O filehandles. Service %1 will not be started.
.
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_BOGUS_STOP_METHOD_SKIP
+Severity = Warning
+Language = English
+The registry value %2, used to specify the method(s) by which %3 will skip when attempting to stop service %1, was not of type REG_DWORD. All available methods will be used.
+.
+Language = French
+The registry value %2, used to specify the method(s) by which %3 will skip when attempting to stop service %1, was not of type REG_DWORD. All available methods will be used.
+.
+Language = Italian
+The registry value %2, used to specify the method(s) by which %3 will skip when attempting to stop service %1, was not of type REG_DWORD. All available methods will be used.
+.
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_PROCESS_STILL_ACTIVE
+Severity = Warning
+Language = English
+The service %1 is stopping but PID %2 is still running.
+Usually %3 will call TerminateProcess() as a last resort to ensure that the process is stopped but the registry value %4 has been set and not all process termination methods have been attempted.
+It will no longer be possible to attempt to control the application and the service will report a stopped status.
+.
+Language = French
+The service %1 is stopping but PID %2 is still running.
+Usually %3 will call TerminateProcess() as a last resort to ensure that the process is stopped but the registry value %4 has been set and not all process termination methods have been attempted.
+It will no longer be possible to attempt to control the application and the service will report a stopped status.
+.
+Language = Italian
+The service %1 is stopping but PID %2 is still running.
+Usually %3 will call TerminateProcess() as a last resort to ensure that the process is stopped but the registry value %4 has been set and not all process termination methods have been attempted.
+It will no longer be possible to attempt to control the application and the service will report a stopped status.
+.
/* Margin of error for service status wait hints in milliseconds. */\r
#define NSSM_WAITHINT_MARGIN 2000\r
\r
+/* Methods used to try to stop the application. */\r
+#define NSSM_STOP_METHOD_CONSOLE (1 << 0)\r
+#define NSSM_STOP_METHOD_WINDOW (1 << 1)\r
+#define NSSM_STOP_METHOD_THREADS (1 << 2)\r
+#define NSSM_STOP_METHOD_TERMINATE (1 << 3)\r
+\r
#endif\r
}
/* Give the process a chance to die gracefully. */
-int kill_process(char *service_name, HANDLE process_handle, unsigned long pid, unsigned long exitcode) {
+int kill_process(char *service_name, unsigned long stop_method, HANDLE process_handle, unsigned long pid, unsigned long exitcode) {
/* Shouldn't happen. */
if (! pid) return 1;
if (! process_handle) return 1;
kill_t k = { pid, exitcode, 0 };
/* Try to send a Control-C event to the console. */
- if (! kill_console(service_name, process_handle, pid)) return 1;
+ if (stop_method & NSSM_STOP_METHOD_CONSOLE) {
+ if (! kill_console(service_name, process_handle, pid)) return 1;
+ }
/*
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;
+ if (stop_method & NSSM_STOP_METHOD_WINDOW) {
+ EnumWindows((WNDENUMPROC) kill_window, (LPARAM) &k);
+ if (k.signalled) {
+ if (! WaitForSingleObject(process_handle, NSSM_KILL_WINDOW_GRACE_PERIOD)) return 1;
+ }
}
/*
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;
+ if (stop_method & NSSM_STOP_METHOD_THREADS) {
+ 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);
+ if (stop_method & NSSM_STOP_METHOD_TERMINATE) {
+ return TerminateProcess(process_handle, exitcode);
+ }
+
+ return 0;
}
/* Simulate a Control-C event to our console (shared with the app). */
return ret;
}
-void kill_process_tree(char *service_name, unsigned long pid, unsigned long exitcode, unsigned long ppid, FILETIME *parent_creation_time, FILETIME *parent_exit_time) {
+void kill_process_tree(char *service_name, unsigned long stop_method, unsigned long pid, unsigned long exitcode, unsigned long ppid, FILETIME *parent_creation_time, FILETIME *parent_exit_time) {
/* Shouldn't happen unless the service failed to start. */
if (! pid) return;
}
/* This is a child of the doomed process so kill it. */
- if (! check_parent(service_name, &pe, pid, parent_creation_time, parent_exit_time)) kill_process_tree(service_name, pe.th32ProcessID, exitcode, ppid, parent_creation_time, parent_exit_time);
+ if (! check_parent(service_name, &pe, pid, parent_creation_time, parent_exit_time)) kill_process_tree(service_name, stop_method, pe.th32ProcessID, exitcode, ppid, parent_creation_time, parent_exit_time);
while (true) {
/* Try to get the next process. */
return;
}
- if (! check_parent(service_name, &pe, pid, parent_creation_time, parent_exit_time)) kill_process_tree(service_name, pe.th32ProcessID, exitcode, ppid, parent_creation_time, parent_exit_time);
+ if (! check_parent(service_name, &pe, pid, parent_creation_time, parent_exit_time)) kill_process_tree(service_name, stop_method, pe.th32ProcessID, exitcode, ppid, parent_creation_time, parent_exit_time);
}
CloseHandle(snapshot);
char ppid_string[16];
_snprintf(ppid_string, sizeof(ppid_string), "%d", ppid);
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, service_name, 0);
- if (! kill_process(service_name, process_handle, pid, exitcode)) {
+ if (! kill_process(service_name, stop_method, process_handle, pid, exitcode)) {
/* Maybe it already died. */
unsigned long ret;
- if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, service_name, error_string(GetLastError()), 0);
+ if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {
+ if (stop_method & NSSM_STOP_METHOD_TERMINATE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, service_name, error_string(GetLastError()), 0);
+ else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, service_name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);
+ }
}
CloseHandle(process_handle);
int check_parent(char *, PROCESSENTRY32 *, unsigned long, FILETIME *, FILETIME *);
int CALLBACK kill_window(HWND, LPARAM);
int kill_threads(char *, kill_t *);
-int kill_process(char *, HANDLE, unsigned long, unsigned long);
int kill_console(char *, HANDLE, unsigned long);
-void kill_process_tree(char *, unsigned long, unsigned long, unsigned long, FILETIME *, FILETIME *);
+int kill_process(char *, unsigned long, HANDLE, unsigned long, unsigned long);
+void kill_process_tree(char *, unsigned long, unsigned long, unsigned long, unsigned long, FILETIME *, FILETIME *);
#endif
return get_number(key, value, number, true);\r
}\r
\r
-int get_parameters(char *service_name, char *exe, int exelen, char *flags, int flagslen, char *dir, int dirlen, char **env, unsigned long *throttle_delay, STARTUPINFO *si) {\r
+int get_parameters(char *service_name, char *exe, int exelen, char *flags, int flagslen, char *dir, int dirlen, char **env, unsigned long *throttle_delay, unsigned long *stop_method, STARTUPINFO *si) {\r
unsigned long ret;\r
\r
/* Get registry */\r
\r
if (! throttle_ok) *throttle_delay = NSSM_RESET_THROTTLE_RESTART;\r
\r
+ /* Try to get service stop flags. */\r
+ type = REG_DWORD;\r
+ unsigned long stop_method_skip;\r
+ buflen = sizeof(stop_method_skip);\r
+ bool stop_ok = false;\r
+ ret = RegQueryValueEx(key, NSSM_REG_STOP_METHOD_SKIP, 0, &type, (unsigned char *) &stop_method_skip, &buflen);\r
+ if (ret != ERROR_SUCCESS) {\r
+ if (ret != ERROR_FILE_NOT_FOUND) {\r
+ if (type != REG_DWORD) {\r
+ log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_STOP_METHOD_SKIP, service_name, NSSM_REG_STOP_METHOD_SKIP, NSSM, 0);\r
+ }\r
+ else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, NSSM_REG_STOP_METHOD_SKIP, error_string(GetLastError()), 0);\r
+ }\r
+ }\r
+ else stop_ok = true;\r
+\r
+ /* Try all methods except those requested to be skipped. */\r
+ *stop_method = ~0;\r
+ if (stop_ok) *stop_method &= ~stop_method_skip;\r
+\r
/* Close registry */\r
RegCloseKey(key);\r
\r
#define NSSM_REG_ENV "AppEnvironment"\r
#define NSSM_REG_EXIT "AppExit"\r
#define NSSM_REG_THROTTLE "AppThrottle"\r
+#define NSSM_REG_STOP_METHOD_SKIP "AppStopMethodSkip"\r
#define NSSM_REG_STDIN "AppStdin"\r
#define NSSM_REG_STDOUT "AppStdout"\r
#define NSSM_REG_STDERR "AppStderr"\r
int expand_parameter(HKEY, char *, char *, unsigned long, bool);\r
int get_number(HKEY, char *, unsigned long *, bool);\r
int get_number(HKEY, char *, unsigned long *);\r
-int get_parameters(char *, char *, int, char *, int, char *, int, char **, unsigned long *, STARTUPINFO *);\r
+int get_parameters(char *, char *, int, char *, int, char *, int, char **, unsigned long *, unsigned long *, STARTUPINFO *);\r
int get_exit_action(char *, unsigned long *, unsigned char *, bool *);\r
\r
#endif\r
char dir[MAX_PATH];\r
bool stopping;\r
unsigned long throttle_delay;\r
+unsigned long stop_method;\r
HANDLE throttle_timer;\r
LARGE_INTEGER throttle_duetime;\r
FILETIME creation_time;\r
\r
/* Get startup parameters */\r
char *env = 0;\r
- int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &si);\r
+ int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &stop_method, &si);\r
if (ret) {\r
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);\r
return stop_service(2, true, true);\r
if (pid) {\r
/* Shut down service */\r
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
- kill_process(service_name, process_handle, pid, 0);\r
+ kill_process(service_name, stop_method, process_handle, pid, 0);\r
}\r
else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
\r
}\r
\r
/* Clean up. */\r
- kill_process_tree(service_name, pid, exitcode, pid, &creation_time, &exit_time);\r
+ kill_process_tree(service_name, stop_method, pid, exitcode, pid, &creation_time, &exit_time);\r
\r
/*\r
The why argument is true if our wait timed out or false otherwise.\r