From a3ad2151ad143eb587958e61a93d2de405d490df Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Fri, 15 Nov 2013 15:44:46 +0000 Subject: [PATCH] Spawn a separate thread for stop_service(). 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 | 16 ++++++++++++++++ service.cpp | 30 ++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/messages.mc b/messages.mc index a2da26f..2ab7dc1 100644 --- a/messages.mc +++ b/messages.mc @@ -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 +. diff --git a/service.cpp b/service.cpp index 5c6adaf..c24263f 100644 --- a/service.cpp +++ b/service.cpp @@ -37,6 +37,14 @@ static inline int throttle_milliseconds() { return ret * 1000; } +/* + Wrapper to be called in a new thread so that we can acknowledge a STOP + control immediately. +*/ +static unsigned long WINAPI shutdown_service(void *arg) { + return stop_service(0, true, true); +} + /* Connect to the service manager */ SC_HANDLE open_service_manager() { SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS); @@ -343,7 +351,24 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon case SERVICE_CONTROL_SHUTDOWN: case SERVICE_CONTROL_STOP: log_service_control(service_name, control, true); - stop_service(0, true, true); + /* + We MUST acknowledge the stop request promptly but we're committed to + waiting for the application to exit. Spawn a new thread to wait + while we acknowledge the request. + */ + if (! CreateThread(NULL, 0, shutdown_service, (void *) service_name, 0, NULL)) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0); + + /* + We couldn't create a thread to tidy up so we'll have to force the tidyup + to complete in time in this thread. + */ + kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD; + kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD; + kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD; + + stop_service(0, true, true); + } return NO_ERROR; case SERVICE_CONTROL_CONTINUE: @@ -449,9 +474,6 @@ int stop_service(unsigned long exitcode, bool graceful, bool default_action) { if (graceful) { service_status.dwCurrentState = SERVICE_STOP_PENDING; service_status.dwWaitHint = NSSM_WAITHINT_MARGIN; - if (stop_method & NSSM_STOP_METHOD_CONSOLE && imports.AttachConsole) service_status.dwWaitHint += kill_console_delay; - if (stop_method & NSSM_STOP_METHOD_WINDOW) service_status.dwWaitHint += kill_window_delay; - if (stop_method & NSSM_STOP_METHOD_THREADS) service_status.dwWaitHint += kill_threads_delay; SetServiceStatus(service_handle, &service_status); } -- 2.7.4