пятница, 28 ноября 2014 г.

Перезапуск падучего приложения из самого приложения (спасение утопающих ...)

Представьте себе ситуацию, когда у вас есть некоторое серверное приложение, которое потенциально может падать (я не буду рассуждать о причинах: допустим, это новое, написанное вами приложение, которое вы хотите считать абсолютно надежным, но это не так). Предположим, что вам ни в коем случае нельзя допускать простоя службы, реализуемой этим приложением. Есть разные способы следить за работой программ и перезапускать их в случае надобности извне (например, cron). Но … Спасение утопающих — дело рук самих утопающих. Именно этот подход я и хочу продемонстрировать в самых общих чертах. Разумеется, вы должны обладать исходным кодом программы. В этом случае достаточно выделить исходный полезный код в отдельный дочерний процесс (worker), а исходный процесс превратить в надсмотрщика (master или supervisor), который будет перезапускать полезный дочерний процесс в случае его падения. Вот код, а пояснения ниже.
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <iostream>

#ifdef MAXCYCLES
#define LOOPSTOPCOND i < MAXCYCLES
#else
#define LOOPSTOPCOND
#endif


int  main( int  argc, char **  argv )
{
    int  pid( 0 );

    std::cout << "Master: " << getpid() << std::endl;

    for ( int  i( 0 ); LOOPSTOPCOND; ++i )
    {
        if ( ( pid = fork() ) == 0 )    /* Worker process */
        {
            std::cout << "(cycle " << i << ") Worker: " << getpid() <<
                    std::endl;

            /* do segv if option -s was specified in command line */
            if ( argc > 1 && ! strcmp( argv[ 1 ], "-s" ) )
                int  a( *( ( int * )0 ) );

            break;
        }
        else                            /* Master process */
        {
            if ( pid < 0 )
            {
                std::cout << "Failed to fork a worker process, exiting" <<
                        std::endl;
                break;
            }

            int   status( 0 );
            bool  respawn( false );

            waitpid( pid, &status, 0 );

            if ( ! WIFSIGNALED( status ) )
                break;

            int  sig( WTERMSIG( status ) );

            std::cout << "Worker process was signaled " << sig <<  std::endl;

            switch ( sig )
            {
            case SIGSEGV:
                respawn = true;
            default:
                break;
            }

            if ( ! respawn )
                break;
        }
    }

    return 0;
}
Прежде всего нужно отметить, что код написан на C++, хотя ничто не мешает переписать его на C, если понадобится. Макросы MAXCYCLES и LOOPSTOPCOND нужны здесь только в демонстрационных целях: дабы ограничить число перезапусков воркеров величиной, заданной во время компиляции (MAXCYCLES). В релизной версии приложения цикл for скорее всего примет вид for ( ; ; ), так что эти макросы окажутся не нужны. Итак, цикл for нужен для перезапуска воркер-процессов в случае их падения. Внутри цикла for с помощью вызова fork() производится расщепление основного процесса, связанного с приложением, на две части — новый процесс (воркер) и старый (мастер). Как известно, fork() возвращает 0 в новом процессе, и какое-либо значение большее нуля — в старом. В новом процессе (блок после if ( ( pid = fork() ) == 0 )) мы выводим сообщение о старте воркера, вызываем его полезный код (в этом примере полезный код присутствует только в случае, когда в программу была передана опция командной строки -s: он приводит к посылке ядром сигнала SIGSEGV из-за разыменования нулевого указателя), и в конце обязательно ставим break;, поскольку воркер по-прежнему находится в цикле, и без его прерывания продолжит запускать новые воркеры, притворившись мастером! Внутри кода, относящегося к мастер-процессу (блок после else), прежде всего проверяется, что вызов функции fork() завершился успешно. Затем мастер ожидает завершения воркера с помощью вызова waitpid() и проверяет, каким образом тот завершился. Макрос WIFSIGNALED() позволяет установить, был ли воркер остановлен сигналом ядра или вышел самостоятельно. Во втором случае цикл for прерывается и мастер завершает свою работу. В случае, если воркер был остановлен сигналом, мы хотим узнать каким именно. Для этого предназначен макрос WTERMSIG(). Если это был сигнал SIGSEGV, то локальной переменной respawn присваивается истинное значение. Если ее значение остается ложным (когда воркер был прерван любым другим сигналом), то цикл завершается. Давайте посмотрим, как это работает (исходный файл я назвал main.cpp). Компилируем.
g++ -g -DMAXCYCLES=4 -o test main.cpp
Я ограничил число перезапусков четырьмя. Запускаем программу с нормальным завершением воркера.
./test 
Master: 6061
(cycle 0) Worker: 6062
Завершился воркер — завершился мастер. А теперь запустим программу с падучим воркером.
./test -s
Master: 6085
(cycle 0) Worker: 6086
Worker process was signaled 11
(cycle 1) Worker: 6152
Worker process was signaled 11
(cycle 2) Worker: 6154
Worker process was signaled 11
(cycle 3) Worker: 6156
Worker process was signaled 11
Мастер-процесс четыре раза перезапустил упавшие воркер-процессы, как и ожидалось. Я не стал касаться вопросов наследования ресурсов мастера при инициализации воркера и корректного завершения воркера при получении мастером сигнала прерывания — это отдельные интересные темы.

Комментариев нет:

Отправить комментарий