Pull to refresh

Создание демон-процесса на Java

Reading time6 min
Views36K
Доброго всем времени суток.

Недавно, по долгу службы, появилась необходимость написать background process для IBM AIX 5.2 на Java с контролирующим shell скриптом.

Раз работа проделана, документация поднята, код написан, почему бы не поделиться с общественностью? По сему, переходим к делу.



1 Демонизация



Для того чтобы демонизировать приложение, необходимо отключить его от терминала, в котором происходит его непосредственный запуск. Для этого необходимо выполнить следующие шаги:
  1. Отключить stdin от терминала
  2. Запустить процесс в background'e путем указания амперсанда «&» в конце строки запуска
  3. Закрыть 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.

Поэтому:
  1. Если у нас есть запущенные не демон-нити, необходимо обеспечить их корректное завершение и присоединить их к main, используя join. Демон-thread'ы, т.е. те, для которых было выполнено setDaemon(true), останавливать не обязательно.
  2. Остановить работу 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
}


Вот в принципе и все, что хотел рассказать.

Интересно услышать, кто что думает на эту тему.
Tags:
Hubs:
Total votes 37: ↑34 and ↓3+31
Comments26

Articles