Try to throttle using a critical section.
authorIain Patterson <me@iain.cx>
Tue, 12 Nov 2013 12:31:22 +0000 (12:31 +0000)
committerIain Patterson <me@iain.cx>
Tue, 12 Nov 2013 12:49:53 +0000 (12:49 +0000)
The first implementation of service restart throttling used a condition
variable in a critical section to sleep for the required amount of time.
The implementation was changed to use a waitable timer because Windows
2000 does not support SleepConditionVariableCS() or
WakeConditionVariable().

Since we are now using LoadLibrary() and GetProcAddress() to use newer
functions dynamically without having to build OS-specific binaries, we
can now use a critical section where it's supported and fall back to a
waitable timer when running on Windows 2000.

imports.cpp
imports.h
service.cpp

index 49483d4..99661be 100644 (file)
@@ -44,6 +44,16 @@ int get_imports() {
     if (! imports.AttachConsole) {
       if (error != ERROR_PROC_NOT_FOUND) return 2;
     }
+
+    imports.SleepConditionVariableCS = (SleepConditionVariableCS_ptr) get_import(imports.kernel32, "SleepConditionVariableCS", &error);
+    if (! imports.SleepConditionVariableCS) {
+      if (error != ERROR_PROC_NOT_FOUND) return 3;
+    }
+
+    imports.WakeConditionVariable = (WakeConditionVariable_ptr) get_import(imports.kernel32, "WakeConditionVariable", &error);
+    if (! imports.WakeConditionVariable) {
+      if (error != ERROR_PROC_NOT_FOUND) return 4;
+    }
   }
   else if (error != ERROR_MOD_NOT_FOUND) return 1;
 
index f731ad6..33dbc19 100644 (file)
--- a/imports.h
+++ b/imports.h
@@ -2,10 +2,14 @@
 #define IMPORTS_H
 
 typedef BOOL (WINAPI *AttachConsole_ptr)(DWORD);
+typedef BOOL (WINAPI *SleepConditionVariableCS_ptr)(PCONDITION_VARIABLE, PCRITICAL_SECTION, DWORD);
+typedef void (WINAPI *WakeConditionVariable_ptr)(PCONDITION_VARIABLE);
 
 typedef struct {
   HMODULE kernel32;
   AttachConsole_ptr AttachConsole;
+  SleepConditionVariableCS_ptr SleepConditionVariableCS;
+  WakeConditionVariable_ptr WakeConditionVariable;
 } imports_t;
 
 HMODULE get_dll(const char *, unsigned long *);
index fcb1c5a..d0cba9f 100644 (file)
@@ -14,10 +14,15 @@ bool stopping;
 bool allow_restart;\r
 unsigned long throttle_delay;\r
 unsigned long stop_method;\r
+CRITICAL_SECTION throttle_section;\r
+CONDITION_VARIABLE throttle_condition;\r
 HANDLE throttle_timer;\r
 LARGE_INTEGER throttle_duetime;\r
+bool use_critical_section;\r
 FILETIME creation_time;\r
 \r
+extern imports_t imports;\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
@@ -184,6 +189,10 @@ void WINAPI service_main(unsigned long argc, char **argv) {
     return;\r
   }\r
 \r
+  /* We can use a condition variable in a critical section on Vista or later. */\r
+  if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
+  else use_critical_section = false;\r
+\r
   /* Initialise status */\r
   ZeroMemory(&service_status, sizeof(service_status));\r
   service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
@@ -218,9 +227,12 @@ void WINAPI service_main(unsigned long argc, char **argv) {
   }\r
 \r
   /* Used for signalling a resume if the service pauses when throttled. */\r
-  throttle_timer = CreateWaitableTimer(0, 1, 0);\r
-  if (! throttle_timer) {\r
-    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service_name, error_string(GetLastError()), 0);\r
+  if (use_critical_section) InitializeCriticalSection(&throttle_section);\r
+  else {\r
+    throttle_timer = CreateWaitableTimer(0, 1, 0);\r
+    if (! throttle_timer) {\r
+      log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service_name, error_string(GetLastError()), 0);\r
+    }\r
   }\r
 \r
   monitor_service();\r
@@ -333,10 +345,13 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
 \r
     case SERVICE_CONTROL_CONTINUE:\r
       log_service_control(service_name, control, true);\r
-      if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
       throttle = 0;\r
-      ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
-      SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
+      if (use_critical_section) imports.WakeConditionVariable(&throttle_condition);\r
+      else {\r
+        if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
+        ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
+        SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
+      }\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
@@ -558,7 +573,8 @@ void throttle_restart() {
   _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%d", ms);\r
   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
 \r
-  if (throttle_timer) {\r
+  if (use_critical_section) EnterCriticalSection(&throttle_section);\r
+  else if (throttle_timer) {\r
     ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
     throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
     SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
@@ -567,6 +583,12 @@ void throttle_restart() {
   service_status.dwCurrentState = SERVICE_PAUSED;\r
   SetServiceStatus(service_handle, &service_status);\r
 \r
-  if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);\r
-  else Sleep(ms);\r
+  if (use_critical_section) {\r
+    imports.SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);\r
+    LeaveCriticalSection(&throttle_section);\r
+  }\r
+  else {\r
+    if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);\r
+    else Sleep(ms);\r
+  }\r
 }\r