Выход из ловушки завершения работы Java

У меня есть программа, которая обрабатывает записи базы данных. Я добавил в программу функцию завершения работы для записи файла дампа в случае каждого выхода, чтобы знать, куда действовать в базе данных в случае досрочного завершения работы. Итак, это выглядит примерно так:

Runtime.getRuntime().addShutdownHook(new Thread() {
        @Override
        public void run() {
            // write the dumpfile here
        }
    });

До сих пор все выглядит/работает нормально. Но поскольку я собирался реализовать запись файла дампа, мне нужно обработать случай, если файл дампа не может быть создан/записан. Итак, я возился и пытался просто напечатать сообщение о том, что файл дампа недоступен для записи, и попытался полностью выйти из него с помощью System.exit(); но я столкнулся с интересным поведением: если вы выходите из программы через System.exit внутри перехватчика завершения работы, программа продолжает работать. Я думаю, это происходит при выходе из процедуры выхода, и поэтому выход никогда не завершается? В результате программа продолжает печатать на стандартный вывод:

package com.ffleuchten.Test;

public class Test {

    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.out.println("shutdown hook running!");
                System.exit(1);
            }
        });
        
        while(true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread alive!");
        }
    }
}

это приводит к:

Thread alive!
^Cshutdown hook running!
Thread alive!
Thread alive!
^CThread alive!
^C^C^CThread alive!
Thread alive!
Thread alive!
Thread alive!
Thread alive!

^C отмечает моменты времени, когда я нажимал Ctrl+C, чтобы остановить программу. Как видите, я больше не могу остановить программу, нажав ctrl+c, поэтому на этом этапе вам придется завершить процесс через ОС. Теперь мне было интересно, как правильно выйти из перехватчика выключения раньше, или вы должны завершать перехват выключения в каждом случае?

Почему ты должен что-то делать? Просто позвольте этому методу ничего не делать (с ранним return; или чем-то еще). Другие совершенно несвязанные перехватчики завершения работы могут захотеть выполнить собственную очистку. Кажется, было бы слишком усердно пытаться помешать им выполнить очистку только потому, что несвязанная очистка не удалась.

Michael 11.04.2024 16:17
System.exit в хуке завершения работы, заставляющем программу продолжать работать, меня тоже удивляет, и я могу воспроизвести это поведение локально.
Michael 11.04.2024 16:19

В моем случае я позволил хуку просто завершиться. Так что никаких проблем нет. Мне просто интересно такое поведение с system.exit

roediGERhard 11.04.2024 16:22

Да, итак, этот вопрос распадается на два несвязанных пункта. [1] Как мне выйти из перехватчика выключения (и ответ разочаровывает: вы этого не делаете - ваша JVM уже завершает работу. Насколько вам известно, другие перехватчики выключения уже запущены или уже работают. Просто... не делайте того, чего не можете сделать, вот и все. Замените logTheStuff() на if (writable) logTheStuff() в вашем крючке выключения, и отдельно, [B] WTF не работает с Java, просто аварийно завершает процедуру выключения, если вы вызываете .exit в середине. Это действительно странно. Сообщите об ошибке. Но прекратите это делать.

rzwitserloot 11.04.2024 16:23

Не вызывайте System.exit в обработчике завершения работы. System.exit не возвращается, пока не завершатся все перехватчики выключения... но вы вызываете его в перехватчике выключения.

matt 11.04.2024 16:24

«Мне просто интересно такое поведение с system.exit». Эта проблема была вторым результатом Google для «system.exit внутри крючка выключения». Кажется, они решили, что это не проблема.

Michael 11.04.2024 16:25

«Все работает так, как задумано». Я понимаю. ТИЛ, я думаю :D

roediGERhard 11.04.2024 16:45

возможно, секрет (частично) задокументирован для exit(): «... а затем блокируется на неопределенный срок. Этот метод не возвращает и не генерирует исключение; то есть он не завершается ни нормально, ни внезапно». - так что, очевидно, вызов exit() блокирует само завершение работы... также из addShutdownHook(): "... перехватчики завершения работы также должны быстро завершить свою работу. ..."

user85421 11.04.2024 17:34

это напоминает мне создание экземпляра внутри собственного конструктора...

user85421 11.04.2024 17:37

Это явно задокументировано для System.exit(int) начиная с Java 20: «Поскольку этот метод всегда блокируется на неопределенный срок, если он вызывается из перехватчика выключения, он предотвратит завершение этого перехватчика выключения. Следовательно, это предотвратит последовательность выключения от завершения."

Mark Rotteveel 11.04.2024 18:06

@MarkRotteveel Это документ для Runtime.exit. Однако System.exit действительно относится к этому методу.

Michael 11.04.2024 23:49

@Майкл Верно, смешал их, потому что по сути это одно и то же :)

Mark Rotteveel 12.04.2024 07:45

Этот вопрос не является «ясным ФП». Тот факт, что кто-то ответил на этот вопрос, и вы нашли этот ответ приемлемым, не означает, что ваш вопрос ясен. Какое отношение это имеет к базам данных? Какое отношение это имеет к файлу дампа? Кажется, это сводится к следующему: «Почему мое приложение не завершает работу, если я вызываю System.exit(1) из перехватчика выключения?» Это часть ясности: у вас есть куча информации, благодаря которой ваш вопрос звучит так, будто он имеет какое-то большее значение или концепцию, но это не так.

matt 21.04.2024 14:28
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
13
80
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В документах это не указано. Однако исходный код довольно ясен. Потоки завершения работы, как следует из названия, выполняются в отдельных потоках. Ожидая завершения каждого потока, JVM находится в середине кода, который этим управляет (в классе java.lang.Shutdown).. в частности, удерживает блокировку. Поэтому, когда ваш поток завершения работы также вызывает его, вы получаете мгновенную тупиковую ситуацию:

  • Ваш вызов System.exit() зависает в ожидании снятия блокировки.
  • Поток, удерживающий блокировку, не откажется от нее до тех пор, пока не завершатся все потоки завершения работы.

Таким образом, ни один из них никогда не закончится, и процесс зашел в тупик.

Вывод: вызов System.exit в перехватчике завершения работы всегда приводит к такому результату. Это последний вызов, который поток когда-либо сделает, и процесс завершения работы никогда не завершится.

NB: Что касается ответа на ваш актуальный вопрос «как мне это сделать», ответ такой: нет. JVM уже закрывается. Итак, если ваше намерение состоит в том, чтобы «мой поток завершения работы не должен больше ничего делать», просто return или просто не делайте этого (например, используйте if). Если ваше намерение таково: «Я также хочу, чтобы другие потоки завершения работы не запускались/перестали работать», вы не можете этого сделать. У вас нет контроля над порядком (вы не можете создать обработчик завершения работы, который запускается раньше других и имеет возможность остановить запуск других), и вы не можете просто прервать выполнение потоков. Они уже бегут, насколько вы знаете, они уже закончили. Вы не можете путешествовать во времени.

Если у вас есть несколько перехватчиков завершения работы, которые не должны запускаться в случае сбоя или не должны запускаться до тех пор, пока не будет завершен шаг «записать некоторые журналы», вам нужно создать небольшую структуру, в которой вы зарегистрируете, что это «должно запускаться первым, а в случае прерывания» никто из остальных не должен запускать эту штуку, и все «они должны запускаться позже и только в том случае, если этап регистрации завершен», и все это представляет собой единственную ловушку завершения работы.

Подсказка¹ из документации из exit(): «... а затем блокируется на неопределенный срок. Этот метод не возвращает и не генерирует исключение; то есть он не завершается ни нормально, ни внезапно». |-| ¹ это не настоящая проблема (замок), но все же признак того, что он все равно заблокирует крючок

user85421 11.04.2024 17:45

@user85421 user85421 Начиная с Java 20 это явно документировано: «Поскольку этот метод всегда блокируется на неопределенный срок, если он вызывается из перехватчика выключения, он предотвратит завершение этого перехватчика выключения. Следовательно, это предотвратит завершение последовательности выключения».

Mark Rotteveel 11.04.2024 18:08

Спасибо @rzwitserloot за это замечательное объяснение/ответ. Я понял проблему и найду другие способы ее решения, кроме использования дополнительных вызовов System.exit. Парню, завершающему вопрос: что вы имеете в виду под словами «нужна детализация или ясность?» Вопрос был ясен и даже получил несколько ответов в течение дня. Так что я думаю, что вопрос не может быть таким уж плохим с точки зрения ясности. Кроме того: что вы хотите получить за дополнительную информацию? Данная информация в основном такая же, как и в официальном описании ошибки, на которое ссылается Майкл. То есть этого достаточно, чтобы исправить ошибку, но недостаточно для SO? Полагаю, что так...

roediGERhard 13.04.2024 13:27

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