Browse Source

Added mandatory restart delay.

The REG_DWORD AppRestartDelay entry specifies a minimum number of
milliseconds by which we will delay the restart (not the initial start)
of the managed application.  If the application exited soon enough to
trigger throttling, the actual delay will be whichever is longer of
AppRestartDelay and the calculated throttle time.

Thanks Andrew RedzMax.
Iain Patterson 8 years ago
parent
commit
7fdd3f6c44
11 changed files with 68 additions and 27 deletions
  1. 3 0
      ChangeLog.txt
  2. 15 0
      README.txt
  3. 3 0
      gui.cpp
  4. BIN
      messages.mc
  5. BIN
      nssm.rc
  6. 5 0
      registry.cpp
  7. 1 0
      registry.h
  8. 22 21
      resource.h
  9. 17 6
      service.cpp
  10. 1 0
      service.h
  11. 1 0
      settings.cpp

+ 3 - 0
ChangeLog.txt

@@ -6,6 +6,9 @@ Changes since 2.21
   * NSSM can now set the priority class and processor
     affinity of the managed application.
 
+  * NSSM can now apply an unconditional delay before
+    restarting the application.
+
   * NSSM can now optionally rotate existing files when
     redirecting I/O.
 

+ 15 - 0
README.txt

@@ -56,6 +56,9 @@ AppEnvironment.
 Since version 2.22, NSSM can set the managed application's process priority
 and CPU affinity.
 
+Since version 2.22, NSSM can apply an unconditional delay before restarting
+an application which has exited.
+
 Since version 2.22, NSSM can rotate existing output files when redirecting I/O.
 
 Since version 2.22, NSSM can set service display name, description, startup
@@ -129,6 +132,17 @@ You can change the threshold for the service by setting the number of
 milliseconds as a REG_DWORD value in the registry at
 HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppThrottle.
 
+Alternatively, NSSM can pause for a configurable amount of time before
+attempting to restart the application even if it successfully ran for the
+amount of time specified by AppThrottle.  NSSM will consult the REG_DWORD value
+at HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppRestartDelay
+for the number of milliseconds to wait before attempting a restart.  If
+AppRestartDelay is set and the application is determined to be subject to
+throttling, NSSM will pause the service for whichever is longer of the
+configured restart delay and the calculated throttle period.
+
+If AppRestartDelay is missing or invalid, only throttling will be applied.
+
 NSSM will look in the registry under
 HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppExit for
 string (REG_EXPAND_SZ) values corresponding to the exit code of the application.
@@ -588,6 +602,7 @@ Thanks to Doug Watson for suggesting file rotation.
 Thanks to Арслан Сайдуганов for suggesting setting process priority.
 Thanks to Robert Middleton for suggestion and draft implementation of process
 affinity support.
+Thanks to Andrew RedzMax for suggesting an unconditional restart delay.
 
 Licence
 -------

+ 3 - 0
gui.cpp

@@ -141,6 +141,7 @@ int nssm_gui(int resource, nssm_service_t *service) {
     SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, service->throttle_delay, 0);
     combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);
     SendMessage(combo, CB_SETCURSEL, service->default_exit_action, 0);
+    SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_RESTART_DELAY, service->restart_delay, 0);
 
     /* I/O tab. */
     SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDIN, service->stdin_path);
@@ -488,6 +489,7 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
   combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);
   service->default_exit_action = (unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0);
   if (service->default_exit_action == CB_ERR) service->default_exit_action = 0;
+  check_number(tablist[NSSM_TAB_EXIT], IDC_RESTART_DELAY, &service->restart_delay);
 
   /* Get I/O stuff. */
   check_io(window, _T("stdin"), service->stdin_path, _countof(service->stdin_path), IDC_STDIN);
@@ -1008,6 +1010,7 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
       SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_REALLY, (LPARAM) message_string(NSSM_GUI_EXIT_REALLY));
       SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_UNCLEAN, (LPARAM) message_string(NSSM_GUI_EXIT_UNCLEAN));
       SendMessage(combo, CB_SETCURSEL, NSSM_EXIT_RESTART, 0);
+      SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_RESTART_DELAY, 0, 0);
 
       /* I/O tab. */
       tab.pszText = message_string(NSSM_GUI_TAB_IO);

BIN
messages.mc


BIN
nssm.rc


+ 5 - 0
registry.cpp

@@ -69,6 +69,8 @@ int create_parameters(nssm_service_t *service, bool editing) {
   if (stop_method_skip) set_number(key, NSSM_REG_STOP_METHOD_SKIP, stop_method_skip);
   else if (editing) RegDeleteValue(key, NSSM_REG_STOP_METHOD_SKIP);
   if (service->default_exit_action < NSSM_NUM_EXIT_ACTIONS) create_exit_action(service->name, exit_action_strings[service->default_exit_action], editing);
+  if (service->restart_delay) set_number(key, NSSM_REG_RESTART_DELAY, service->restart_delay);
+  else if (editing) RegDeleteValue(key, NSSM_REG_RESTART_DELAY);
   if (service->throttle_delay != NSSM_RESET_THROTTLE_RESTART) set_number(key, NSSM_REG_THROTTLE, service->throttle_delay);
   else if (editing) RegDeleteValue(key, NSSM_REG_THROTTLE);
   if (service->kill_console_delay != NSSM_KILL_CONSOLE_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, service->kill_console_delay);
@@ -566,6 +568,9 @@ int get_parameters(nssm_service_t *service, STARTUPINFO *si) {
   /* Change back in case the startup directory needs to be deleted. */
   SetCurrentDirectory(cwd);
 
+  /* Try to get mandatory restart delay */
+  override_milliseconds(service->name, key, NSSM_REG_RESTART_DELAY, &service->restart_delay, 0, NSSM_EVENT_BOGUS_RESTART_DELAY);
+
   /* Try to get throttle restart delay */
   override_milliseconds(service->name, key, NSSM_REG_THROTTLE, &service->throttle_delay, NSSM_RESET_THROTTLE_RESTART, NSSM_EVENT_BOGUS_THROTTLE);
 

+ 1 - 0
registry.h

@@ -8,6 +8,7 @@
 #define NSSM_REG_ENV _T("AppEnvironment")
 #define NSSM_REG_ENV_EXTRA _T("AppEnvironmentExtra")
 #define NSSM_REG_EXIT _T("AppExit")
+#define NSSM_REG_RESTART_DELAY _T("AppRestartDelay")
 #define NSSM_REG_THROTTLE _T("AppThrottle")
 #define NSSM_REG_STOP_METHOD_SKIP _T("AppStopMethodSkip")
 #define NSSM_REG_KILL_CONSOLE_GRACE_PERIOD _T("AppStopMethodConsole")

+ 22 - 21
resource.h

@@ -39,26 +39,27 @@
 #define IDC_BROWSE_STDERR               1020
 #define IDC_THROTTLE                    1021
 #define IDC_APPEXIT                     1022
-#define IDC_DIR                         1023
-#define IDC_BROWSE_DIR                  1024
-#define IDC_ENVIRONMENT                 1025
-#define IDC_ENVIRONMENT_REPLACE         1026
-#define IDC_TRUNCATE                    1027
-#define IDC_ROTATE                      1028
-#define IDC_ROTATE_SECONDS              1029
-#define IDC_ROTATE_BYTES_LOW            1030
-#define IDC_DISPLAYNAME                 1031
-#define IDC_DESCRIPTION                 1032
-#define IDC_STARTUP                     1033
-#define IDC_LOCALSYSTEM                 1034
-#define IDC_INTERACT                    1035
-#define IDC_ACCOUNT                     1036
-#define IDC_USERNAME                    1037
-#define IDC_PASSWORD1                   1038
-#define IDC_PASSWORD2                   1039
-#define IDC_PRIORITY                    1040
-#define IDC_AFFINITY_ALL                1041
-#define IDC_AFFINITY                    1042
+#define IDC_RESTART_DELAY               1023
+#define IDC_DIR                         1024
+#define IDC_BROWSE_DIR                  1025
+#define IDC_ENVIRONMENT                 1026
+#define IDC_ENVIRONMENT_REPLACE         1027
+#define IDC_TRUNCATE                    1028
+#define IDC_ROTATE                      1029
+#define IDC_ROTATE_SECONDS              1030
+#define IDC_ROTATE_BYTES_LOW            1031
+#define IDC_DISPLAYNAME                 1032
+#define IDC_DESCRIPTION                 1033
+#define IDC_STARTUP                     1034
+#define IDC_LOCALSYSTEM                 1035
+#define IDC_INTERACT                    1036
+#define IDC_ACCOUNT                     1037
+#define IDC_USERNAME                    1038
+#define IDC_PASSWORD1                   1039
+#define IDC_PASSWORD2                   1040
+#define IDC_PRIORITY                    1041
+#define IDC_AFFINITY_ALL                1042
+#define IDC_AFFINITY                    1043
 
 // Next default values for new objects
 // 
@@ -66,7 +67,7 @@
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_NEXT_RESOURCE_VALUE        115
 #define _APS_NEXT_COMMAND_VALUE         40001
-#define _APS_NEXT_CONTROL_VALUE         1043
+#define _APS_NEXT_CONTROL_VALUE         1044
 #define _APS_NEXT_SYMED_VALUE           101
 #endif
 #endif

+ 17 - 6
service.cpp

@@ -162,9 +162,9 @@ unsigned long priority_index_to_constant(int index) {
   return NORMAL_PRIORITY_CLASS;
 }
 
-static inline int throttle_milliseconds(unsigned long throttle) {
+static inline unsigned long throttle_milliseconds(unsigned long throttle) {
   /* pow() operates on doubles. */
-  int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;
+  unsigned long ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;
   return ret * 1000;
 }
 
@@ -1490,6 +1490,9 @@ int start_service(nssm_service_t *service) {
     else service->throttle = 0;
   }
 
+  /* Ensure the restart delay is always applied. */
+  if (service->restart_delay && ! service->throttle) service->throttle++;
+
   return 0;
 }
 
@@ -1632,14 +1635,22 @@ void throttle_restart(nssm_service_t *service) {
   /* This can't be a restart if the service is already running. */
   if (! service->throttle++) return;
 
-  int ms = throttle_milliseconds(service->throttle);
+  unsigned long ms;
+  unsigned long throttle_ms = throttle_milliseconds(service->throttle);
+  TCHAR threshold[8], milliseconds[8];
+
+  if (service->restart_delay > throttle_ms) ms = service->restart_delay;
+  else ms = throttle_ms;
 
   if (service->throttle > 7) service->throttle = 8;
 
-  TCHAR threshold[8], milliseconds[8];
-  _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
-  log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
+
+  if (service->throttle == 1 && service->restart_delay > throttle_ms) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESTART_DELAY, service->name, milliseconds, 0);
+  else {
+    _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
+  }
 
   if (use_critical_section) EnterCriticalSection(&service->throttle_section);
   else if (service->throttle_timer) {

+ 1 - 0
service.h

@@ -66,6 +66,7 @@ typedef struct {
   unsigned long rotate_bytes_low;
   unsigned long rotate_bytes_high;
   unsigned long default_exit_action;
+  unsigned long restart_delay;
   unsigned long throttle_delay;
   unsigned long stop_method;
   unsigned long kill_console_delay;

+ 1 - 0
settings.cpp

@@ -811,6 +811,7 @@ settings_t settings[] = {
   { NSSM_REG_ENV, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
   { NSSM_REG_ENV_EXTRA, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
   { NSSM_REG_PRIORITY, REG_SZ, (void *) priority_strings[NSSM_NORMAL_PRIORITY], false, 0, setting_set_priority, setting_get_priority },
+  { NSSM_REG_RESTART_DELAY, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
   { NSSM_REG_STDIN, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
   { NSSM_REG_STDIN NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDIN_SHARING, false, 0, setting_set_number, setting_get_number },
   { NSSM_REG_STDIN NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDIN_DISPOSITION, false, 0, setting_set_number, setting_get_number },