AnyEvent::Fork, как дождаться ребенка

Я смотрю на модуль AnyEvent::Fork. У меня есть 20 внешних скриптов, которые я хотел бы вызывать параллельно (по 6 за раз) и обобщать их вывод позже, когда все будет готово. Я в недоумении, как этого добиться.

Пример кода (который вызывает только 1 дочерний элемент) в модуле имеет проблему. Я просто добавляю в код простой сон, чтобы не возвращаться сразу, и родитель сразу выходит, не дожидаясь дочернего процесса.

open my $output, ">/tmp/log" or die "$!";

AnyEvent::Fork
   ->new
   ->eval ('
        # compile a helper function for later use
        sub run {
           my ($fh, $output, @cmd) = @_;

           # perl will clear close-on-exec on STDOUT/STDERR
           open STDOUT, ">&", $output or die;
           open STDERR, ">&", $fh or die;

           ### Added by me to demonstrate that
           ### $cv->recv returns immediately.
           sleep 5;

           exec @cmd;
        }
     ')
   ->send_fh ($output)
   ->send_arg ("/bin/echo", "hi")
   ->run ("run", my $cv = AE::cv);

my $stderr = $cv->recv;

В результате /tmp/log пусто. Я не понимаю, как здесь используется condvar, этого нет в документации. Могу ли я получить количество бегущих детей, используя condvar?

Пожалуйста, помогите, как сделать это правильно.

ОБНОВЛЕНИЕ Основная проблема здесь заключается в том, что родитель не ждет завершения дочернего процесса.


person cstamas    schedule 26.11.2014    source источник
comment
(Я могу воспроизвести проблему, и ничего очевидного не приходит в голову. У меня нет времени искать дальше. Интересен тот факт, что возвращаемое значение — это имя $stderr. Возможно, это не сигнализирует об окончании процесса.)   -  person ikegami    schedule 26.11.2014
comment
Бьюсь об заклад, это не удается, потому что у вас нет вызова функции run для запуска. Извините, я не знаю, как использовать этот модуль, и у меня нет времени, чтобы выяснить это прямо сейчас.   -  person ikegami    schedule 26.11.2014
comment
Отличается ли этот API от AnyEvent->condvar? Здесь при запуске вашего кода у меня появляется /tmp/log через 5 секунд. Какого поведения вы ожидаете от тестового сценария?   -  person G. Cito    schedule 26.11.2014
comment
@G.Cito /tmp/log для меня пуст. Икегами подтвердил это.   -  person cstamas    schedule 26.11.2014
comment
Не знаю, как продолжить в чате, но просто для информации: perl5-16.3 FreeBSD AnyEvent::Fork 1.2 , AnyEvent 7.07 ... perl, скомпилированный с -Dusethreads=y ... for ((i = 0; i < 42; i++)) ; do perl ae-fork.pl ;done Используя ваш код, но с переключением ">>/tmp/log" и ("/bin/echo", "hi from $$") ... я получаю файл журнала из 42 строк с PID   -  person G. Cito    schedule 26.11.2014
comment
у меня дебиан перл 5.20.1-2 амд64   -  person cstamas    schedule 27.11.2014
comment
Сварил установку 5.20.1, чтобы попробовать, и все заработало, как указано выше. Поскольку @ikegami подтверждает такое же поведение, вы видите очевидные ошибки, которые, как мне кажется, могут объяснить, что это неприменимо (Unix и разрешения?). Это выяснится.   -  person G. Cito    schedule 27.11.2014
comment
@ГРАММ. Cito, проблема не в том, что дочерний не заспавнится, вопрос в том, как дождаться окончания дочернего?   -  person ikegami    schedule 27.11.2014
comment
@cstamas, Вы сказали, что программа не ждала завершения работы ребенка, и я это подтвердил. /tmp/log не будет пустым, если вы подождете 5 секунд.   -  person ikegami    schedule 27.11.2014
comment
как выглядела рабочая версия вашего кода? (вы можете показать это в разделе EDIT). Вы просто заменили аргументы ->run()?   -  person G. Cito    schedule 17.12.2014


Ответы (1)


Проблема здесь в том, что родительский и дочерний процессы являются отдельными процессами и могут работать независимо друг от друга, поэтому, если один должен ждать другого, требуется явная синхронизация. Есть много способов сделать это, самым простым было бы использовать AnyEvent::Fork::RPC и отправить, скажем, запрос «подождать» дочернему элементу, на который он отвечает, когда закончит.

Чтобы сделать это с голым AnyEvent::Fork, проще всего воспользоваться двунаправленным каналом, предоставляемым ->run:

  AnyEvent::Fork
    ->new
    ->run (sub {
       my ($fh) = @_;
       sysread $fh, my $dummy, 1; # will wait for data, or eof
       ... done, you can now call e.g. $cv->send

sysread попытается прочитать от дочернего элемента. Если дочерний элемент никогда ничего не отправляет, это заблокирует родителя до тех пор, пока дочерний элемент не выйдет, так как в этот момент дочерний элемент закроет свой конец канала, а sysread получит EOF.

Конечно, в программе AnyEvent вы, вероятно, не хотите блокировать, поэтому вы используете наблюдатель за вводом-выводом:

 ->run (sub {
    my ($fh) = @_;
    my $rw; $rw = AE::io $fh, 0, sub {
       ... read data received, or EOF
       undef $rw;
       ... done, you can now call e.g. $cv->send;
    }
  });

Этот трюк можно использовать и для внешних команд (exec в вашем примере), очищая состояние close-on-exec канала в дочернем элементе и, таким образом, передавая его исполняемой программе - в этом случае, когда все программы которые наследуют выход канала, канал будет сигнализировать EOF.

Это должно помочь вам начать. Есть и другие способы сделать это, но большинство или даже все хорошие из них будут включать в себя некоторый канал для связи, и самый простой способ получить его — использовать тот, который предоставляется AnyEvent::Fork.

person Remember Monica    schedule 30.11.2014
comment
Круто, спасибо, я поставил +1. Я скоро проверю это. - person cstamas; 02.12.2014