Browse Source

Enforce maximum report delay when starting service.

We were not changing the service's state to running until the restart
throttling threshold had passed.  However best practice is to change the
status to running or stopped as quickly as possible.  Therefore we we
now report the status after no more than 20000 milliseconds regardless
of the throttle setting.
Iain Patterson 8 years ago
parent
commit
98a2516c22
3 changed files with 42 additions and 7 deletions
  1. 13 0
      messages.mc
  2. 1 1
      nssm.h
  3. 28 6
      service.cpp

+ 13 - 0
messages.mc

@@ -1376,3 +1376,16 @@ Language = Italian
 Chiamata a CreateThread() fallita:
 %1
 .
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_STARTUP_DELAY_TOO_LONG
+Severity = Informational
+Language = English
+The minimum number of milliseconds which must pass before service %1 is considered to have been started successfully is set to %2.  Access to the Windows service control manager is blocked until the service updates its status, therefore %3 will wait a maximum of %4 milliseconds before reporting the service's state as running.  Service restart throttling will be enforced if the service runs for less than the full %2 milliseconds.
+.
+Language = French
+The minimum number of milliseconds which must pass before service %1 is considered to have been started successfully is set to %2.  Access to the Windows service control manager is blocked until the service updates its status, therefore %3 will wait a maximum of %4 milliseconds before reporting the service's state as running.  Service restart throttling will be enforced if the service runs for less than the full %2 milliseconds.
+.
+Language = Italian
+The minimum number of milliseconds which must pass before service %1 is considered to have been started successfully is set to %2.  Access to the Windows service control manager is blocked until the service updates its status, therefore %3 will wait a maximum of %4 milliseconds before reporting the service's state as running.  Service restart throttling will be enforced if the service runs for less than the full %2 milliseconds.
+.

+ 1 - 1
nssm.h

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

+ 28 - 6
service.cpp

@@ -450,13 +450,34 @@ int start_service() {
 
   close_output_handles(&si);
 
-  /* Wait for a clean startup. */
-  if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;
+  /*
+    Wait for a clean startup before changing the service status to RUNNING
+    but be mindful of the fact that we are blocking the service control manager
+    so abandon the wait before too much time has elapsed.
+  */
+  unsigned long delay = throttle_delay;
+  if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
+    char delay_milliseconds[16];
+    _snprintf_s(delay_milliseconds, sizeof(delay_milliseconds), _TRUNCATE, "%lu", delay);
+    char deadline_milliseconds[16];
+    _snprintf_s(deadline_milliseconds, sizeof(deadline_milliseconds), _TRUNCATE, "%lu", NSSM_SERVICE_STATUS_DEADLINE);
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service_name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
+    delay = NSSM_SERVICE_STATUS_DEADLINE;
+  }
+  unsigned long deadline = WaitForSingleObject(process_handle, delay);
 
   /* Signal successful start */
   service_status.dwCurrentState = SERVICE_RUNNING;
   SetServiceStatus(service_handle, &service_status);
 
+  /* Continue waiting for a clean startup. */
+  if (deadline == WAIT_TIMEOUT) {
+    if (throttle_delay > delay) {
+      if (WaitForSingleObject(process_handle, throttle_delay - delay) == WAIT_TIMEOUT) throttle = 0;
+    }
+    else throttle = 0;
+  }
+
   return 0;
 }
 
@@ -639,9 +660,10 @@ void throttle_restart() {
   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.
+  NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running
+  and we haven't finished waiting we increment dwCheckPoint and add whichever is
+  smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
+  dwWaitHint.
 
   Only doing both these things will prevent the system from killing the service.
 
@@ -672,7 +694,7 @@ int await_shutdown(char *function_name, char *service_name, SERVICE_STATUS_HANDL
   waited = 0;
   while (waited < timeout) {
     interval = timeout - waited;
-    if (interval > NSSM_SHUTDOWN_CHECKPOINT) interval = NSSM_SHUTDOWN_CHECKPOINT;
+    if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
 
     service_status->dwCurrentState = SERVICE_STOP_PENDING;
     service_status->dwWaitHint += interval;