Added strip_basename() function.
[nssm.git] / service.cpp
index 5c6adaf..be70501 100644 (file)
@@ -37,6 +37,14 @@ static inline int throttle_milliseconds() {
   return ret * 1000;\r
 }\r
 \r
   return ret * 1000;\r
 }\r
 \r
+/*\r
+  Wrapper to be called in a new thread so that we can acknowledge a STOP\r
+  control immediately.\r
+*/\r
+static unsigned long WINAPI shutdown_service(void *arg) {\r
+  return stop_service(0, true, true);\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
 /* 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
@@ -343,7 +351,24 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
     case SERVICE_CONTROL_SHUTDOWN:\r
     case SERVICE_CONTROL_STOP:\r
       log_service_control(service_name, control, true);\r
     case SERVICE_CONTROL_SHUTDOWN:\r
     case SERVICE_CONTROL_STOP:\r
       log_service_control(service_name, control, true);\r
-      stop_service(0, true, true);\r
+      /*\r
+        We MUST acknowledge the stop request promptly but we're committed to\r
+        waiting for the application to exit.  Spawn a new thread to wait\r
+        while we acknowledge the request.\r
+      */\r
+      if (! CreateThread(NULL, 0, shutdown_service, (void *) service_name, 0, NULL)) {\r
+        log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
+\r
+        /*\r
+          We couldn't create a thread to tidy up so we'll have to force the tidyup\r
+          to complete in time in this thread.\r
+        */\r
+        kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
+        kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
+        kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
+\r
+        stop_service(0, true, true);\r
+      }\r
       return NO_ERROR;\r
 \r
     case SERVICE_CONTROL_CONTINUE:\r
       return NO_ERROR;\r
 \r
     case SERVICE_CONTROL_CONTINUE:\r
@@ -425,13 +450,34 @@ int start_service() {
 \r
   close_output_handles(&si);\r
 \r
 \r
   close_output_handles(&si);\r
 \r
-  /* Wait for a clean startup. */\r
-  if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;\r
+  /*\r
+    Wait for a clean startup before changing the service status to RUNNING\r
+    but be mindful of the fact that we are blocking the service control manager\r
+    so abandon the wait before too much time has elapsed.\r
+  */\r
+  unsigned long delay = throttle_delay;\r
+  if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
+    char delay_milliseconds[16];\r
+    _snprintf_s(delay_milliseconds, sizeof(delay_milliseconds), _TRUNCATE, "%lu", delay);\r
+    char deadline_milliseconds[16];\r
+    _snprintf_s(deadline_milliseconds, sizeof(deadline_milliseconds), _TRUNCATE, "%lu", NSSM_SERVICE_STATUS_DEADLINE);\r
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service_name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
+    delay = NSSM_SERVICE_STATUS_DEADLINE;\r
+  }\r
+  unsigned long deadline = WaitForSingleObject(process_handle, delay);\r
 \r
   /* Signal successful start */\r
   service_status.dwCurrentState = SERVICE_RUNNING;\r
   SetServiceStatus(service_handle, &service_status);\r
 \r
 \r
   /* Signal successful start */\r
   service_status.dwCurrentState = SERVICE_RUNNING;\r
   SetServiceStatus(service_handle, &service_status);\r
 \r
+  /* Continue waiting for a clean startup. */\r
+  if (deadline == WAIT_TIMEOUT) {\r
+    if (throttle_delay > delay) {\r
+      if (WaitForSingleObject(process_handle, throttle_delay - delay) == WAIT_TIMEOUT) throttle = 0;\r
+    }\r
+    else throttle = 0;\r
+  }\r
+\r
   return 0;\r
 }\r
 \r
   return 0;\r
 }\r
 \r
@@ -449,9 +495,6 @@ int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
   if (graceful) {\r
     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
     service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
   if (graceful) {\r
     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
     service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
-    if (stop_method & NSSM_STOP_METHOD_CONSOLE && imports.AttachConsole) service_status.dwWaitHint += kill_console_delay;\r
-    if (stop_method & NSSM_STOP_METHOD_WINDOW) service_status.dwWaitHint += kill_window_delay;\r
-    if (stop_method & NSSM_STOP_METHOD_THREADS) service_status.dwWaitHint += kill_threads_delay;\r
     SetServiceStatus(service_handle, &service_status);\r
   }\r
 \r
     SetServiceStatus(service_handle, &service_status);\r
   }\r
 \r
@@ -617,9 +660,10 @@ void throttle_restart() {
   time dwCheckPoint is also increased.\r
 \r
   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
   time dwCheckPoint is also increased.\r
 \r
   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
-  NSSM_SHUTDOWN_CHECKPOINT milliseconds.  If the process is still running and\r
-  we haven't finished waiting we increment dwCheckPoint and add whichever is\r
-  smaller of NSSM_SHUTDOWN_CHECKPOINT or the remaining timeout to dwWaitHint.\r
+  NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
+  and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
+  smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
+  dwWaitHint.\r
 \r
   Only doing both these things will prevent the system from killing the service.\r
 \r
 \r
   Only doing both these things will prevent the system from killing the service.\r
 \r
@@ -650,7 +694,7 @@ int await_shutdown(char *function_name, char *service_name, SERVICE_STATUS_HANDL
   waited = 0;\r
   while (waited < timeout) {\r
     interval = timeout - waited;\r
   waited = 0;\r
   while (waited < timeout) {\r
     interval = timeout - waited;\r
-    if (interval > NSSM_SHUTDOWN_CHECKPOINT) interval = NSSM_SHUTDOWN_CHECKPOINT;\r
+    if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
 \r
     service_status->dwCurrentState = SERVICE_STOP_PENDING;\r
     service_status->dwWaitHint += interval;\r
 \r
     service_status->dwCurrentState = SERVICE_STOP_PENDING;\r
     service_status->dwWaitHint += interval;\r