Завершение работы Windows при запуске Java-приложения из сценария bat

У меня есть скрипт bat, который запускает Java-приложение. Если я нажимаю Ctrl + C на нем, приложение завершает работу изящно, вызывая все перехватчики завершения работы. Однако, если я просто закрою окно cmd скрипта bat, перехватчики отключения никогда не будут вызываться.

Есть ли способ решить это? Может быть, есть способ рассказать сценарию bat, как завершить вызванные приложения, когда его окно закрыто?

3 ответа

Решение

Из документации addShutdownHook:

В редких случаях виртуальная машина может прервать работу, то есть прекратить работу, не выключившись полностью. Это происходит, когда виртуальная машина завершается извне, например, с помощью сигнала SIGKILL в Unix или вызова TerminateProcess в Microsoft Windows.

Поэтому я думаю, что здесь нечего делать, к сожалению.


Сигнал CTRL-CLOSE в консоли Windows. Кажется, не настраивается.

Цитирование выше ссылка:

Система генерирует CTRL+CLOSE сигнал, когда пользователь закрывает консоль. Все процессы, подключенные к консоли, получают сигнал, давая возможность каждому процессу очиститься перед завершением. Когда процесс получает этот сигнал, функция-обработчик может выполнить одно из следующих действий после выполнения любых операций очистки:

  • Вызов ExitProcess прекратить процесс.
  • Вернуть FALSE, Если ни одна из зарегистрированных функций-обработчиков не возвращает TRUEобработчик по умолчанию завершает процесс.
  • Вернуть TRUE, В этом случае никакие другие функции-обработчики не вызываются, и всплывающее диалоговое окно спрашивает пользователя, следует ли завершить процесс. Если пользователь решает не завершать процесс, система не закрывает консоль, пока процесс не завершится окончательно.

UPD. Если вам подходят нативные твики, WinAPI SetConsoleCtrlHandler Функция открывает путь для подавления поведения по умолчанию.

UPD2. Откровения по обработке сигналов Java и прекращению относительно старая статья, но раздел Написание обработчиков сигналов Java действительно может содержать то, что вам нужно.


UPD3. Я пробовал обработчики сигналов Java из статьи выше. Работает с SIGINT хорошо, но это не то, что нам нужно, и я решил нести его с SetConsoleCtrlHandler, Результат немного сложен и, возможно, не стоит реализовывать в вашем проекте. Во всяком случае, это может помочь кому-то еще.

Итак, идея была:

  1. Сохраняйте ссылку на поток обработчика отключения.
  2. Установите собственную подпрограмму обработчика консоли с помощью JNI.
  3. Вызвать пользовательский метод Java на CTRL+CLOSE сигнал.
  4. Вызовите обработчик отключения из этого метода.

Java-код:

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");
    }
}

Родные replaceConsoleHandler:

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

И сам обработчик:

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

И это работает. В коде JNI все проверки ошибок опущены для очистки. Обработчик выключения создает пустой файл "d:\shutdown.mark" чтобы указать правильное отключение.

Полные исходники с скомпилированными тестовыми файлами здесь.

В дополнение к приведенному выше ответу об использовании SetConsoleCtrlHandler, вы также можете сделать это, используя JNA, вместо того, чтобы писать собственный нативный код.

Вы можете создать свой собственный интерфейс в kernel32, если хотите, или использовать тот, который представлен в этой превосходной среде: https://gitlab.com/axet/desktop

Пример кода:

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();
}

Обратите внимание, что я назначил анонимный класс полю. Первоначально я определил это в вызове SetConsoleCtrlHandler, но я думаю, что это собиралось JVM.

Редактировать 09.04.17: Обновлена ​​ссылка с github на gitlab.

Хотя пакетный файл может быть прерван, консоль (окно), в котором запущен пакетный файл, может быть оставлена ​​открытой в зависимости от операционной системы, командного процессора и того, как было запущено выполнение пакетного файла (из командной строки или через ярлык).

taskkill хорошая команда для завершения программы, которая распространяется в Windows (я предполагаю, что вы хотите остановить другую программу, а не сам пакетный файл).

Можно завершить программы по идентификатору процесса, имени исполняемого файла, заголовку окна, состоянию (т.е. не отвечает), имени DLL или имени службы.

Вот несколько примеров, основанных на том, как вы можете использовать это в своем пакетном файле:

Принудительно завершить "program.exe" (часто требуется флаг /f "force", чтобы принудительно остановить программу, просто проверьте, нужен ли он для вашего приложения методом проб и ошибок):

taskkill /f /im program.exe 

Остановите все неотвечающие программы:

taskkill /fi "Status eq NOT RESPONDING"

Остановите программу на основе заголовка окна (здесь подстановочный знак * может совпадать с чем угодно):

taskkill /fi "WindowTitle eq Please Login"

taskkill /fi "WindowTitle eq Microsoft*"

Вы даже можете использовать его, чтобы остановить программу на другом компьютере в вашей сети (хотя запись пароля учетной записи в пакетном файле просто не очень хорошая идея).

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

Другая альтернатива, также распространяемая с Windows, tskill, Хотя у него не так много опций, команды немного проще.

РЕДАКТИРОВАТЬ:

Я не уверен, что есть. Я думаю, что закрытие окна cmd сродни force-closing приложение т.е. немедленное прекращение без дальнейшего уведомления. Это имеет поведение многих приложений, которые, когда их просят force-close, они на самом деле занимают много времени, чтобы окончательно прекратить. Это связано с тем, что в попытках ОС освободить все ресурсы некоторые ресурсы (особенно определенные ресурсы ввода-вывода и / или файлов) не сразу отпускаются.

ИМО, Java ничего не может с этим поделать, если захочет. Принудительное закрытие происходит на уровне операционной системы, после чего оно освобождает используемые дескрипторы памяти, файлов и ввода-вывода и т. Д. Java не (и не любая другая программа) не контролирует принудительное закрытие. На данный момент ОС берет на себя ответственность.

Кто-нибудь, пожалуйста, поправьте меня, если я ошибаюсь.

Другие вопросы по тегам