Статьи

Heroku и Java — от новичка до новичка, часть 2

Эта проблема

Так что через несколько дней я смог вернуться к своему маленькому проекту Recaps. Я начал с проверки логов и нашел что-то вроде этого:

1
2
3
4
2012-03-04T01:52:51+00:00 heroku[web.1]: Idling
2012-03-04T01:52:53+00:00 heroku[web.1]: Stopping process with SIGTERM
2012-03-04T01:53:03+00:00 heroku[web.1]: Error R12 (Exit timeout) -> Process failed to exit within 10 seconds of SIGTERM
2012-03-04T01:53:03+00:00 heroku[web.1]: Stopping process with SIGKILL

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

Решение

Это кажется простой проблемой. Я начал с того, что нашел, что это за SIGTERM. Я знал, что это сигнал от Linux, я просто хотел знать, что на самом деле делает Heroku. Поэтому иногда Heroku просто отправляет SIGTERM в ваш процесс, чтобы ему было разрешено корректно завершить работу. Это очень просто.

Как я уже упоминал в своем последнем посте, я решил использовать Jetty вместо Grizzly. Сначала я решил использовать jetty-runner для запуска моего веб-приложения, и оно работало нормально, ресурсы сканировались при запуске сервлета Джерси. Развертывание в Heroku также было простым, и с измененным Procfile приложение запустилось.

Тем не менее приложение не реагировало правильно на SIGTERM, поэтому, не углубляясь в настройку Jetty-Runner, я решил просто использовать встроенный сервер Jetty. Это очень просто, и запуск мастера запускает приложение. Поэтому без дальнейшего рассмотрения я просто развернул измененное приложение в Heroku. Чтобы проверить, появляется ли ошибка снова, после первого запуска я просто перезагрузил heroku и подключился к логам в другом терминале. Но сообщение об ошибке тайм-аута выхода все еще было. Моя ошибка там — я не проверял приложение, будет ли оно правильно работать при использовании мастера. Итак, снова запускается мастер, а затем ctrl + c, чтобы посмотреть, что произойдет (позже я попытался выполнить команду kill -s TERM и получил аналогичный вывод):

01
02
03
04
05
06
07
08
09
10
11
12
pbu@pbudesk ~/recaps $ foreman start
21:57:27 web.1     | started with pid 9603
21:57:27 web.1     | 0    [main] INFO  org.eclipse.jetty.server.Server  - jetty-8.1.1.v20120215
21:57:27 web.1     | 110  [main] INFO  org.eclipse.jetty.webapp.StandardDescriptorProcessor  - NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet
21:57:27 web.1     | 132  [main] INFO  org.eclipse.jetty.server.handler.ContextHandler  - started o.e.j.w.WebAppContext{/,file:/home/pbu/Devel/IdeaProjects/recaps/webmodule/src/main/webapp/},webmodule/src/main/webapp
21:57:27 web.1     | 133  [main] INFO  org.eclipse.jetty.server.handler.ContextHandler  - started o.e.j.w.WebAppContext{/,file:/home/pbu/Devel/IdeaProjects/recaps/webmodule/src/main/webapp/},webmodule/src/main/webapp
21:57:27 web.1     | 183  [main] INFO  org.eclipse.jetty.server.AbstractConnector  - Started SelectChannelConnector@0.0.0.0:5000
^CSIGINT received
21:57:57 system    | sending SIGTERM to all processes
21:57:57 system    | sending SIGTERM to pid 9603
21:57:57 web.1     | process terminated
pbu@pbudesk ~/recaps $

Хорошо, поэтому, когда мастер получает SIGINT, он отправляет SIGTERM всем процессам, круто — вероятно, династии Heroku ведут себя одинаково. Тем не менее, это было не изящное завершение работы, но в Jetty есть раздел изящного завершения работы, в котором упоминаются два приятных свойства: gracefulShutdown и stopAtShutdown. Модифицированный класс выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public class Serve {
    public static void main(String[] args) throws Exception {
        int port = Integer.valueOf(System.getenv("PORT"));
 
        Server jetty = new Server(port);
 
        WebAppContext context = new WebAppContext();
        context.setContextPath("/");
        String webapp = "webmodule/src/main/webapp";
        context.setWar(webapp);
        context.setResourceBase(webapp);
 
        jetty.setHandler(context);
 
        jetty.setGracefulShutdown(1000);
        jetty.setStopAtShutdown(true);
 
        jetty.start();
        jetty.join();
    }
}

Повторный запуск мастера и использование ctrl + c доказывает, что это работает! Большой!

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
pbu@pbudesk ~/recaps $ foreman start
22:11:47 web.1     | started with pid 9863
22:11:47 web.1     | 0    [main] INFO  org.eclipse.jetty.server.Server  - jetty-8.1.1.v20120215
22:11:47 web.1     | 110  [main] INFO  org.eclipse.jetty.webapp.StandardDescriptorProcessor  - NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet
22:11:47 web.1     | 131  [main] INFO  org.eclipse.jetty.server.handler.ContextHandler  - started o.e.j.w.WebAppContext{/,file:/home/pbu/Devel/IdeaProjects/recaps/webmodule/src/main/webapp/},webmodule/src/main/webapp
22:11:47 web.1     | 132  [main] INFO  org.eclipse.jetty.server.handler.ContextHandler  - started o.e.j.w.WebAppContext{/,file:/home/pbu/Devel/IdeaProjects/recaps/webmodule/src/main/webapp/},webmodule/src/main/webapp
22:11:48 web.1     | 183  [main] INFO  org.eclipse.jetty.server.AbstractConnector  - Started SelectChannelConnector@0.0.0.0:5000
^C22:11:49 web.1     | 1969 [Thread-1] INFO  org.eclipse.jetty.server.Server  - Graceful shutdown SelectChannelConnector@0.0.0.0:5000
22:11:49 web.1     | 1970 [Thread-1] INFO  org.eclipse.jetty.server.Server  - Graceful shutdown o.e.j.w.WebAppContext{/,file:/home/pbu/Devel/IdeaProjects/recaps/webmodule/src/main/webapp/},webmodule/src/main/webapp
SIGINT received
22:11:49 system    | sending SIGTERM to all processes
22:11:49 system    | sending SIGTERM to pid 9863
22:11:50 web.1     | 2982 [Thread-1] INFO  org.eclipse.jetty.server.handler.ContextHandler  - stopped o.e.j.w.WebAppContext{/,file:/home/pbu/Devel/IdeaProjects/recaps/webmodule/src/main/webapp/},webmodule/src/main/webapp
22:11:50 web.1     | process terminated
pbu@pbudesk ~/recaps $
So off to deploy it to the cloud! Again, deploy, heroku restart and watch logs... but it doesn't work.

Другой путь

После первоначального сбоя я попробовал другой подход. Я узнал, что вы можете зарегистрировать хуки отключения — очень простая вещь. Для этого просто зарегистрируйте новый поток с помощью метода Runtime.getRuntime (). AddShutdownHook (Thread):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Serve {
    public static void main(String[] args) throws Exception {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.out.println("Shutting down by shutdown hook");
            }
        });
        int port = Integer.valueOf(System.getenv("PORT"));
 
        Server jetty = new Server(port);
 
        WebAppContext context = new WebAppContext();
        context.setContextPath("/");
        String webapp = "webmodule/src/main/webapp";
        context.setWar(webapp);
        context.setResourceBase(webapp);
 
        jetty.setHandler(context);
 
        jetty.setGracefulShutdown(1000);
        jetty.setStopAtShutdown(true);
 
        jetty.start();
        jetty.join();
    }
}

Финальный тест с прорабом доказывает, что он тоже работает, но в очередной раз не работает на Heroku.

На данный момент я понятия не имею, как избавиться от этого тайм-аута. Это не очень важно, я просто хотел проверить, могу ли я как-то на это отреагировать, но безрезультатно. Пока, я думаю, я просто свяжусь с Хероку, может, они помогут. Другим вариантом может быть попытка встроенного Tomcat, но, возможно, позже. На данный момент у меня есть другие дела, такие как проверка Jelastic .