Browse Source

Try to send Control-C event to application console.

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.
Iain Patterson 9 years ago
parent
commit
b841998b63
6 changed files with 145 additions and 0 deletions
  1. 3 0
      ChangeLog.txt
  2. 6 0
      README.txt
  3. 76 0
      messages.mc
  4. 5 0
      nssm.h
  5. 54 0
      process.cpp
  6. 1 0
      process.h

+ 3 - 0
ChangeLog.txt

@@ -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

+ 6 - 0
README.txt

@@ -36,6 +36,10 @@ Thanks François-Régis Tardy.
 Since version 2.15, NSSM is translated into Italian.
 Thanks Riccardo Gusmeroli.
 
+Since version 2.17, NSSM can try to shut down console applications by
+simulating a Control-C keypress.  If they have installed a handler routine
+they can clean up and shut down gracefully on receipt of the event.
+
 Usage
 -----
 In the usage notes below, arguments to the program may be written in angle 
@@ -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
 the default language when the user's display language was not translated.
 Thanks to Riccardo Gusmeroli for Italian translation.
+Thanks to Eric Cheldelin for the inspiration to generate a Control-C event
+on shutdown.
 
 Licence
 -------

+ 76 - 0
messages.mc

@@ -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
+.

+ 5 - 0
nssm.h

@@ -38,6 +38,11 @@ int str_equiv(const char *, const char *);
 */
 #define NSSM_RESET_THROTTLE_RESTART 1500
 
+/*
+  How many milliseconds to wait for the application to die after sending
+  a Control-C event to its console.
+*/
+#define NSSM_KILL_CONSOLE_GRACE_PERIOD 1500
 /*
   How many milliseconds to wait for the application to die after posting to
   its windows' message queues.

+ 54 - 0
process.cpp

@@ -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;

+ 1 - 0
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