From cb571db509c239c3c465907a718479612fe5bb15 Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Wed, 13 Nov 2013 15:35:20 +0000 Subject: [PATCH] Allow overriding time to wait when trying to kill the application. Three new registry entries can be used to specify a wait time in milliseconds after attempting a stop method. AppStopMethodConsole AppStopMethodWindow AppStopMethodThreads The default for each remains the same, 1500ms. Thanks Russ Holmann. --- ChangeLog.txt | 5 +++++ README.txt | 18 ++++++++++++++++++ messages.mc | 39 +++++++++++++++++++++++++++++++++++++++ nssm.h | 6 +++--- process.cpp | 9 ++++++--- registry.cpp | 7 ++++++- registry.h | 5 ++++- service.cpp | 11 +++++++---- 8 files changed, 88 insertions(+), 12 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 1513a54..d4850a3 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,8 @@ +Changes since 2.17 +----------------- + * Timeouts for each shutdown method can be configured in + the registry. + Changes since 2.16 ----------------- * NSSM can now redirect the service's I/O streams to any path diff --git a/README.txt b/README.txt index 2fbe5c8..f2fe712 100644 --- a/README.txt +++ b/README.txt @@ -43,6 +43,9 @@ they can clean up and shut down gracefully on receipt of the event. Since version 2.17, NSSM can redirect the managed application's I/O streams to an arbitrary path. +Since version 2.18, NSSM can be configured to wait a user-specified amount +of time for the application to exit when shutting down. + Usage ----- @@ -182,6 +185,20 @@ Take great care when including 8 in the value of AppStopMethodSkip. If NSSM does not call TerminateProcess() it is possible that the application will not exit when the service stops. +By default NSSM will allow processes 1500ms to respond to each of the methods +described above before proceeding to the next one. The timeout can be +configured on a per-method basis by creating REG_DWORD entries in the +registry under HKLM\SYSTEM\CurrentControlSet\Services\\Parameters. + + AppStopMethodConsole + AppStopMethodWindow + AppStopMethodThreads + +Each value should be set to the number of milliseconds to wait. Please note +that the timeout applies to each process in the application's process tree, +so the actual time to shutdown may be longer than the sum of all configured +timeouts if the application spawns multiple subprocesses. + I/O redirection --------------- @@ -282,6 +299,7 @@ Thanks to Riccardo Gusmeroli for Italian translation. Thanks to Eric Cheldelin for the inspiration to generate a Control-C event on shutdown. Thanks to Brian Baxter for suggesting how to escape quotes from the command prompt. +Thanks to Russ Holmann for suggesting that the shutdown timeout be configurable. Licence ------- diff --git a/messages.mc b/messages.mc index 6f6ae40..fdf295b 100644 --- a/messages.mc +++ b/messages.mc @@ -1305,3 +1305,42 @@ Language = Italian Chiamata a GetProcAddress(%1) fallita: %2 . + +MessageId = +1 +SymbolicName = NSSM_EVENT_BOGUS_KILL_CONSOLE_GRACE_PERIOD +Severity = Warning +Language = English +The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after sending a Control-C event, was not of type REG_DWORD. The default time of %3 milliseconds will be used. +. +Language = French +The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after sending a Control-C event, was not of type REG_DWORD. The default time of %3 milliseconds will be used. +. +Language = Italian +The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after sending a Control-C event, was not of type REG_DWORD. The default time of %3 milliseconds will be used. +. + +MessageId = +1 +SymbolicName = NSSM_EVENT_BOGUS_KILL_WINDOW_GRACE_PERIOD +Severity = Warning +Language = English +The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after posting a WM_CLOSE message to windows managed by the application, was not of type REG_DWORD. The default time of %3 milliseconds will be used. +. +Language = French +The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after posting a WM_CLOSE message to windows managed by the application, was not of type REG_DWORD. The default time of %3 milliseconds will be used. +. +Language = Italian +The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after posting a WM_CLOSE message to windows managed by the application, was not of type REG_DWORD. The default time of %3 milliseconds will be used. +. + +MessageId = +1 +SymbolicName = NSSM_EVENT_BOGUS_KILL_THREADS_GRACE_PERIOD +Severity = Warning +Language = English +The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after posting a WM_QUIT message to the message queues of threads managed by the application, was not of type REG_DWORD. The default time of %3 milliseconds will be used. +. +Language = French +The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after posting a WM_QUIT message to the message queues of threads managed by the application, was not of type REG_DWORD. The default time of %3 milliseconds will be used. +. +Language = Italian +The registry value %2, used to specify the maximum number of milliseconds to wait for service %1 to stop after posting a WM_QUIT message to the message queues of threads managed by the application, was not of type REG_DWORD. The default time of %3 milliseconds will be used. +. diff --git a/nssm.h b/nssm.h index b5d457e..01228bd 100644 --- a/nssm.h +++ b/nssm.h @@ -43,17 +43,17 @@ int str_equiv(const char *, const char *); /* How many milliseconds to wait for the application to die after sending - a Control-C event to its console. + a Control-C event to its console. Override in registry. */ #define NSSM_KILL_CONSOLE_GRACE_PERIOD 1500 /* How many milliseconds to wait for the application to die after posting to - its windows' message queues. + its windows' message queues. Override in registry. */ #define NSSM_KILL_WINDOW_GRACE_PERIOD 1500 /* How many milliseconds to wait for the application to die after posting to - its threads' message queues. + its threads' message queues. Override in registry. */ #define NSSM_KILL_THREADS_GRACE_PERIOD 1500 diff --git a/process.cpp b/process.cpp index fc0056d..f060d72 100644 --- a/process.cpp +++ b/process.cpp @@ -1,6 +1,9 @@ #include "nssm.h" extern imports_t imports; +extern unsigned long kill_console_delay; +extern unsigned long kill_window_delay; +extern unsigned long kill_threads_delay; int get_process_creation_time(HANDLE process_handle, FILETIME *ft) { FILETIME creation_time, exit_time, kernel_time, user_time; @@ -161,7 +164,7 @@ int kill_process(char *service_name, unsigned long stop_method, HANDLE process_h if (stop_method & NSSM_STOP_METHOD_WINDOW) { EnumWindows((WNDENUMPROC) kill_window, (LPARAM) &k); if (k.signalled) { - if (! WaitForSingleObject(process_handle, NSSM_KILL_WINDOW_GRACE_PERIOD)) return 1; + if (! WaitForSingleObject(process_handle, kill_window_delay)) return 1; } } @@ -172,7 +175,7 @@ int kill_process(char *service_name, unsigned long stop_method, HANDLE process_h */ if (stop_method & NSSM_STOP_METHOD_THREADS) { if (kill_threads(service_name, &k)) { - if (! WaitForSingleObject(process_handle, NSSM_KILL_THREADS_GRACE_PERIOD)) return 1; + if (! WaitForSingleObject(process_handle, kill_threads_delay)) return 1; } } @@ -233,7 +236,7 @@ int kill_console(char *service_name, HANDLE process_handle, unsigned long pid) { } /* Wait for process to exit. */ - if (WaitForSingleObject(process_handle, NSSM_KILL_CONSOLE_GRACE_PERIOD)) return 6; + if (WaitForSingleObject(process_handle, kill_console_delay)) return 6; return ret; } diff --git a/registry.cpp b/registry.cpp index 7a7b784..7eb6112 100644 --- a/registry.cpp +++ b/registry.cpp @@ -239,7 +239,7 @@ void override_milliseconds(char *service_name, HKEY key, char *value, unsigned l if (! ok) *buffer = default_value; } -int get_parameters(char *service_name, char *exe, unsigned long exelen, char *flags, unsigned long flagslen, char *dir, unsigned long dirlen, char **env, unsigned long *throttle_delay, unsigned long *stop_method, STARTUPINFO *si) { +int get_parameters(char *service_name, char *exe, unsigned long exelen, char *flags, unsigned long flagslen, char *dir, unsigned long dirlen, char **env, unsigned long *throttle_delay, unsigned long *stop_method, unsigned long *kill_console_delay, unsigned long *kill_window_delay, unsigned long *kill_threads_delay, STARTUPINFO *si) { unsigned long ret; /* Get registry */ @@ -322,6 +322,11 @@ int get_parameters(char *service_name, char *exe, unsigned long exelen, char *fl *stop_method = ~0; if (stop_ok) *stop_method &= ~stop_method_skip; + /* Try to get kill delays - may fail. */ + override_milliseconds(service_name, key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, kill_console_delay, NSSM_KILL_CONSOLE_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_CONSOLE_GRACE_PERIOD); + override_milliseconds(service_name, key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, kill_window_delay, NSSM_KILL_WINDOW_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_WINDOW_GRACE_PERIOD); + override_milliseconds(service_name, key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, kill_threads_delay, NSSM_KILL_THREADS_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_THREADS_GRACE_PERIOD); + /* Close registry */ RegCloseKey(key); diff --git a/registry.h b/registry.h index 9129b64..17f26d7 100644 --- a/registry.h +++ b/registry.h @@ -9,6 +9,9 @@ #define NSSM_REG_EXIT "AppExit" #define NSSM_REG_THROTTLE "AppThrottle" #define NSSM_REG_STOP_METHOD_SKIP "AppStopMethodSkip" +#define NSSM_REG_KILL_CONSOLE_GRACE_PERIOD "AppStopMethodConsole" +#define NSSM_REG_KILL_WINDOW_GRACE_PERIOD "AppStopMethodWindow" +#define NSSM_REG_KILL_THREADS_GRACE_PERIOD "AppStopMethodThreads" #define NSSM_REG_STDIN "AppStdin" #define NSSM_REG_STDOUT "AppStdout" #define NSSM_REG_STDERR "AppStderr" @@ -26,7 +29,7 @@ int expand_parameter(HKEY, char *, char *, unsigned long, bool); int get_number(HKEY, char *, unsigned long *, bool); int get_number(HKEY, char *, unsigned long *); void override_milliseconds(char *, HKEY, char *, unsigned long *, unsigned long, unsigned long); -int get_parameters(char *, char *, unsigned long, char *, unsigned long, char *, unsigned long, char **, unsigned long *, unsigned long *, STARTUPINFO *); +int get_parameters(char *, char *, unsigned long, char *, unsigned long, char *, unsigned long, char **, unsigned long *, unsigned long *, unsigned long *, unsigned long *, unsigned long *, STARTUPINFO *); int get_exit_action(char *, unsigned long *, unsigned char *, bool *); #endif diff --git a/service.cpp b/service.cpp index 596aa7e..f14390f 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; @@ -390,7 +393,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); @@ -446,9 +449,9 @@ 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; + 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.20.1