Throttle restarts.
authorIain Patterson <me@iain.cx>
Thu, 3 Feb 2011 18:43:02 +0000 (18:43 +0000)
committerIain Patterson <me@iain.cx>
Thu, 3 Feb 2011 18:43:02 +0000 (18:43 +0000)
Back off from restarting the application immediately if it starts
successfully but exits too soon.

Handle resume messages from the service console to restart the
application immediately even if it is throttled.

Use service status wait hints to tell the operating system how long
start, stop and resume actions are likely to take.

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

index 3ef6005..88a7290 100644 (file)
@@ -68,8 +68,15 @@ action if/when the application dies.
 \r
 With no configuration from you, NSSM will try to restart itself if it notices\r
 that the application died but you didn't send it a stop signal.  NSSM will\r
-keep trying, pausing 30 seconds between each attempt, until the service is\r
-successfully started or you send it a stop signal.\r
+keep trying, pausing between each attempt, until the service is successfully\r
+started or you send it a stop signal.\r
+\r
+NSSM will pause an increasingly longer time between subsequent restart attempts\r
+if the service fails to start in a timely manner, up to a maximum of 60 seconds.\r
+This is so it does not consume an excessive amount of CPU time trying to start\r
+a failed application over and over again.  If you identify the cause of the\r
+failure and don't want to wait you can use the Windows service console to\r
+send a continue signal to NSSM and it will retry within a few seconds.\r
 \r
 NSSM will look in the registry under\r
 HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppExit for\r
@@ -161,6 +168,7 @@ Thanks to Joel Reingold for spotting a command line truncation bug.
 Thanks to Arve Knudsen for spotting that child processes of the monitored\r
 application could be left running on service shutdown, and that a missing\r
 registry value for AppDirectory confused NSSM.\r
+Thanks to Peter Wagemans and Laszlo Kereszt for suggesting throttling restarts.\r
 \r
 Licence\r
 -------\r
index c92daa3..413eab3 100644 (file)
@@ -259,3 +259,18 @@ Failed to enumerate running threads when terminating service %1:
 %2
 .
 
+MessageId = +1
+SymbolicName = NSSM_EVENT_THROTTLED
+Severity = Warning
+Language = English
+Service %1 ran for less than %2 milliseconds.
+Restart will be delayed by %3 milliseconds.
+.
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_RESET_THROTTLE
+Severity = Informational
+Language = English
+Request to resume service %1.  Throttling of restart attempts will be reset.
+.
+
diff --git a/nssm.h b/nssm.h
index c29ec81..af8380e 100644 (file)
--- a/nssm.h
+++ b/nssm.h
@@ -33,6 +33,12 @@ int str_equiv(const char *, const char *);
 #define SERVICE_NAME_LENGTH KEY_LENGTH - 55\r
 \r
 /*\r
+  Throttle the restart of the service if it stops before this many\r
+  milliseconds have elapsed since startup.\r
+*/\r
+#define NSSM_RESET_THROTTLE_RESTART 1500\r
+\r
+/*\r
   How many milliseconds to wait for the application to die after posting to\r
   its windows' message queues.\r
 */\r
@@ -43,4 +49,7 @@ int str_equiv(const char *, const char *);
 */\r
 #define NSSM_KILL_THREADS_GRACE_PERIOD 1500\r
 \r
+/* Margin of error for service status wait hints in milliseconds. */\r
+#define NSSM_WAITHINT_MARGIN 2000\r
+\r
 #endif\r
index 8e56cf7..1010666 100644 (file)
@@ -10,10 +10,20 @@ char exe[EXE_LENGTH];
 char flags[CMD_LENGTH];\r
 char dir[MAX_PATH];\r
 bool stopping;\r
+CRITICAL_SECTION throttle_section;\r
+CONDITION_VARIABLE throttle_condition;\r
 \r
 static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;\r
 static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };\r
 \r
+static unsigned long throttle;\r
+\r
+static inline int throttle_milliseconds() {\r
+  /* pow() operates on doubles. */\r
+  int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
+  return ret * 1000;\r
+}\r
+\r
 /* Connect to the service manager */\r
 SC_HANDLE open_service_manager() {\r
   SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);\r
@@ -148,11 +158,11 @@ void WINAPI service_main(unsigned long argc, char **argv) {
   /* Initialise status */\r
   ZeroMemory(&service_status, sizeof(service_status));\r
   service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
-  service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;\r
+  service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
   service_status.dwWin32ExitCode = NO_ERROR;\r
   service_status.dwServiceSpecificExitCode = 0;\r
   service_status.dwCheckPoint = 0;\r
-  service_status.dwWaitHint = 1000;\r
+  service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
 \r
   /* Signal we AREN'T running the server */\r
   process_handle = 0;\r
@@ -177,6 +187,7 @@ void WINAPI service_main(unsigned long argc, char **argv) {
   }\r
 \r
   service_status.dwCurrentState = SERVICE_START_PENDING;\r
+  service_status.dwWaitHint = NSSM_RESET_THROTTLE_RESTART + NSSM_WAITHINT_MARGIN;\r
   SetServiceStatus(service_handle, &service_status);\r
 \r
   /* Try to create the exit action parameters; we don't care if it fails */\r
@@ -184,6 +195,9 @@ void WINAPI service_main(unsigned long argc, char **argv) {
 \r
   set_service_recovery(service_name);\r
 \r
+  /* Used for signalling a resume if the service pauses when throttled. */\r
+  InitializeCriticalSection(&throttle_section);\r
+\r
   monitor_service();\r
 }\r
 \r
@@ -230,6 +244,22 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
     case SERVICE_CONTROL_STOP:\r
       stop_service(0, true, true);\r
       return NO_ERROR;\r
+\r
+    case SERVICE_CONTROL_CONTINUE:\r
+      throttle = 0;\r
+      WakeConditionVariable(&throttle_condition);\r
+      service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
+      service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;\r
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);\r
+      SetServiceStatus(service_handle, &service_status);\r
+      return NO_ERROR;\r
+\r
+    case SERVICE_CONTROL_PAUSE:\r
+      /*\r
+        We don't accept pause messages but it isn't possible to register\r
+        only for continue messages so we have to handle this case.\r
+      */\r
+      return ERROR_CALL_NOT_IMPLEMENTED;\r
   }\r
 \r
   /* Unknown control */\r
@@ -257,6 +287,9 @@ int start_service() {
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);\r
     return stop_service(2, true, true);\r
   }\r
+\r
+  throttle_restart();\r
+\r
   if (! CreateProcess(0, cmd, 0, 0, false, 0, 0, dir, &si, &pi)) {\r
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, GetLastError(), 0);\r
     return stop_service(3, true, true);\r
@@ -268,6 +301,9 @@ int start_service() {
   service_status.dwCurrentState = SERVICE_RUNNING;\r
   SetServiceStatus(service_handle, &service_status);\r
 \r
+  /* Wait for a clean startup. */\r
+  if (WaitForSingleObject(process_handle, NSSM_RESET_THROTTLE_RESTART) == WAIT_TIMEOUT) throttle = 0;\r
+\r
   return 0;\r
 }\r
 \r
@@ -281,6 +317,7 @@ int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
   /* Signal we are stopping */\r
   if (graceful) {\r
     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
+    service_status.dwWaitHint = NSSM_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;\r
     SetServiceStatus(service_handle, &service_status);\r
   }\r
 \r
@@ -383,3 +420,26 @@ void CALLBACK end_service(void *arg, unsigned char why) {
     break;\r
   }\r
 }\r
+\r
+void throttle_restart() {\r
+  /* This can't be a restart if the service is already running. */\r
+  if (! throttle++) return;\r
+\r
+  int ms = throttle_milliseconds();\r
+\r
+  if (throttle > 7) throttle = 8;\r
+\r
+  char threshold[8], milliseconds[8];\r
+  _snprintf(threshold, sizeof(threshold), "%d", NSSM_RESET_THROTTLE_RESTART);\r
+  _snprintf(milliseconds, sizeof(milliseconds), "%d", ms);\r
+  log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
+\r
+  EnterCriticalSection(&throttle_section);\r
+\r
+  service_status.dwCurrentState = SERVICE_PAUSED;\r
+  SetServiceStatus(service_handle, &service_status);\r
+\r
+  SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);\r
+\r
+  LeaveCriticalSection(&throttle_section);\r
+}\r
index fb52c16..6914661 100644 (file)
--- a/service.h
+++ b/service.h
@@ -16,5 +16,6 @@ int monitor_service();
 int start_service();\r
 int stop_service(unsigned long, bool, bool);\r
 void CALLBACK end_service(void *, unsigned char);\r
+void throttle_restart();\r
 \r
 #endif\r