Доброго всем времени суток.
Недавно, по долгу службы, появилась необходимость написать 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
}Вот в принципе и все, что хотел рассказать.
Интересно услышать, кто что думает на эту тему.