Try to send Control-C event to application console.
authorIain Patterson <me@iain.cx>
Tue, 19 Feb 2013 17:38:03 +0000 (09:38 -0800)
committerIain Patterson <me@iain.cx>
Tue, 19 Feb 2013 18:04:23 +0000 (10:04 -0800)
Try to attach to the application's console and send a Control-C event when
shutting down.  If a console exists, is successfully attached and successfully
sent the event, allow a grace period for the application to exit before sending
window messages or eventually calling TerminateProcess().

Now console applications which register shutdown handlers, such as java
launched from a batch file, have a chance to clean up and shut down gracefully.

Thanks Eric Cheldelin.

ChangeLog.txt
README.txt
messages.mc
nssm.h
process.cpp
process.h

index 727c4b0..77ba306 100644 (file)
@@ -4,6 +4,9 @@ Changes since 2.16
 
   * Silently ignore INTERROGATE control.
 
+  * Try to send Control-C events to console applications when
+    shutting them down.
+
 Changes since 2.15
 -----------------
   * Fixed case where NSSM could kill unrelated processes when
index f0270f8..ddc6092 100644 (file)
@@ -36,6 +36,10 @@ Thanks François-Régis Tardy.
 Since version 2.15, NSSM is translated into Italian.\r
 Thanks Riccardo Gusmeroli.\r
 \r
+Since version 2.17, NSSM can try to shut down console applications by\r
+simulating a Control-C keypress.  If they have installed a handler routine\r
+they can clean up and shut down gracefully on receipt of the event.\r
+\r
 Usage\r
 -----\r
 In the usage notes below, arguments to the program may be written in angle \r
@@ -198,6 +202,8 @@ Thanks to François-Régis Tardy for French translation.
 Thanks to Emilio Frini for spotting that French was inadvertently set as\r
 the default language when the user's display language was not translated.\r
 Thanks to Riccardo Gusmeroli for Italian translation.\r
+Thanks to Eric Cheldelin for the inspiration to generate a Control-C event\r
+on shutdown.\r
 \r
 Licence\r
 -------\r
index d8cb74c..e8dabe0 100644 (file)
@@ -1116,3 +1116,79 @@ Language = Italian
 Chiamata a GetProcessTimes():
 %1
 .
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_ATTACHCONSOLE_FAILED
+Severity = Error
+Language = English
+Error attaching to console for service %1.
+AttachConsole() failed:
+%2
+.
+Language = French
+Error attaching to console for service %1.
+AttachConsole() a échoué:
+%2
+.
+Language = Italian
+Error attaching to console for service %1.
+AttachConsole() fallita:
+%2
+.
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED
+Severity = Error
+Language = English
+Error setting null handler for Control-C events sent to service %1.
+SetConsoleCtrlHandler() failed:
+%2
+.
+Language = French
+Error setting null handler for Control-C events sent to service %1.
+SetConsoleCtrlHandler() a échoué:
+%2
+.
+Language = Italian
+Error setting null handler for Control-C events sent to service %1.
+SetConsoleCtrlHandler() fallita:
+%2
+.
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED
+Severity = Error
+Language = English
+Error generating Control-C event for service %1.
+GenerateConsoleCtrlEvent() failed:
+%2
+.
+Language = French
+Error generating Control-C event for service %1.
+GenerateConsoleCtrlEvent() a échoué:
+%2
+.
+Language = Italian
+Error generating Control-C event for service %1.
+GenerateConsoleCtrlEvent() fallita:
+%2
+.
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_FREECONSOLE_FAILED
+Severity = Warning
+Language = English
+Error detaching from console for service %1.
+FreeConsole() failed:
+%2
+.
+Language = French
+Error detaching from console for service %1.
+FreeConsole() a échoué:
+%2
+.
+Language = Italian
+Error detaching from console for service %1.
+FreeConsole() fallita:
+%2
+.
diff --git a/nssm.h b/nssm.h
index 7ab0546..01885eb 100644 (file)
--- a/nssm.h
+++ b/nssm.h
@@ -38,6 +38,11 @@ int str_equiv(const char *, const char *);
 */\r
 #define NSSM_RESET_THROTTLE_RESTART 1500\r
 \r
+/*\r
+  How many milliseconds to wait for the application to die after sending\r
+  a Control-C event to its console.\r
+*/\r
+#define NSSM_KILL_CONSOLE_GRACE_PERIOD 1500\r
 /*\r
   How many milliseconds to wait for the application to die after posting to\r
   its windows' message queues.\r
index b63cc41..b55da50 100644 (file)
@@ -146,6 +146,9 @@ int kill_process(char *service_name, HANDLE process_handle, unsigned long pid, u
 
   kill_t k = { pid, exitcode, 0 };
 
+  /* Try to send a Control-C event to the console. */
+  if (! kill_console(service_name, process_handle, pid)) return 1;
+
   /*
     Try to post messages to the windows belonging to the given process ID.
     If the process is a console application it won't have any windows so there's
@@ -169,6 +172,57 @@ int kill_process(char *service_name, HANDLE process_handle, unsigned long pid, u
   return TerminateProcess(process_handle, exitcode);
 }
 
+/* Simulate a Control-C event to our console (shared with the app). */
+int kill_console(char *service_name, HANDLE process_handle, unsigned long pid) {
+  unsigned long ret;
+
+  /* Try to attach to the process's console. */
+  if (! AttachConsole(pid)) {
+    ret = GetLastError();
+
+    switch (ret) {
+      case ERROR_INVALID_HANDLE:
+        /* The app doesn't have a console. */
+        return 1;
+
+      case ERROR_GEN_FAILURE:
+        /* The app already exited. */
+        return 2;
+
+      case ERROR_ACCESS_DENIED:
+      default:
+        /* We already have a console. */
+        log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, service_name, error_string(ret), 0);
+        return 3;
+    }
+  }
+
+  /* Ignore the event ourselves. */
+  ret = 0;
+  if (! SetConsoleCtrlHandler(0, TRUE)) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, service_name, error_string(GetLastError()), 0);
+    ret = 4;
+  }
+
+  /* Sent the event. */
+  if (! ret) {
+    if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, service_name, error_string(GetLastError()), 0);
+      ret = 5;
+    }
+  }
+
+  /* Detach from the console. */
+  if (! FreeConsole()) {
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, service_name, error_string(GetLastError()), 0);
+  }
+
+  /* Wait for process to exit. */
+  if (! WaitForSingleObject(process_handle, NSSM_KILL_CONSOLE_GRACE_PERIOD)) return 6;
+
+  return ret;
+}
+
 void kill_process_tree(char *service_name, unsigned long pid, unsigned long exitcode, unsigned long ppid, FILETIME *parent_creation_time, FILETIME *parent_exit_time) {
   /* Shouldn't happen unless the service failed to start. */
   if (! pid) return;
index c12d733..2560621 100644 (file)
--- a/process.h
+++ b/process.h
@@ -15,6 +15,7 @@ int check_parent(char *, PROCESSENTRY32 *, unsigned long, FILETIME *, FILETIME *
 int CALLBACK kill_window(HWND, LPARAM);
 int kill_threads(char *, kill_t *);
 int kill_process(char *, HANDLE, unsigned long, unsigned long);
+int kill_console(char *, HANDLE, unsigned long);
 void kill_process_tree(char *, unsigned long, unsigned long, unsigned long, FILETIME *, FILETIME *);
 
 #endif