Skip some or all methods of stopping the application.
authorIain Patterson <me@iain.cx>
Thu, 31 Oct 2013 13:14:31 +0000 (13:14 +0000)
committerIain Patterson <me@iain.cx>
Thu, 31 Oct 2013 13:14:31 +0000 (13:14 +0000)
Use the AppStopMethodSkip bit field to specify any method(s) which
should not be employed when stopping the application.

README.txt
messages.mc
nssm.h
process.cpp
process.h
registry.cpp
registry.h
service.cpp

index 904468a..3559245 100644 (file)
@@ -140,6 +140,49 @@ request to suicide if you explicitly configure a registry key for exit code 0.
 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
index 21eb640..0aea1ae 100644 (file)
@@ -1238,3 +1238,35 @@ Error setting up one or more I/O filehandles.  Service %1 will not be started.
 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.
+.
diff --git a/nssm.h b/nssm.h
index 0e918c3..afff8c4 100644 (file)
--- a/nssm.h
+++ b/nssm.h
@@ -59,4 +59,10 @@ int str_equiv(const char *, const char *);
 /* 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
index 67bc978..e654811 100644 (file)
@@ -134,7 +134,7 @@ int kill_threads(char *service_name, kill_t *k) {
 }
 
 /* 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;
@@ -147,16 +147,20 @@ int kill_process(char *service_name, HANDLE process_handle, unsigned long pid, u
   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;
+    }
   }
 
   /*
@@ -164,12 +168,18 @@ int kill_process(char *service_name, HANDLE process_handle, unsigned long pid, u
     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). */
@@ -223,7 +233,7 @@ int kill_console(char *service_name, HANDLE process_handle, unsigned long pid) {
   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;
 
@@ -250,7 +260,7 @@ void kill_process_tree(char *service_name, unsigned long pid, unsigned long exit
   }
 
   /* 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. */
@@ -262,7 +272,7 @@ void kill_process_tree(char *service_name, unsigned long pid, unsigned long exit
       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);
@@ -277,10 +287,13 @@ void kill_process_tree(char *service_name, unsigned long pid, unsigned long exit
   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);
index 2560621..4a1547b 100644 (file)
--- a/process.h
+++ b/process.h
@@ -14,8 +14,8 @@ int get_process_exit_time(HANDLE, FILETIME *);
 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
index eacde54..aa16ac7 100644 (file)
@@ -219,7 +219,7 @@ int get_number(HKEY key, char *value, unsigned long *number) {
   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
@@ -298,6 +298,26 @@ int get_parameters(char *service_name, char *exe, int exelen, char *flags, int f
 \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
index 811ac75..2f45d81 100644 (file)
@@ -8,6 +8,7 @@
 #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
@@ -24,7 +25,7 @@ int expand_parameter(HKEY, char *, char *, unsigned long, bool, bool);
 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
index 21726df..873b8a8 100644 (file)
@@ -12,6 +12,7 @@ char flags[CMD_LENGTH];
 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
@@ -372,7 +373,7 @@ int start_service() {
 \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
@@ -431,7 +432,7 @@ int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
   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
@@ -480,7 +481,7 @@ void CALLBACK end_service(void *arg, unsigned char why) {
   }\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