Разветвление дочерних процессов на события файловой системы в perl

У меня есть процесс демона, написанный на Perl, который использует Inotify2 для просмотра каталогов для входящих файлов. По прибытии каждого файла демон запускает дочерний процесс. Теперь кажется, что слишком много файлов поступает одновременно (следовательно, слишком много разветвлений), потому что я получил эту ошибку в своем файле журнала:

Cannot allocate memory at notifyd.pl line ...

который является результатом fork().

В основном у меня есть следующий код:

    my $inotify = new Linux::Inotify2() or die($!);

    foreach my $k (@PATHS) {
        $inotify->watch($k,
IN_MOVE_SELF|IN_DELETE_SELF|IN_CLOSE_WRITE, \&watcher) or die($!);
    }

    $inotify->blocking(1) or die($!);

    for(;;) {
        $inotify->poll() or die($!);
    }

с функцией наблюдателя, выполняющей fork, а затем execv:

sub watcher {
        my $e = shift;
        my $pid = fork();
        if(!defined $pid) {
            print "[ERROR]", $!;
        }
        elsif($pid == 0) {
            my @args = ($e->fullname, $e->mask);
            exec($childprocess, @args) or die($!);
        }
}

Я не могу позволить себе пропустить события, не разветвляя процесс.

Есть ли у кого-нибудь предложения, как я могу улучшить это и убедиться, что вилка не выйдет из строя?


Изменить: похоже, что дочерние процессы становились зомби после выхода, поскольку демон не отвечал на SIGCHLD. Таким образом, причиной сбоя fork() могло быть множество дочерних процессов-зомби. Теперь демон выполняет $SIG{CHLD} = 'IGNORE'; перед разветвлением.


person Nikolay Spassov    schedule 22.03.2012    source источник
comment
Если у вас много изменений в файлах, довольно легко исчерпать ресурсы, и в зависимости от вашего варианта использования это вектор для атак DOS. Вы должны обрабатывать эти события с помощью одного из циклов обработки событий на cpan. Хорошей отправной точкой может стать AnyEvent.   -  person matthias krull    schedule 22.03.2012
comment
Вы уверены, что fork() не работает? Сообщение об ошибке, которое вы (частично) представляете, выглядит так, как будто оно пришло из die или warn, но код, который вы показываете, не выдаст этот вывод, если вилка не удалась (то есть, если $pid не определено).   -  person pilcrow    schedule 22.03.2012
comment
Да, из этой части кода пришло сообщение: if(!defined $pid) { print [ERROR], $!; }   -  person Nikolay Spassov    schedule 22.03.2012
comment
Э-э, но мы не видим токен [Ошибка], и ни $!, ни print обычно не добавляются к строке script независимо. Ошибка, которую вы показываете, в той форме, в которой вы ее показываете, возникла не из этой строки кода.   -  person pilcrow    schedule 22.03.2012


Ответы (2)


Решите проблему, добавив еще один уровень косвенности.

При получении события поместите имя файла в очередь заданий. Очередь запускает новое задание, обрабатывая файл, когда ресурсы достаточно свободны; эта схема гарантирует, что действие в конечном итоге будет выполнено, но не сразу.

person daxim    schedule 22.03.2012
comment
Спасибо, мне нужно изучить возможные способы его реализации. Дело в том, что я бы не хотел иметь еще один процесс только для работы с очередью. - person Nikolay Spassov; 22.03.2012

Используйте более надежный диспетчер фоновых процессов, например Forks::Super.

Например, в этой установке одновременно будет работать до 10 вилок. Новые запросы, поступающие, когда все 10 форков заняты, будут поставлены в очередь. Задания в очереди будут выполняться по мере завершения других фоновых процессов и освобождения ресурсов.

use Forks::Super  MAX_PROC => 10, ON_BUSY => 'queue';

...

sub watcher {
    my $e = shift;
    fork {
       sub => sub {
           my @args = ($e->fullname, $e->mask);
           exec($childprocess, @args) or die($!);
       }
    };
}
person mob    schedule 22.03.2012
comment
Спасибо. Выглядит хорошо, за исключением того, что я не смогу установить этот модуль, поэтому мне придется реализовать что-то подобное самостоятельно. - person Nikolay Spassov; 22.03.2012