Демоны — сигналы

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

Внимание! В статье требуется расширение php pcntl

Сигналы — введение

Отправка сигналов демону, как и любой другой программе в unix системах, выполняется совершенно нелогичной командой «kill».

И для нас очень важно что бы программа могла правильно отработать своё «убиение». Завершить все незавершённые дела, или оставить их другому демону.

Убийство происходит посредством передачи программе kill специального системного сигнала, например SIGTERM и идентификатора процесса PID. Если вызывать команду kill без параметров, по-умолчанию будет подставляться параметр SIGTERM.

В Unix системах есть целая куча сигналов и в наших силах перехватить почти все сигналы, разве что, кроме SIGKILL.

В терминале можно набрать команду ps, она покажет все наши процессы, и там мы можем получить PID запущенного демона:

ukko@u:/www/daemons$ ps
 PID TTY          TIME CMD
 2739 pts/0    00:00:00 zsh
 2888 pts/0    00:00:02 php
 2889 pts/0    00:00:00 ps

После чего, мы можем набрать команду kill с параметрами для отправки нашему демону сигнала корректного завершения работы

ukko@u:/www/daemons$ kill -SIGINT 2888
ukko@u:/www/daemons$ ps
 PID TTY          TIME CMD
 2739 pts/0    00:00:00 zsh
 2951 pts/0    00:00:00 ps

как видите, тут всё просто. Теперь нам нужно только научиться перехватывать эти сигналы и начать их обрабатывать.

Обработка сигналов

Давайте посмотрим на стандартный пример работы с сигналами, с сайта php.net.

В коде должно быть всё понятно и без объяснений

    // Когда мы используем сигналы, эту штуку включать нужно обязательно.
    // Без неё работать не будет. Подробнее тут http://php.net/manual/en/control-structures.declare.php
    declare(ticks = 1);

    /**
     * Функция перехватывающая сигналы
     */
    function sig_handler($signo)
    {

        switch ($signo) 
        {
            case SIGTERM:
                // Обычное завершение работы
                exit;
                break;
            case SIGHUP:
                // Требуется перезапуск
                break;
            case SIGUSR1:
                echo "Пользовательский сигнал SIGUSR1...\n";
                break;
            default:
                // Сюда попадают все остальные сигналы
        }
    }

    echo "Устанавливаем функцию для перехвата сигнала...\n";

    pcntl_signal(SIGTERM, "sig_handler");
    pcntl_signal(SIGHUP,  "sig_handler");
    pcntl_signal(SIGUSR1, "sig_handler");

    echo "Генерируем сигнал SIGTERM...\n";

    // Отправляем сигнал текущему процессу
    posix_kill(posix_getpid(), SIGUSR1);

    echo "Готово\n";

Давайте попробуем теперь встроить сигналы в наш простейший демон:

    /**
     * @link 
     */
    declare(ticks = 1);
    
    $stop = FALSE;

    /**
     * Функция перехватывающая сигналы
     */
    function sig_handler($signo)
    {
        global $stop;
        switch ($signo) 
        {
            case SIGTERM:
                echo "Закрываем какие-нибудь соединения с базой\n";
                echo "Для примера, ждём 5 секунд, и устанавливаем флаг завершения работы\n";
                sleep(5);
                $stop = TRUE;
                echo "Время прошло\n";
                break;
            case SIGUSR1:
                echo "Привет, ты вызвал пользовательский сигнал\n";
                break;
            default:
                // Ловим все остальные сигналы
        }
    }

    // Регистрируем сигналы
    pcntl_signal(SIGTERM, "sig_handler");
    pcntl_signal(SIGUSR1, "sig_handler");


    // Форкаем процесс
    $pid = pcntl_fork();

    if ($pid == -1) 
    {
        // Ошибка 
        die('could not fork'.PHP_EOL);
    } 
    else if ($pid) 
    {
        // Родительский процесс, убиваем
        die('die parent process'.PHP_EOL);
    } 
    else 
    {
        // Новый процесс, запускаем главный цикл
        while( ! $stop ) 
        {
            // Тут выполняется какая-то важная работа
        }
    }
    // Отцепляемся от терминала
    posix_setsid();

Теперь пробуем всё это запустить и понять что там происходит:

Сперва проверяем что у нас нет запущенных инстансов php

ukko@u:/www/daemons$ ps
 PID TTY          TIME CMD
 3892 pts/4    00:00:00 zsh
 5640 pts/4    00:00:00 ps

Запускаем наш демон и проверяем что он запущен

ukko@u:/www/daemons$ php daemon-signal.php 
die parent process

ukko@u:/www/daemons$ ps
  PID TTY          TIME CMD
 3892 pts/4    00:00:00 zsh
 5644 pts/4    00:00:44 php
 5646 pts/4    00:00:00 ps

Отправляем демону пользовательский сигнал. Заметьте, что работа демона не завершается!

ukko@u:/www/daemons$ kill -SIGUSR1 5644
ukko@u:/www/daemons$ Привет, ты вызвал пользовательский сигнал

А теперь корректно завершаем работу демона, ждём 5 секунд и проверяем что демон завершил свою работу

ukko@u:/www/daemons$ kill 5644         
Закрываем какие-нибудь соединения с базой
Для примера, ждём 5 секунд, и устанавливаем флаг завершения работы                                                                                                                             
ukko@u:/www/daemons$ Время прошло
ukko@u:/www/daemons$ ps
  PID TTY          TIME CMD
 3892 pts/4    00:00:00 zsh
 5652 pts/4    00:00:00 ps

Теперь, можно смело сказать что мы освоили основную работу с сигналами.