Allow overriding time to wait when trying to kill the application.
authorIain Patterson <me@iain.cx>
Wed, 13 Nov 2013 15:35:20 +0000 (15:35 +0000)
committerIain Patterson <me@iain.cx>
Fri, 15 Nov 2013 16:02:58 +0000 (16:02 +0000)
Three new registry entries can be used to specify a wait time in
milliseconds after attempting a stop method.

  AppStopMethodConsole
  AppStopMethodWindow
  AppStopMethodThreads

The default for each remains the same, 1500ms.

Thanks Russ Holmann.

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

index 1513a54..d4850a3 100644 (file)
@@ -1,3 +1,8 @@
+Changes since 2.17
+-----------------
+  * Timeouts for each shutdown method can be configured in
+    the registry.
+
 Changes since 2.16
 -----------------
   * NSSM can now redirect the service's I/O streams to any path
index 2fbe5c8..f2fe712 100644 (file)
@@ -43,6 +43,9 @@ they can clean up and shut down gracefully on receipt of the event.
 Since version 2.17, NSSM can redirect the managed application's I/O streams\r
 to an arbitrary path.\r
 \r
+Since version 2.18, NSSM can be configured to wait a user-specified amount\r
+of time for the application to exit when shutting down.\r
+\r
 \r
 Usage\r
 -----\r
@@ -182,6 +185,20 @@ Take great care when including 8 in the value of AppStopMethodSkip.  If NSSM
 does not call TerminateProcess() it is possible that the application will not\r
 exit when the service stops.\r
 \r
+By default NSSM will allow processes 1500ms to respond to each of the methods\r
+described above before proceeding to the next one.  The timeout can be\r
+configured on a per-method basis by creating REG_DWORD entries in the\r
+registry under HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters.\r
+\r
+  AppStopMethodConsole\r
+  AppStopMethodWindow\r
+  AppStopMethodThreads\r
+\r
+Each value should be set to the number of milliseconds to wait.  Please note\r
+that the timeout applies to each process in the application's process tree,\r
+so the actual time to shutdown may be longer than the sum of all configured\r
+timeouts if the application spawns multiple subprocesses.\r
+\r
 \r
 I/O redirection\r
 ---------------\r
@@ -282,6 +299,7 @@ Thanks to Riccardo Gusmeroli for Italian translation.
 Thanks to Eric Cheldelin for the inspiration to generate a Control-C event\r
 on shutdown.\r
 Thanks to Brian Baxter for suggesting how to escape quotes from the command prompt.\r
+Thanks to Russ Holmann for suggesting that the shutdown timeout be configurable.\r
 \r
 Licence\r
 -------\r
index 6f6ae40..fdf295b 100644 (file)
@@ -1305,3 +1305,42 @@ Language = Italian
 Chiamata a GetProcAddress(%1) fallita:
 %2
 .
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_BOGUS_KILL_CONSOLE_GRACE_PERIOD
+Severity = Warning
+Language = English
+The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after sending a Control-C event, was not of type REG_DWORD.  The default time of %3 milliseconds will be used.
+.
+Language = French
+The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after sending a Control-C event, was not of type REG_DWORD.  The default time of %3 milliseconds will be used.
+.
+Language = Italian
+The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after sending a Control-C event, was not of type REG_DWORD.  The default time of %3 milliseconds will be used.
+.
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_BOGUS_KILL_WINDOW_GRACE_PERIOD
+Severity = Warning
+Language = English
+The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after posting a WM_CLOSE message to windows managed by the application, was not of type REG_DWORD.  The default time of %3 milliseconds will be used.
+.
+Language = French
+The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after posting a WM_CLOSE message to windows managed by the application, was not of type REG_DWORD.  The default time of %3 milliseconds will be used.
+.
+Language = Italian
+The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after posting a WM_CLOSE message to windows managed by the application, was not of type REG_DWORD.  The default time of %3 milliseconds will be used.
+.
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_BOGUS_KILL_THREADS_GRACE_PERIOD
+Severity = Warning
+Language = English
+The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after posting a WM_QUIT message to the message queues of threads managed by the application, was not of type REG_DWORD.  The default time of %3 milliseconds will be used.
+.
+Language = French
+The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after posting a WM_QUIT message to the message queues of threads managed by the application, was not of type REG_DWORD.  The default time of %3 milliseconds will be used.
+.
+Language = Italian
+The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after posting a WM_QUIT message to the message queues of threads managed by the application, was not of type REG_DWORD.  The default time of %3 milliseconds will be used.
+.
diff --git a/nssm.h b/nssm.h
index b5d457e..01228bd 100644 (file)
--- a/nssm.h
+++ b/nssm.h
@@ -43,17 +43,17 @@ int str_equiv(const char *, const char *);
 \r
 /*\r
   How many milliseconds to wait for the application to die after sending\r
-  a Control-C event to its console.\r
+  a Control-C event to its console.  Override in registry.\r
 */\r
 #define NSSM_KILL_CONSOLE_GRACE_PERIOD 1500\r
 /*\r
   How many milliseconds to wait for the application to die after posting to\r
-  its windows' message queues.\r
+  its windows' message queues.  Override in registry.\r
 */\r
 #define NSSM_KILL_WINDOW_GRACE_PERIOD 1500\r
 /*\r
   How many milliseconds to wait for the application to die after posting to\r
-  its threads' message queues.\r
+  its threads' message queues.  Override in registry.\r
 */\r
 #define NSSM_KILL_THREADS_GRACE_PERIOD 1500\r
 \r
index fc0056d..f060d72 100644 (file)
@@ -1,6 +1,9 @@
 #include "nssm.h"
 
 extern imports_t imports;
+extern unsigned long kill_console_delay;
+extern unsigned long kill_window_delay;
+extern unsigned long kill_threads_delay;
 
 int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {
   FILETIME creation_time, exit_time, kernel_time, user_time;
@@ -161,7 +164,7 @@ int kill_process(char *service_name, unsigned long stop_method, HANDLE process_h
   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;
+      if (! WaitForSingleObject(process_handle, kill_window_delay)) return 1;
     }
   }
 
@@ -172,7 +175,7 @@ int kill_process(char *service_name, unsigned long stop_method, HANDLE process_h
   */
   if (stop_method & NSSM_STOP_METHOD_THREADS) {
     if (kill_threads(service_name, &k)) {
-      if (! WaitForSingleObject(process_handle, NSSM_KILL_THREADS_GRACE_PERIOD)) return 1;
+      if (! WaitForSingleObject(process_handle, kill_threads_delay)) return 1;
     }
   }
 
@@ -233,7 +236,7 @@ int kill_console(char *service_name, HANDLE process_handle, unsigned long pid) {
   }
 
   /* Wait for process to exit. */
-  if (WaitForSingleObject(process_handle, NSSM_KILL_CONSOLE_GRACE_PERIOD)) return 6;
+  if (WaitForSingleObject(process_handle, kill_console_delay)) return 6;
 
   return ret;
 }
index 7a7b784..7eb6112 100644 (file)
@@ -239,7 +239,7 @@ void override_milliseconds(char *service_name, HKEY key, char *value, unsigned l
   if (! ok) *buffer = default_value;\r
 }\r
 \r
-int get_parameters(char *service_name, char *exe, unsigned long exelen, char *flags, unsigned long flagslen, char *dir, unsigned long dirlen, char **env, unsigned long *throttle_delay, unsigned long *stop_method, STARTUPINFO *si) {\r
+int get_parameters(char *service_name, char *exe, unsigned long exelen, char *flags, unsigned long flagslen, char *dir, unsigned long dirlen, char **env, unsigned long *throttle_delay, unsigned long *stop_method, unsigned long *kill_console_delay, unsigned long *kill_window_delay, unsigned long *kill_threads_delay, STARTUPINFO *si) {\r
   unsigned long ret;\r
 \r
   /* Get registry */\r
@@ -322,6 +322,11 @@ int get_parameters(char *service_name, char *exe, unsigned long exelen, char *fl
   *stop_method = ~0;\r
   if (stop_ok) *stop_method &= ~stop_method_skip;\r
 \r
+  /* Try to get kill delays - may fail. */\r
+  override_milliseconds(service_name, key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, kill_console_delay, NSSM_KILL_CONSOLE_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_CONSOLE_GRACE_PERIOD);\r
+  override_milliseconds(service_name, key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, kill_window_delay, NSSM_KILL_WINDOW_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_WINDOW_GRACE_PERIOD);\r
+  override_milliseconds(service_name, key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, kill_threads_delay, NSSM_KILL_THREADS_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_THREADS_GRACE_PERIOD);\r
+\r
   /* Close registry */\r
   RegCloseKey(key);\r
 \r
index 9129b64..17f26d7 100644 (file)
@@ -9,6 +9,9 @@
 #define NSSM_REG_EXIT "AppExit"\r
 #define NSSM_REG_THROTTLE "AppThrottle"\r
 #define NSSM_REG_STOP_METHOD_SKIP "AppStopMethodSkip"\r
+#define NSSM_REG_KILL_CONSOLE_GRACE_PERIOD "AppStopMethodConsole"\r
+#define NSSM_REG_KILL_WINDOW_GRACE_PERIOD "AppStopMethodWindow"\r
+#define NSSM_REG_KILL_THREADS_GRACE_PERIOD "AppStopMethodThreads"\r
 #define NSSM_REG_STDIN "AppStdin"\r
 #define NSSM_REG_STDOUT "AppStdout"\r
 #define NSSM_REG_STDERR "AppStderr"\r
@@ -26,7 +29,7 @@ int expand_parameter(HKEY, char *, char *, unsigned long, bool);
 int get_number(HKEY, char *, unsigned long *, bool);\r
 int get_number(HKEY, char *, unsigned long *);\r
 void override_milliseconds(char *, HKEY, char *, unsigned long *, unsigned long, unsigned long);\r
-int get_parameters(char *, char *, unsigned long, char *, unsigned long, char *, unsigned long, char **, unsigned long *, unsigned long *, STARTUPINFO *);\r
+int get_parameters(char *, char *, unsigned long, char *, unsigned long, char *, unsigned long, char **, unsigned long *, unsigned long *, unsigned long *, unsigned long *, unsigned long *, STARTUPINFO *);\r
 int get_exit_action(char *, unsigned long *, unsigned char *, bool *);\r
 \r
 #endif\r
index 596aa7e..f14390f 100644 (file)
@@ -14,6 +14,9 @@ bool stopping;
 bool allow_restart;\r
 unsigned long throttle_delay;\r
 unsigned long stop_method;\r
+unsigned long kill_console_delay;\r
+unsigned long kill_window_delay;\r
+unsigned long kill_threads_delay;\r
 CRITICAL_SECTION throttle_section;\r
 CONDITION_VARIABLE throttle_condition;\r
 HANDLE throttle_timer;\r
@@ -390,7 +393,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, &stop_method, &si);\r
+  int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &stop_method, &kill_console_delay, &kill_window_delay, &kill_threads_delay, &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
@@ -446,9 +449,9 @@ int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
   if (graceful) {\r
     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
     service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
-    if (stop_method & NSSM_STOP_METHOD_CONSOLE && imports.AttachConsole) service_status.dwWaitHint += NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
-    if (stop_method & NSSM_STOP_METHOD_WINDOW) service_status.dwWaitHint += NSSM_KILL_WINDOW_GRACE_PERIOD;\r
-    if (stop_method & NSSM_STOP_METHOD_THREADS) service_status.dwWaitHint += NSSM_KILL_THREADS_GRACE_PERIOD;\r
+    if (stop_method & NSSM_STOP_METHOD_CONSOLE && imports.AttachConsole) service_status.dwWaitHint += kill_console_delay;\r
+    if (stop_method & NSSM_STOP_METHOD_WINDOW) service_status.dwWaitHint += kill_window_delay;\r
+    if (stop_method & NSSM_STOP_METHOD_THREADS) service_status.dwWaitHint += kill_threads_delay;\r
     SetServiceStatus(service_handle, &service_status);\r
   }\r
 \r