Доброго всем времени суток.
Недавно, по долгу службы, появилась необходимость написать background process для IBM AIX 5.2 на Java с контролирующим shell скриптом.
Раз работа проделана, документация поднята, код написан, почему бы не поделиться с общественностью? По сему, переходим к делу.
Для того чтобы демонизировать приложение, необходимо отключить его от терминала, в котором происходит его непосредственный запуск. Для этого необходимо выполнить следующие шаги:
Таким образом, минимальная строка запуска должна выглядеть следующим образом:
где "<&-" отключит stdin;
"&" как и говорилось, позволит перевести работу приложения из foreground в background режим.
Код daemon_app должен выглядеть приблизительно следующим образом:
Метод daemonize отключает приложение от stdin и stdout. Единым активным остается поток stderr, который мы можем использовать для логирования ошибок на стадии инициализации.
Метод doProcessing предназначен для реализации основной логики.
Далее мы можем использовать какой-либо фреймворк для логирования, например log4j.
Следующим усовершенствованием будет изменение строки запуска для перехвата данных отправленных нашим процессом в stderr. Для этого, модифицируем строку запуска следующим образом:
где «2>/var/log/daemon_app_error.log» перенаправит вывод с stderr в файл /var/log/daemon_app_error.log.
Если читатель не в курсе, то потоки ввода-вывода в unix shell имеют следующие идентификаторы:
stdin – 0
stdout – 1
stderr – 2
В методе doProcessing мы можем организовать бесконечный цикл с предусловием, по которому будет осуществляться окончание работы процесса. Таким условием может быть SIGTERM отправленный из операционной системы, например, посредством kill -15 <pid>.
Код 15 (SIGTERM) одинаков как для AIX, HP-UX так и для обычной Linux-based системы.
Список сигналов и их кодов можно получить с помощью команды kill -l.
Есть несколько способов обработки сигналов в Java:
Первый метод не очень хорош из за использования классов из пакета sun.misc. Почему?
Вот, что Sun пишет (http://java.sun.com/products/jdk/faq/faq-sun-packages.html) про package sun.*:
2-й метод предусматривает передачу в него в качестве аргумента класс наследованный от Thread. Это значит, что при получении SIGTERM будет создан новый thread, который будет совершать определенные программистом действия для завершения работы процесса в целом.
Обратимся к документации (http://java.sun.com/docs/books/jvms/second_edition/html/Concepts.doc.html#19152), какие же предусловия необходимы, для завершения работы Java программы?
Поэтому:
Можно попросту сделать System.exit(), если Вам не нужно проводить специфические операции по завершению работы программы, закрывая используемые ресурсы и активные соединения.
Модифицированный код с использованием Runtime.getRuntime().addShutdownHook для создания обработчика сигналов прерывания приведен ниже:
Итак, мы имеем метод registerShutdownHook, который вызывается из main и регистрирует обработчик на сигнал прерывания.
При получении сигнала прерывания, вызывается статический метод setShutdownFlag, который меняет значение статического булевого свойства shutdownFlag на true, по значению которого организован цикл в методе doProcessing с предусловием.
Итак, процесс-демон написан. Теперь необходимо создать скрипт управляющий его запуском, остановкой и мониторингом состояния.
Описывать весь процесс создания shell control скрипта я не буду, приведу только несколько полезных процедур.
Процедура без аргументов. Перебираем необходимые переменные окружения циклом for. Если переменной нет, выводим предупреждение. Если хотя бы одна переменная не установлена, прекращаем выполнение с кодом ошибки 1.
Аргументом этой процедуры является строка с classpath, в котором элементы отделены двоеточием.
Заменяем двоеточие пробельным символом — в результате получаем возможность проверить каждый элемент.
Данная процедура предусматривает наличие установленных переменных — PID_DIR, PROCESS_NAME, CPATH
где
PID_DIR – директория для хранения pid файлов
PROCESS_NAME — имя процесса
CPATH — строка с classpath
Идентификатор процесса запущенного в background'е, можно определить с помощью shell-переменной «$!», который впоследствии записывается в pid файл.
После чего происходит проверка на протяжении 5 секунд, или процесс «не упал» в процессе запуска. Каждую секунду происходит проверка с помощью ps -p статус процесса.
Приведенная реализация принимает 2 аргумента на вход:
Процедура посылает процессу SIGTERM и после 5 секунд ожидания, если процесс не остановлен, посылает ему SIGKILL, после чего удаляет pid файл.
Принимает 1 аргумент — абсолютный путь к pid файлу
Возвращает значение отличное от 0, если процесс запущен и работает, 0 — если процесс неактивен.
Вот в принципе и все, что хотел рассказать.
Интересно услышать, кто что думает на эту тему.
Недавно, по долгу службы, появилась необходимость написать background process для IBM AIX 5.2 на Java с контролирующим shell скриптом.
Раз работа проделана, документация поднята, код написан, почему бы не поделиться с общественностью? По сему, переходим к делу.
1 Демонизация
Для того чтобы демонизировать приложение, необходимо отключить его от терминала, в котором происходит его непосредственный запуск. Для этого необходимо выполнить следующие шаги:
- Отключить stdin от терминала
- Запустить процесс в background'e путем указания амперсанда «&» в конце строки запуска
- Закрыть stdin, stdout непосредственно в приложении (в нашем случае, для Java, это будет System.in.close(); System.out.close();)
Таким образом, минимальная строка запуска должна выглядеть следующим образом:
java daemon_app <&- &
где "<&-" отключит stdin;
"&" как и говорилось, позволит перевести работу приложения из foreground в background режим.
Код daemon_app должен выглядеть приблизительно следующим образом:
public class daemon_app { public static int main(String[] args) { try { daemonize(); } catch (Throwable e) { System.err.println("Startup failed. " + e.getMessage()); return 1; } doProcessing(); return 0; } static private void daemonize() throws Exception { System.in.close(); System.out.close(); } static private void doProcessing() { //Do some processing } }
Метод daemonize отключает приложение от stdin и stdout. Единым активным остается поток stderr, который мы можем использовать для логирования ошибок на стадии инициализации.
Метод doProcessing предназначен для реализации основной логики.
Далее мы можем использовать какой-либо фреймворк для логирования, например log4j.
Следующим усовершенствованием будет изменение строки запуска для перехвата данных отправленных нашим процессом в stderr. Для этого, модифицируем строку запуска следующим образом:
java daemon_app <&- 2>/var/log/daemon_app_error.log &
где «2>/var/log/daemon_app_error.log» перенаправит вывод с stderr в файл /var/log/daemon_app_error.log.
Если читатель не в курсе, то потоки ввода-вывода в unix shell имеют следующие идентификаторы:
stdin – 0
stdout – 1
stderr – 2
2 Организация обработки сигналов прерывания
В методе doProcessing мы можем организовать бесконечный цикл с предусловием, по которому будет осуществляться окончание работы процесса. Таким условием может быть SIGTERM отправленный из операционной системы, например, посредством kill -15 <pid>.
Код 15 (SIGTERM) одинаков как для AIX, HP-UX так и для обычной Linux-based системы.
Список сигналов и их кодов можно получить с помощью команды kill -l.
Есть несколько способов обработки сигналов в Java:
- Использование sun.misc.Signal и sun.misc.SignalHandler с последующим созданием собственного класса-обработчика, который имплементирует sun.misc.SignalHandler. Более детальную информацию по данному методу вы можете найти тут (http://www.ibm.com/developerworks/ibm/library/i-signalhandling/)
- Использование метода Runtime.getRuntime().addShutdownHook. (сигнатура метода: public void addShutdownHook(Thread hook))
Первый метод не очень хорош из за использования классов из пакета sun.misc. Почему?
Вот, что Sun пишет (http://java.sun.com/products/jdk/faq/faq-sun-packages.html) про package sun.*:
The sun.* packages are not part of the supported, public interface.
A Java program that directly calls into sun.* packages is not guaranteed to work on all Java-compatible platforms. In fact, such a program is not guaranteed to work even in future versions on the same platform…
2-й метод предусматривает передачу в него в качестве аргумента класс наследованный от Thread. Это значит, что при получении SIGTERM будет создан новый thread, который будет совершать определенные программистом действия для завершения работы процесса в целом.
Обратимся к документации (http://java.sun.com/docs/books/jvms/second_edition/html/Concepts.doc.html#19152), какие же предусловия необходимы, для завершения работы Java программы?
The Java virtual machine terminates all its activity and exits when one of two things happens:
- All the threads that are not daemon threads (§2.19) terminate.
- Some thread invokes the exit method of class Runtime or class System, and the exit operation is permitted by the security manager.
Поэтому:
- Если у нас есть запущенные не демон-нити, необходимо обеспечить их корректное завершение и присоединить их к main, используя join. Демон-thread'ы, т.е. те, для которых было выполнено setDaemon(true), останавливать не обязательно.
- Остановить работу thread main, что подразумевает выход из main метода запускаемого класса.
Можно попросту сделать System.exit(), если Вам не нужно проводить специфические операции по завершению работы программы, закрывая используемые ресурсы и активные соединения.
Модифицированный код с использованием Runtime.getRuntime().addShutdownHook для создания обработчика сигналов прерывания приведен ниже:
public class daemon_app { static private boolean shutdownFlag = false; public static int main(String[] args) { try { daemonize(); } catch (Throwable e) { System.err.println("Startup failed. " + e.getMessage()); return 1; } registerShutdownHook(); doProcessing(); return 0; } static private void doProcessing() { while (false == shutdownFlag) { //Do some processing } } static public void setShutdownFlag() {shutdownFlag = true;} private static void registerShutdownHook() { Runtime.getRuntime().addShutdownHook( new Thread() { public void run() { daemon_app.setShutdownFlag(); } } ); } static private void daemonize() throws Exception { System.in.close(); System.out.close(); } }
Итак, мы имеем метод registerShutdownHook, который вызывается из main и регистрирует обработчик на сигнал прерывания.
При получении сигнала прерывания, вызывается статический метод setShutdownFlag, который меняет значение статического булевого свойства shutdownFlag на true, по значению которого организован цикл в методе doProcessing с предусловием.
3 Контроллирующий скрипт
Итак, процесс-демон написан. Теперь необходимо создать скрипт управляющий его запуском, остановкой и мониторингом состояния.
Описывать весь процесс создания shell control скрипта я не буду, приведу только несколько полезных процедур.
Пример проверки переменных окружения необходымых для запуска/работы процесса
Процедура без аргументов. Перебираем необходимые переменные окружения циклом for. Если переменной нет, выводим предупреждение. Если хотя бы одна переменная не установлена, прекращаем выполнение с кодом ошибки 1.
check_env() { exit_flag=0 for env_var in JAVA_HOME ORACLE_HOME TUXEDO_HOME ; do eval "env_value=\$$env_var" if [ -z "$env_value" ] then echo "ERROR: environment variable '$env_var' is not set" exit_flag=1 fi done if [ $exit_flag -eq 1 ] then echo "Exiting. No process started" exit 1 fi }
Проверка элементов classpath
Аргументом этой процедуры является строка с classpath, в котором элементы отделены двоеточием.
Заменяем двоеточие пробельным символом — в результате получаем возможность проверить каждый элемент.
check_classpath() { #Checking files in classpath are exists and readable for resource in `echo $1 | sed -e "s/:/ /g"`; do if [ ${#resource} -gt 0 ] && [ ! -r $resource ] # if file not exists or not readable then echo "WARNING: Resource '$resource' included in CLASSPATH does not exist or not readable" fi done }
Пример процедуры запуска процесса
Данная процедура предусматривает наличие установленных переменных — PID_DIR, PROCESS_NAME, CPATH
где
PID_DIR – директория для хранения pid файлов
PROCESS_NAME — имя процесса
CPATH — строка с classpath
Идентификатор процесса запущенного в background'е, можно определить с помощью shell-переменной «$!», который впоследствии записывается в pid файл.
После чего происходит проверка на протяжении 5 секунд, или процесс «не упал» в процессе запуска. Каждую секунду происходит проверка с помощью ps -p статус процесса.
launch_daemon() { JVM_OPTS="" # Set max memory to 1G JVM_OPTS="$JVM_OPTS -Xmx1G" echo "Starting process \c" # Run process in background with closed input stream, to detach it from terminal $JAVA_HOME/bin/java $JVM_OPTS -cp $CPATH daemon_app <&- 2>/var/log/$PROCESS_NAME.pid & #Write pid to pid file echo $! > $PID_DIR/$PROCESS_NAME.pid if [ ${#!} -eq 0 ] then echo "... [ failed ]" else # Checking for 5 seconds if process is alive timer=0 while [ $timer -lt 6 ] do echo ".\c" sleep 1 timer=$timer+1 done if [ `ps -p $! | wc -l` -gt 1 ] then echo " [ started ]" exit 0 else echo " [ failed ]" exit 1 fi fi }
Пример процедуры остановки процесса
Приведенная реализация принимает 2 аргумента на вход:
- id процесса
- абсолютный путь к pid файлу
Процедура посылает процессу SIGTERM и после 5 секунд ожидания, если процесс не остановлен, посылает ему SIGKILL, после чего удаляет pid файл.
stop_daemon() { echo "Stopping process \c" timer=0 if [ `ps -p $1 | wc -l` -eq 1 ] then echo " not running" else kill -TERM $1 while [ `ps -p $1 | wc -l` -gt 1 ] do if [ $timer -gt 5 ] then kill -KILL $1 timer=0 else echo ".\c" sleep 1 fi timer=$timer+1 done echo " stopped " fi rm $2 }
Пример процедуры проверки статуса процесса
Принимает 1 аргумент — абсолютный путь к pid файлу
Возвращает значение отличное от 0, если процесс запущен и работает, 0 — если процесс неактивен.
check_running() { if [ -e $1 ] then fpid=`cat $1` if [ ${#fpid} -gt 0 ] then lines=`ps -p $fpid | wc -l` echo $(($lines-1)) else echo 0 fi else echo 0 fi }
Вот в принципе и все, что хотел рассказать.
Интересно услышать, кто что думает на эту тему.