У меня есть программа, которая обрабатывает записи базы данных. Я добавил в программу функцию завершения работы для записи файла дампа в случае каждого выхода, чтобы знать, куда действовать в базе данных в случае досрочного завершения работы. Итак, это выглядит примерно так:
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, поэтому на этом этапе вам придется завершить процесс через ОС. Теперь мне было интересно, как правильно выйти из перехватчика выключения раньше, или вы должны завершать перехват выключения в каждом случае?
System.exit
в хуке завершения работы, заставляющем программу продолжать работать, меня тоже удивляет, и я могу воспроизвести это поведение локально.
В моем случае я позволил хуку просто завершиться. Так что никаких проблем нет. Мне просто интересно такое поведение с system.exit
Да, итак, этот вопрос распадается на два несвязанных пункта. [1] Как мне выйти из перехватчика выключения (и ответ разочаровывает: вы этого не делаете - ваша JVM уже завершает работу. Насколько вам известно, другие перехватчики выключения уже запущены или уже работают. Просто... не делайте того, чего не можете сделать, вот и все. Замените logTheStuff()
на if (writable) logTheStuff()
в вашем крючке выключения, и отдельно, [B] WTF не работает с Java, просто аварийно завершает процедуру выключения, если вы вызываете .exit
в середине. Это действительно странно. Сообщите об ошибке. Но прекратите это делать.
Не вызывайте System.exit в обработчике завершения работы. System.exit не возвращается, пока не завершатся все перехватчики выключения... но вы вызываете его в перехватчике выключения.
«Мне просто интересно такое поведение с system.exit». Эта проблема была вторым результатом Google для «system.exit внутри крючка выключения». Кажется, они решили, что это не проблема.
«Все работает так, как задумано». Я понимаю. ТИЛ, я думаю :D
возможно, секрет (частично) задокументирован для exit()
: «... а затем блокируется на неопределенный срок. Этот метод не возвращает и не генерирует исключение; то есть он не завершается ни нормально, ни внезапно». - так что, очевидно, вызов exit()
блокирует само завершение работы... также из addShutdownHook()
: "... перехватчики завершения работы также должны быстро завершить свою работу. ..."
это напоминает мне создание экземпляра внутри собственного конструктора...
Это явно задокументировано для System.exit(int) начиная с Java 20: «Поскольку этот метод всегда блокируется на неопределенный срок, если он вызывается из перехватчика выключения, он предотвратит завершение этого перехватчика выключения. Следовательно, это предотвратит последовательность выключения от завершения."
@MarkRotteveel Это документ для Runtime.exit
. Однако System.exit
действительно относится к этому методу.
@Майкл Верно, смешал их, потому что по сути это одно и то же :)
Этот вопрос не является «ясным ФП». Тот факт, что кто-то ответил на этот вопрос, и вы нашли этот ответ приемлемым, не означает, что ваш вопрос ясен. Какое отношение это имеет к базам данных? Какое отношение это имеет к файлу дампа? Кажется, это сводится к следующему: «Почему мое приложение не завершает работу, если я вызываю System.exit(1)
из перехватчика выключения?» Это часть ясности: у вас есть куча информации, благодаря которой ваш вопрос звучит так, будто он имеет какое-то большее значение или концепцию, но это не так.
В документах это не указано. Однако исходный код довольно ясен. Потоки завершения работы, как следует из названия, выполняются в отдельных потоках. Ожидая завершения каждого потока, JVM находится в середине кода, который этим управляет (в классе java.lang.Shutdown
).. в частности, удерживает блокировку. Поэтому, когда ваш поток завершения работы также вызывает его, вы получаете мгновенную тупиковую ситуацию:
System.exit()
зависает в ожидании снятия блокировки.Таким образом, ни один из них никогда не закончится, и процесс зашел в тупик.
Вывод: вызов System.exit
в перехватчике завершения работы всегда приводит к такому результату. Это последний вызов, который поток когда-либо сделает, и процесс завершения работы никогда не завершится.
NB: Что касается ответа на ваш актуальный вопрос «как мне это сделать», ответ такой: нет. JVM уже закрывается. Итак, если ваше намерение состоит в том, чтобы «мой поток завершения работы не должен больше ничего делать», просто return
или просто не делайте этого (например, используйте if
). Если ваше намерение таково: «Я также хочу, чтобы другие потоки завершения работы не запускались/перестали работать», вы не можете этого сделать. У вас нет контроля над порядком (вы не можете создать обработчик завершения работы, который запускается раньше других и имеет возможность остановить запуск других), и вы не можете просто прервать выполнение потоков. Они уже бегут, насколько вы знаете, они уже закончили. Вы не можете путешествовать во времени.
Если у вас есть несколько перехватчиков завершения работы, которые не должны запускаться в случае сбоя или не должны запускаться до тех пор, пока не будет завершен шаг «записать некоторые журналы», вам нужно создать небольшую структуру, в которой вы зарегистрируете, что это «должно запускаться первым, а в случае прерывания» никто из остальных не должен запускать эту штуку, и все «они должны запускаться позже и только в том случае, если этап регистрации завершен», и все это представляет собой единственную ловушку завершения работы.
Подсказка¹ из документации из exit()
: «... а затем блокируется на неопределенный срок. Этот метод не возвращает и не генерирует исключение; то есть он не завершается ни нормально, ни внезапно». |-| ¹ это не настоящая проблема (замок), но все же признак того, что он все равно заблокирует крючок
@user85421 user85421 Начиная с Java 20 это явно документировано: «Поскольку этот метод всегда блокируется на неопределенный срок, если он вызывается из перехватчика выключения, он предотвратит завершение этого перехватчика выключения. Следовательно, это предотвратит завершение последовательности выключения».
Спасибо @rzwitserloot за это замечательное объяснение/ответ. Я понял проблему и найду другие способы ее решения, кроме использования дополнительных вызовов System.exit. Парню, завершающему вопрос: что вы имеете в виду под словами «нужна детализация или ясность?» Вопрос был ясен и даже получил несколько ответов в течение дня. Так что я думаю, что вопрос не может быть таким уж плохим с точки зрения ясности. Кроме того: что вы хотите получить за дополнительную информацию? Данная информация в основном такая же, как и в официальном описании ошибки, на которое ссылается Майкл. То есть этого достаточно, чтобы исправить ошибку, но недостаточно для SO? Полагаю, что так...
Почему ты должен что-то делать? Просто позвольте этому методу ничего не делать (с ранним
return;
или чем-то еще). Другие совершенно несвязанные перехватчики завершения работы могут захотеть выполнить собственную очистку. Кажется, было бы слишком усердно пытаться помешать им выполнить очистку только потому, что несвязанная очистка не удалась.