Browse Source

Added await_shutdown() function.

The system expects service STOP requests to be honoured promptly.  If
service shutdown will take longer than 30 seconds we must update the
service status checkpoint variable before then.

We also need to ensure that the service status wait hint time is
strictly increasing every time we update the checkpoint, otherwise the
service will be considered to be hung after 60 seconds.
Iain Patterson 8 years ago
parent
commit
f3d91adc48
4 changed files with 106 additions and 0 deletions
  1. 16 0
      messages.mc
  2. 3 0
      nssm.h
  3. 86 0
      service.cpp
  4. 1 0
      service.h

+ 16 - 0
messages.mc

@@ -1344,3 +1344,19 @@ The registry value %2, used to specify the maximum number of milliseconds to wai
 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.
 .
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_AWAITING_SHUTDOWN
+Severity = Informational
+Language = English
+%1 has waited %3 of %5 milliseconds for the %2 service to exit.
+Next update in %4 milliseconds.
+.
+Language = French
+%1 has waited %3 of %5 milliseconds for the %2 service to exit.
+Next update in %4 milliseconds.
+.
+Language = Italian
+%1 has waited %3 of %5 milliseconds for the %2 service to exit.
+Next update in %4 milliseconds.
+.

+ 3 - 0
nssm.h

@@ -66,4 +66,7 @@ int str_equiv(const char *, const char *);
 #define NSSM_STOP_METHOD_THREADS (1 << 2)
 #define NSSM_STOP_METHOD_TERMINATE (1 << 3)
 
+/* How many milliseconds to wait before updating service status. */
+#define NSSM_SHUTDOWN_CHECKPOINT 20000
+
 #endif

+ 86 - 0
service.cpp

@@ -599,3 +599,89 @@ void throttle_restart() {
     else Sleep(ms);
   }
 }
+
+/*
+  When responding to a stop (or any other) request we need to set dwWaitHint to
+  the number of milliseconds we expect the operation to take, and optionally
+  increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the
+  operation completing or dwCheckPoint increasing, the system will consider the
+  service to be hung.
+
+  However the system will consider the service to be hung after 30000
+  milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
+  changed.  Therefore if we want to wait longer than that we must periodically
+  increase dwCheckPoint.
+
+  Furthermore, it will consider the service to be hung after 60000 milliseconds
+  regardless of the value of dwCheckPoint unless dwWaitHint is increased every
+  time dwCheckPoint is also increased.
+
+  Our strategy then is to retrieve the initial dwWaitHint and wait for
+  NSSM_SHUTDOWN_CHECKPOINT milliseconds.  If the process is still running and
+  we haven't finished waiting we increment dwCheckPoint and add whichever is
+  smaller of NSSM_SHUTDOWN_CHECKPOINT or the remaining timeout to dwWaitHint.
+
+  Only doing both these things will prevent the system from killing the service.
+
+  Returns: 1 if the wait timed out.
+           0 if the wait completed.
+          -1 on error.
+*/
+int await_shutdown(char *function_name, char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVICE_STATUS *service_status, HANDLE process_handle, unsigned long timeout) {
+  unsigned long interval;
+  unsigned long waithint;
+  unsigned long ret;
+  unsigned long waited;
+  char interval_milliseconds[16];
+  char timeout_milliseconds[16];
+  char waited_milliseconds[16];
+  char *function = function_name;
+
+  /* Add brackets to function name. */
+  size_t funclen = strlen(function_name) + 3;
+  char *func = (char *) HeapAlloc(GetProcessHeap(), 0, funclen);
+  if (func) {
+    if (_snprintf_s(func, funclen, _TRUNCATE, "%s()", function_name) > -1) function = func;
+  }
+
+  _snprintf_s(timeout_milliseconds, sizeof(timeout_milliseconds), _TRUNCATE, "%lu", timeout);
+
+  waithint = service_status->dwWaitHint;
+  waited = 0;
+  while (waited < timeout) {
+    interval = timeout - waited;
+    if (interval > NSSM_SHUTDOWN_CHECKPOINT) interval = NSSM_SHUTDOWN_CHECKPOINT;
+
+    service_status->dwCurrentState = SERVICE_STOP_PENDING;
+    service_status->dwWaitHint += interval;
+    service_status->dwCheckPoint++;
+    SetServiceStatus(service_handle, service_status);
+
+    if (waited) {
+      _snprintf_s(waited_milliseconds, sizeof(waited_milliseconds), _TRUNCATE, "%lu", waited);
+      _snprintf_s(interval_milliseconds, sizeof(interval_milliseconds), _TRUNCATE, "%lu", interval);
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service_name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
+    }
+
+    switch (WaitForSingleObject(process_handle, interval)) {
+      case WAIT_OBJECT_0:
+        ret = 0;
+        goto awaited;
+
+      case WAIT_TIMEOUT:
+        ret = 1;
+      break;
+
+      default:
+        ret = -1;
+        goto awaited;
+    }
+
+    waited += interval;
+  }
+
+awaited:
+  if (func) HeapFree(GetProcessHeap(), 0, func);
+
+  return ret;
+}

+ 1 - 0
service.h

@@ -19,5 +19,6 @@ int start_service();
 int stop_service(unsigned long, bool, bool);
 void CALLBACK end_service(void *, unsigned char);
 void throttle_restart();
+int await_shutdown(char *, char *, SERVICE_STATUS_HANDLE, SERVICE_STATUS *, HANDLE, unsigned long);
 
 #endif