Browse Source

Allow overriding time to wait when trying to kill the application.

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.
Iain Patterson 9 years ago
parent
commit
cb571db509
8 changed files with 88 additions and 12 deletions
  1. 5 0
      ChangeLog.txt
  2. 18 0
      README.txt
  3. 39 0
      messages.mc
  4. 3 3
      nssm.h
  5. 6 3
      process.cpp
  6. 6 1
      registry.cpp
  7. 4 1
      registry.h
  8. 7 4
      service.cpp

+ 5 - 0
ChangeLog.txt

@@ -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

+ 18 - 0
README.txt

@@ -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
 to an arbitrary path.
 
+Since version 2.18, NSSM can be configured to wait a user-specified amount
+of time for the application to exit when shutting down.
+
 
 Usage
 -----
@@ -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
 exit when the service stops.
 
+By default NSSM will allow processes 1500ms to respond to each of the methods
+described above before proceeding to the next one.  The timeout can be
+configured on a per-method basis by creating REG_DWORD entries in the
+registry under HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters.
+
+  AppStopMethodConsole
+  AppStopMethodWindow
+  AppStopMethodThreads
+
+Each value should be set to the number of milliseconds to wait.  Please note
+that the timeout applies to each process in the application's process tree,
+so the actual time to shutdown may be longer than the sum of all configured
+timeouts if the application spawns multiple subprocesses.
+
 
 I/O redirection
 ---------------
@@ -282,6 +299,7 @@ Thanks to Riccardo Gusmeroli for Italian translation.
 Thanks to Eric Cheldelin for the inspiration to generate a Control-C event
 on shutdown.
 Thanks to Brian Baxter for suggesting how to escape quotes from the command prompt.
+Thanks to Russ Holmann for suggesting that the shutdown timeout be configurable.
 
 Licence
 -------

+ 39 - 0
messages.mc

@@ -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.
+.

+ 3 - 3
nssm.h

@@ -43,17 +43,17 @@ int str_equiv(const char *, const char *);
 
 /*
   How many milliseconds to wait for the application to die after sending
-  a Control-C event to its console.
+  a Control-C event to its console.  Override in registry.
 */
 #define NSSM_KILL_CONSOLE_GRACE_PERIOD 1500
 /*
   How many milliseconds to wait for the application to die after posting to
-  its windows' message queues.
+  its windows' message queues.  Override in registry.
 */
 #define NSSM_KILL_WINDOW_GRACE_PERIOD 1500
 /*
   How many milliseconds to wait for the application to die after posting to
-  its threads' message queues.
+  its threads' message queues.  Override in registry.
 */
 #define NSSM_KILL_THREADS_GRACE_PERIOD 1500
 

+ 6 - 3
process.cpp

@@ -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;
 }

+ 6 - 1
registry.cpp

@@ -239,7 +239,7 @@ void override_milliseconds(char *service_name, HKEY key, char *value, unsigned l
   if (! ok) *buffer = default_value;
 }
 
-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) {
+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) {
   unsigned long ret;
 
   /* Get registry */
@@ -322,6 +322,11 @@ int get_parameters(char *service_name, char *exe, unsigned long exelen, char *fl
   *stop_method = ~0;
   if (stop_ok) *stop_method &= ~stop_method_skip;
 
+  /* Try to get kill delays - may fail. */
+  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);
+  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);
+  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);
+
   /* Close registry */
   RegCloseKey(key);
 

+ 4 - 1
registry.h

@@ -9,6 +9,9 @@
 #define NSSM_REG_EXIT "AppExit"
 #define NSSM_REG_THROTTLE "AppThrottle"
 #define NSSM_REG_STOP_METHOD_SKIP "AppStopMethodSkip"
+#define NSSM_REG_KILL_CONSOLE_GRACE_PERIOD "AppStopMethodConsole"
+#define NSSM_REG_KILL_WINDOW_GRACE_PERIOD "AppStopMethodWindow"
+#define NSSM_REG_KILL_THREADS_GRACE_PERIOD "AppStopMethodThreads"
 #define NSSM_REG_STDIN "AppStdin"
 #define NSSM_REG_STDOUT "AppStdout"
 #define NSSM_REG_STDERR "AppStderr"
@@ -26,7 +29,7 @@ int expand_parameter(HKEY, char *, char *, unsigned long, bool);
 int get_number(HKEY, char *, unsigned long *, bool);
 int get_number(HKEY, char *, unsigned long *);
 void override_milliseconds(char *, HKEY, char *, unsigned long *, unsigned long, unsigned long);
-int get_parameters(char *, char *, unsigned long, char *, unsigned long, char *, unsigned long, char **, unsigned long *, unsigned long *, STARTUPINFO *);
+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 *);
 int get_exit_action(char *, unsigned long *, unsigned char *, bool *);
 
 #endif

+ 7 - 4
service.cpp

@@ -14,6 +14,9 @@ bool stopping;
 bool allow_restart;
 unsigned long throttle_delay;
 unsigned long stop_method;
+unsigned long kill_console_delay;
+unsigned long kill_window_delay;
+unsigned long kill_threads_delay;
 CRITICAL_SECTION throttle_section;
 CONDITION_VARIABLE throttle_condition;
 HANDLE throttle_timer;
@@ -390,7 +393,7 @@ int start_service() {
 
   /* Get startup parameters */
   char *env = 0;
-  int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &stop_method, &si);
+  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);
   if (ret) {
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);
     return stop_service(2, true, true);
@@ -446,9 +449,9 @@ int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
   if (graceful) {
     service_status.dwCurrentState = SERVICE_STOP_PENDING;
     service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;
-    if (stop_method & NSSM_STOP_METHOD_CONSOLE && imports.AttachConsole) service_status.dwWaitHint += NSSM_KILL_CONSOLE_GRACE_PERIOD;
-    if (stop_method & NSSM_STOP_METHOD_WINDOW) service_status.dwWaitHint += NSSM_KILL_WINDOW_GRACE_PERIOD;
-    if (stop_method & NSSM_STOP_METHOD_THREADS) service_status.dwWaitHint += NSSM_KILL_THREADS_GRACE_PERIOD;
+    if (stop_method & NSSM_STOP_METHOD_CONSOLE && imports.AttachConsole) service_status.dwWaitHint += kill_console_delay;
+    if (stop_method & NSSM_STOP_METHOD_WINDOW) service_status.dwWaitHint += kill_window_delay;
+    if (stop_method & NSSM_STOP_METHOD_THREADS) service_status.dwWaitHint += kill_threads_delay;
     SetServiceStatus(service_handle, &service_status);
   }