18
votes

I have a bat script which runs a java application. If I press ctrl+c on it, it the application terminates gracefully, invoking all the shutdown hooks. However, if I just close the cmd window of the bat script, the shutdown hooks are never invoked.

Is there a way to solve this? Perhaps there's a way to tell the bat script how to terminate the invoked applications when its window is closed?

3

3 Answers

22
votes

From addShutdownHook documentation:

In rare circumstances the virtual machine may abort, that is, stop running without shutting down cleanly. This occurs when the virtual machine is terminated externally, for example with the SIGKILL signal on Unix or the TerminateProcess call on Microsoft Windows.

So i think nothing to do here, unfortunately.


CTRL-CLOSE signal in Windows Console. Seems non-tweakable.

Quoting above link:

The system generates a CTRL+CLOSE signal when the user closes a console. All processes attached to the console receive the signal, giving each process an opportunity to clean up before termination. When a process receives this signal, the handler function can take one of the following actions after performing any cleanup operations:

  • Call ExitProcess to terminate the process.
  • Return FALSE. If none of the registered handler functions returns TRUE, the default handler terminates the process.
  • Return TRUE. In this case, no other handler functions are called, and a pop-up dialog box asks the user whether to terminate the process. If the user chooses not to terminate the process, the system does not close the console until the process finally terminates.

UPD. If native tweaks are acceptable for you, WinAPI SetConsoleCtrlHandler function opens way for suppressing of default behavior.

UPD2. Revelations on Java signal handling and termination relatively old article, but section Writing Java signal handlers really may contain what you need.


UPD3. I've tried Java signal handlers from article above. It works with SIGINT nicely, but it not what we need, and i decided to carry it with SetConsoleCtrlHandler. The result is a bit complicated and may be not worth to implement in your project. Anyway, it could help someone else.

So, the idea was:

  1. Keep reference to shutdown handler thread.
  2. Set custom native console handler routine with JNI.
  3. Call custom Java method on CTRL+CLOSE signal.
  4. Call shutdown handler from that method.

Java code:

public class TestConsoleHandler {

    private static Thread hook;

    public static void main(String[] args) {
        System.out.println("Start");
        hook = new ShutdownHook();
        Runtime.getRuntime().addShutdownHook(hook);
        replaceConsoleHandler(); // actually not "replace" but "add"

        try {
            Thread.sleep(10000); // You have 10 seconds to close console
        } catch (InterruptedException e) {}
    }

    public static void shutdown() {
        hook.run();
    }

    private static native void replaceConsoleHandler();

    static {
        System.loadLibrary("TestConsoleHandler");
    }
}

class ShutdownHook extends Thread {
    public void run() {
        try {
            // do some visible work
            new File("d:/shutdown.mark").createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("Shutdown");
    }
}

Native replaceConsoleHandler:

JNIEXPORT void JNICALL Java_TestConsoleHandler_replaceConsoleHandler(JNIEnv *env, jclass clazz) {
    env->GetJavaVM(&jvm);
    SetConsoleCtrlHandler(&HandlerRoutine, TRUE);
}

And handler itself:

BOOL WINAPI HandlerRoutine(__in DWORD dwCtrlType) {
    if (dwCtrlType == CTRL_CLOSE_EVENT) {
        JNIEnv *env;
        jint res =  jvm->AttachCurrentThread((void **)(&env), &env);
        jclass cls = env->FindClass("TestConsoleHandler");
        jmethodID mid = env->GetStaticMethodID(cls, "shutdown", "()V");
        env->CallStaticVoidMethod(cls, mid);
        jvm->DetachCurrentThread();
        return TRUE;
    }
    return FALSE;
}

And it works. In JNI code all error checks are omitted for clearance. Shutdown handler creates empty file "d:\shutdown.mark" to indicate correct shutdown.

Complete sources with compiled test binaries here.

5
votes

Further to the above answer of using SetConsoleCtrlHandler, you can also do this using JNA rather than writing your own native code.

You could create your own interface on kernel32 if you wanted, or use the one provided in this excellent framework: https://gitlab.com/axet/desktop

Some example code:

import com.github.axet.desktop.os.win.GetLastErrorException;
import com.github.axet.desktop.os.win.handle.HANDLER_ROUTINE;
import com.github.axet.desktop.os.win.libs.Kernel32Ex;
...
private static HANDLER_ROUTINE handler =
  new HANDLER_ROUTINE()
  {
    @Override
    public long callback(long dwCtrlType) {
      if ((int)dwCtrlType == CTRL_CLOSE_EVENT) {
        // *** do your shutdown code here ***
        return 1;
      }
      return 0;
    }
  };

public static void assignShutdownHook() {
  if (!Kernel32Ex.INSTANCE.SetConsoleCtrlHandler(handler, true))
    throw new GetLastErrorException();
}

Note that I assigned the anonymous class to a field. I originally defined it in the call to SetConsoleCtrlHandler, but I think it was getting collected by the JVM.

Edit 4/9/17: Updated link from github to gitlab.

0
votes

Though the batch file may be terminated, the console (window) the batch file has been running in may be left open, depending on the operating system, the command processor, and how batch file execution was started (from a command prompt or through a shortcut).

taskkill is a good command for ending a program which is distributed with Windows (I'm assuming you want to stop a DIFFERENT program, not the batch file itself).

It is possible to end programs by process id, executable name, window title, status (i.e. not responding), DLL name, or service name.

Here are some examples based on how you might use this in your batch file:

Force "program.exe" to stop (often the /f "force" flag is needed to force the program to stop, just check whether it is needed for your application by trial and error):

taskkill /f /im program.exe 

Stop any non-responsive programs:

taskkill /fi "Status eq NOT RESPONDING"

Stop a program based on its window title (* wildcard is allowed here to match anything):

taskkill /fi "WindowTitle eq Please Login"

taskkill /fi "WindowTitle eq Microsoft*"

You can even use it to stop a program on another computer on your network (although writing the password of an account in a batch file is just not a good idea).

taskkill /s JimsPC /u Jim /p James_007 /im firefox.exe

Another alternative also distributed with Windows is tskill. Although it doesn't have nearly as many options, the commands are a bit simpler.

EDIT:

I am not sure there is. I would think that closing the cmd window is akin to force-closing the app i.e. immediate termination with no further notice. This has the behavior of many applications that, when they are asked to force-close, they actually take a long time to finally terminate. This is because in the OS's efforts to release all resources, some resources (especially certain I/O and/or file resources) don't let go immediately.

IMO, there's nothing Java could even do about it if it wanted to. A force-close happens at the OS level, at which point it releases the memory, file and I/O handles in use, etc. Java does not (nor does any other program) have control over a force-close. At this point, the OS is taking charge.

Someone please correct me if I'm wrong.