Spawn a separate thread for stop_service().
authorIain Patterson <me@iain.cx>
Fri, 15 Nov 2013 15:44:46 +0000 (15:44 +0000)
committerIain Patterson <me@iain.cx>
Fri, 15 Nov 2013 16:10:40 +0000 (16:10 +0000)
We must acknowledge a STOP or SHUTDOWN control promptly but the
termination of the application may take a significant amount of time if
one of the AppStopMethod* registry values is set.

We now spawn a separate thread to try to stop the process and to call
await_shutdown() while the main thread immediately acknowledges receipt
of the STOP request.  Once the worker thread has updated the service
status to say that application is really stopped we will be
automatically cleaned up by the system.

If for some reason we can't spawn a new thread we log an error and
ignore user-supplied timeouts so as to ensure we tidy up promptly.

messages.mc
service.cpp

index a2da26f..2ab7dc1 100644 (file)
@@ -1360,3 +1360,19 @@ Language = Italian
 %1 has waited %3 of %5 milliseconds for the %2 service to exit.
 Next update in %4 milliseconds.
 .
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_CREATETHREAD_FAILED
+Severity = Error
+Language = English
+CreateThread() failed:
+%1
+.
+Language = French
+CreateThread() a échoué:
+%1
+.
+Language = Italian
+Chiamata a CreateThread() fallita:
+%1
+.
index 5c6adaf..c24263f 100644 (file)
@@ -37,6 +37,14 @@ static inline int throttle_milliseconds() {
   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
@@ -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
-      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
@@ -449,9 +474,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 (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