Вызов System.exit() в методе destroy() сервлета

Это продолжение моего предыдущий вопрос.

В Tomcat 5.0.28 была ошибка, из-за которой метод destroy() сервлета не вызывался контейнером при завершении работы. Это исправлено в Tomcat 5.0.30, но если бы метод destroy() сервлета имел System.exit(), это привело бы к тому, что служба Windows Tomcat выдавала ошибку 1053 и отказывалась корректно завершать работу (см. ссылку выше для получения более подробной информации о эта ошибка)

У кого-нибудь есть идеи о том, является ли:

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

  • Почему Tomcat 5.0.30 и (более поздние версии, включая Tomcat 6.x.x) не завершают работу должным образом, если в методе destroy() сервлета есть System.exit().


person Nikhil Kashyap    schedule 13.02.2009    source источник


Ответы (4)


Вы задаете два вопроса:

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

Вызов System.exit() внутри ЛЮБОГО метода, связанного с сервлетом, всегда на 100% неверен. Ваш код — не единственный код, работающий в JVM — даже если вы — единственный работающий сервлет (в контейнере сервлета есть ресурсы, которые ему нужно будет очистить, когда JVM действительно выйдет).

Правильный способ справиться с этим случаем — очистить ваши потоки в методе destroy(). Это означает запускать их так, чтобы вы могли аккуратно остановить их правильным образом. Вот пример (где MyThread является одним из ваших потоков и расширяет ServletManagedThread):

 public class MyServlet extends HttpServlet {
    private List<ServletManagedThread> threads = new ArrayList<ServletManagedThread>();     

     // lots of irrelevant stuff left out for brevity

    public void init() {
        ServletManagedThread t = new MyThread();
        threads.add(t);
        t.start();
    }

    public void destroy() {
        for(ServletManagedThread thread : threads) {
           thread.stopExecuting();
        }
    }
 }

 public abstract class ServletManagedThread extends Thread {

    private boolean keepGoing = true;

    protected abstract void doSomeStuff();
    protected abstract void probablySleepForABit();
    protected abstract void cleanup();

    public void stopExecuting() {
       keepRunning = false;
    }

    public void run() {
       while(keepGoing) {
            doSomeStuff();
            probablySleepForABit();
       }
       this.cleanup();
    }
}

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

Вопрос 2. Почему Tomcat 5.0.30 и (более поздние версии, включая Tomcat 6.x.x) не завершают работу должным образом, если в методе destroy() сервлета есть System.exit()?

Без дополнительного анализа трудно сказать наверняка. Microsoft сообщает, что ошибка 1053 возникает, когда Windows запрашивает выключение службы, но время запроса истекает. Это могло бы создать впечатление, что что-то произошло внутри Tomcat, что привело его в очень плохое состояние. Я, конечно, подозреваю, что причиной может быть ваш звонок System.exit(). Tomcat (в частности, Catalina) регистрирует хук выключения с виртуальной машиной (see org.apache.catalina.startup.Catalina.start(), по крайней мере, в 5.0.30). Этот хук выключения будет вызываться JVM, когда вы вызываете System.exit(). Перехватчик выключения делегирует работу запущенным службам, поэтому потенциально от каждой службы может потребоваться выполнение большого объема работы.

Если хуки выключения (triggered by your System.exit()) не выполняются (они блокируются или что-то в этом роде), то очень легко понять, почему возникает ошибка 1053, учитывая документацию метода Runtime.exit(int) (который вызывается из System.exit()):

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

Это поведение «бессрочной блокировки» определенно вызовет ошибку 1053.

Если вам нужен более полный ответ, вы можете загрузите исходный код и отладьте его самостоятельно.

Но я готов поспорить, что если вы правильно справитесь с проблемой управления потоками (как указано выше), ваши проблемы исчезнут.

Короче говоря, оставьте вызов System.exit() Tomcat — это не ваша работа.

person Jared    schedule 13.02.2009
comment
Я бы даже сказал, что 99,99% всех приложений никогда не нуждаются в вызове System.exit(). Просто выйдите из цикла в своем основном методе или вернитесь из метода run() вашего потока. - person Mr. Shiny and New 安宇; 13.02.2009
comment
Согласен, мистер Блестящий. Каждый раз, когда вы видите вызов System.exit(), вы должны заподозрить плохой дизайн программы. - person Jared; 13.02.2009
comment
Я считаю, что keepGoing должен быть помечен как volatile (или доступ к нему должен быть синхронизирован), иначе некоторые потоки могут не увидеть новое значение. - person Jerome; 01.12.2009
comment
System.exit(int) требуется, если вы хотите указать статус выхода для своей программы, как это делают практически все инструменты командной строки. - person Geoff; 31.05.2010
comment
@Джефф - Ага. Если вы пишете утилиту командной строки, которой нужно манипулировать кодом выхода, вам нужно вызвать System.exit(). Если вам нужно манипулировать кодом выхода из процесса, связанного с сервлетом, я бы серьезно усомнился в конструкции вашей системы. System.exit() полезен только в ситуациях, когда у вас очень жесткий контроль над всей системой - сервлеты не такая ситуация - кто-то другой (контейнер) отвечает за систему, а не ваш сервлет. - person Jared; 07.06.2010

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

Это абсолютно не хорошая идея - это ужасная идея. Метод destroy() вызывается, когда сервлет выводится из эксплуатации, что может произойти по ряду причин: сервлет/веб-приложение было остановлено, веб-приложение не развертывается, веб-приложение перезапускается и т. д.

System.exit() отключает всю JVM! Почему вы хотите принудительно отключить весь сервер только потому, что один сервлет выгружается?

Почему Tomcat 5.0.30 и (более поздние версии, включая Tomcat 6.x.x) не завершают работу должным образом, если в методе destroy() сервлета есть System.exit().

Вероятно, чтобы предотвратить такое опасное поведение, как это.

Вы не должны писать код, предполагающий, что ваш код/приложение — это единственное, что работает на сервере.

person matt b    schedule 13.02.2009
comment
Ну, я поддерживаю этот код. Кроме того, почти гарантировано, что это будет единственное веб-приложение, работающее на этом Tomcat. - person Nikhil Kashyap; 13.02.2009
comment
+1, System.exit() НИКОГДА не следует вызывать в сервлете ни при каких обстоятельствах. Период. - person David Z; 13.02.2009
comment
@Nikhil - это не объясняет, ПОЧЕМУ ты это делаешь. Вы спросили, хорошая ли это идея, и ответили, что это плохая практика. Возможно, если вы расскажете нам, чего вы пытаетесь достичь, мы сможем помочь вам найти лучшее решение? - person matt b; 13.02.2009
comment
Другими словами, эта ошибка 1053 является побочным эффектом плохой практики, а не истинной причиной ваших проблем. - person matt b; 13.02.2009
comment
См. мой ответ ниже для полного шаблона передовой практики для управления потоками, запущенными сервлетом. - person Jared; 13.02.2009

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

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

Почему Tomcat 5.0.30 и (более поздние версии, включая Tomcat 6.xx) не завершают работу должным образом, если в методе destroy() сервлета есть System.exit().

много кода выполняется после удаления истории сервлета. Уничтожение контекста и всех других его слушателей для одного... других сервлетов. Другие приложения. Сам кот. Вызывая System.exit, вы предотвращаете запуск всего этого.

Лучше спросить, что это за потоки, не являющиеся демонами, почему они работают и кто их запускает?

person James Schek    schedule 13.02.2009
comment
Чтобы ответить на ваш последний вопрос, существует множество потоков, которые выполняют различные задачи, такие как опросы, отчеты и так далее. Сервлет инициализирует эти потоки, а затем они начинают работать. - person Nikhil Kashyap; 13.02.2009

При написании кода остановки потока, как у Джареда, я обычно делаю член "keepGoing" и метод "stopExecuting()" статическими, чтобы все потоки получали сигнал о завершении работы одним вызовом завершения работы. Хорошая идея или нет?

person Community    schedule 13.02.2009
comment
У меня глубоко врожденное отвращение ко всему статичному, особенно когда речь идет о параллелизме. Это не значит, что подход неправильный, или что он не работает, или что он не более эффективен. Мое отвращение к статике, вероятно, чрезмерно. - person Jared; 13.02.2009
comment
На самом деле... на практике я бы обычно реализовывал MyThread как абстрактный класс с doSomeStuff(), вероятно, SleepForABit() и cleanup(), все они были бы абстрактными... поскольку статика не наследуется, это не сработает. - person Jared; 13.02.2009
comment
Отредактировал мой ответ, включив в него абстракцию управления потоками сервлета. - person Jared; 13.02.2009
comment
Я бы оставил переменную keepGoing нестатичной, потому что иногда вам нужно закрыть только один поток. Достаточно просто поместить все ваши дескрипторы потоков в одну коллекцию и просто перебирать ее и останавливать потоки. - person Mr. Shiny and New 安宇; 13.02.2009