X-Git-Url: http://git.iain.cx/?a=blobdiff_plain;f=service.cpp;h=be7050117415e799b11f4fbbb0b9928d59d23294;hb=bf75c3da8b88b87c298e2b358a8d38c8476fa85b;hp=596aa7e4fcabfd902074ab0bd344675e323516d8;hpb=ee52ab253b65c52ff039d8058c66b1a63a656b01;p=nssm.git diff --git a/service.cpp b/service.cpp index 596aa7e..be70501 100644 --- a/service.cpp +++ b/service.cpp @@ -14,6 +14,9 @@ bool stopping; bool allow_restart; unsigned long throttle_delay; unsigned long stop_method; +unsigned long kill_console_delay; +unsigned long kill_window_delay; +unsigned long kill_threads_delay; CRITICAL_SECTION throttle_section; CONDITION_VARIABLE throttle_condition; HANDLE throttle_timer; @@ -34,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); @@ -340,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: @@ -390,7 +418,7 @@ int start_service() { /* Get startup parameters */ char *env = 0; - int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &stop_method, &si); + int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &stop_method, &kill_console_delay, &kill_window_delay, &kill_threads_delay, &si); if (ret) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0); return stop_service(2, true, true); @@ -422,13 +450,34 @@ int start_service() { close_output_handles(&si); - /* Wait for a clean startup. */ - if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0; + /* + Wait for a clean startup before changing the service status to RUNNING + but be mindful of the fact that we are blocking the service control manager + so abandon the wait before too much time has elapsed. + */ + unsigned long delay = throttle_delay; + if (delay > NSSM_SERVICE_STATUS_DEADLINE) { + char delay_milliseconds[16]; + _snprintf_s(delay_milliseconds, sizeof(delay_milliseconds), _TRUNCATE, "%lu", delay); + char deadline_milliseconds[16]; + _snprintf_s(deadline_milliseconds, sizeof(deadline_milliseconds), _TRUNCATE, "%lu", NSSM_SERVICE_STATUS_DEADLINE); + log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service_name, delay_milliseconds, NSSM, deadline_milliseconds, 0); + delay = NSSM_SERVICE_STATUS_DEADLINE; + } + unsigned long deadline = WaitForSingleObject(process_handle, delay); /* Signal successful start */ service_status.dwCurrentState = SERVICE_RUNNING; SetServiceStatus(service_handle, &service_status); + /* Continue waiting for a clean startup. */ + if (deadline == WAIT_TIMEOUT) { + if (throttle_delay > delay) { + if (WaitForSingleObject(process_handle, throttle_delay - delay) == WAIT_TIMEOUT) throttle = 0; + } + else throttle = 0; + } + return 0; } @@ -446,9 +495,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 += NSSM_KILL_CONSOLE_GRACE_PERIOD; - if (stop_method & NSSM_STOP_METHOD_WINDOW) service_status.dwWaitHint += NSSM_KILL_WINDOW_GRACE_PERIOD; - if (stop_method & NSSM_STOP_METHOD_THREADS) service_status.dwWaitHint += NSSM_KILL_THREADS_GRACE_PERIOD; SetServiceStatus(service_handle, &service_status); } @@ -456,7 +502,7 @@ int stop_service(unsigned long exitcode, bool graceful, bool default_action) { if (pid) { /* Shut down service */ log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0); - kill_process(service_name, stop_method, process_handle, pid, 0); + kill_process(service_name, service_handle, &service_status, stop_method, process_handle, pid, 0); } else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0); @@ -506,7 +552,7 @@ void CALLBACK end_service(void *arg, unsigned char why) { /* Clean up. */ if (exitcode == STILL_ACTIVE) exitcode = 0; - kill_process_tree(service_name, stop_method, pid, exitcode, pid, &creation_time, &exit_time); + kill_process_tree(service_name, service_handle, &service_status, stop_method, pid, exitcode, pid, &creation_time, &exit_time); /* The why argument is true if our wait timed out or false otherwise. @@ -596,3 +642,90 @@ void throttle_restart() { else Sleep(ms); } } + +/* + When responding to a stop (or any other) request we need to set dwWaitHint to + the number of milliseconds we expect the operation to take, and optionally + increase dwCheckPoint. If dwWaitHint milliseconds elapses without the + operation completing or dwCheckPoint increasing, the system will consider the + service to be hung. + + However the system will consider the service to be hung after 30000 + milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not + changed. Therefore if we want to wait longer than that we must periodically + increase dwCheckPoint. + + Furthermore, it will consider the service to be hung after 60000 milliseconds + regardless of the value of dwCheckPoint unless dwWaitHint is increased every + time dwCheckPoint is also increased. + + Our strategy then is to retrieve the initial dwWaitHint and wait for + NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running + and we haven't finished waiting we increment dwCheckPoint and add whichever is + smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to + dwWaitHint. + + Only doing both these things will prevent the system from killing the service. + + Returns: 1 if the wait timed out. + 0 if the wait completed. + -1 on error. +*/ +int await_shutdown(char *function_name, char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVICE_STATUS *service_status, HANDLE process_handle, unsigned long timeout) { + unsigned long interval; + unsigned long waithint; + unsigned long ret; + unsigned long waited; + char interval_milliseconds[16]; + char timeout_milliseconds[16]; + char waited_milliseconds[16]; + char *function = function_name; + + /* Add brackets to function name. */ + size_t funclen = strlen(function_name) + 3; + char *func = (char *) HeapAlloc(GetProcessHeap(), 0, funclen); + if (func) { + if (_snprintf_s(func, funclen, _TRUNCATE, "%s()", function_name) > -1) function = func; + } + + _snprintf_s(timeout_milliseconds, sizeof(timeout_milliseconds), _TRUNCATE, "%lu", timeout); + + waithint = service_status->dwWaitHint; + waited = 0; + while (waited < timeout) { + interval = timeout - waited; + if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE; + + service_status->dwCurrentState = SERVICE_STOP_PENDING; + service_status->dwWaitHint += interval; + service_status->dwCheckPoint++; + SetServiceStatus(service_handle, service_status); + + if (waited) { + _snprintf_s(waited_milliseconds, sizeof(waited_milliseconds), _TRUNCATE, "%lu", waited); + _snprintf_s(interval_milliseconds, sizeof(interval_milliseconds), _TRUNCATE, "%lu", interval); + log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service_name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0); + } + + switch (WaitForSingleObject(process_handle, interval)) { + case WAIT_OBJECT_0: + ret = 0; + goto awaited; + + case WAIT_TIMEOUT: + ret = 1; + break; + + default: + ret = -1; + goto awaited; + } + + waited += interval; + } + +awaited: + if (func) HeapFree(GetProcessHeap(), 0, func); + + return ret; +}