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
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
\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
+ /* 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
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
if (pid) {\r
/* Shut down service */\r
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
- kill_process(service_name, stop_method, process_handle, pid, 0);\r
+ kill_process(service_name, service_handle, &service_status, stop_method, process_handle, pid, 0);\r
}\r
else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
\r
\r
/* Clean up. */\r
if (exitcode == STILL_ACTIVE) exitcode = 0;\r
- kill_process_tree(service_name, stop_method, pid, exitcode, pid, &creation_time, &exit_time);\r
+ kill_process_tree(service_name, service_handle, &service_status, stop_method, pid, exitcode, pid, &creation_time, &exit_time);\r
\r
/*\r
The why argument is true if our wait timed out or false otherwise.\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
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