Используя AnyEvent run_cmd в Mojolicious, я продолжаю получать эту ошибку: AnyEvent::CondVar: попытка ожидания рекурсивной блокировки

В приложении Mojolicious я пытаюсь преобразовать файлы ODT в HTML при нажатии на ссылку. Я конвертирую файлы с помощью команды оболочки «soffice». Преобразование файлов занимает некоторое время. Я отправляю сообщения о состоянии пользователю, чтобы уведомить его о ходе выполнения. Я отправляю эти сообщения об обновлении статуса, записывая их в объект Mojo::Log. Затем я подписываюсь на этот объект журнала в маршруте EventSource.

Затем я перебираю файлы и использую AnyEvent::Util run_cmd для выполнения внешней программы «soffice».

for my $file (@{ $filelist }) {
   my $output_dir = './output_dir';
   my $cmd = "soffice --headless --convert-to html --outdir '$output_dir' '$file'";
   my $cv = AnyEvent->condvar;
   my $w;
   $w = run_cmd($cmd, 
                '>'  => sub { my $out = shift;
                              &WriteToLog({ status => "cmd output '$out'..." });
                              undef $w;
                              $cv->send;
                 },

                '2>' => sub { my $err = shift;
                              &WriteToLog({ status => "ERROR '$err'..." });
                              undef $w;
                              $cv->send;
                 }
            );

   $cv->recv;
}

В значительной степени скопировано и вставлено из основных руководств AnyEvent. Если нужно конвертировать всего несколько файлов (около 2 или 3), то все идет хорошо. Сообщения о состоянии, отправленные через соединение EventSource, отображаются в браузере клиента. Затем, после преобразования всех файлов, отображается веб-страница.

Если необходимо обработать больше файлов, несколько файлов будут преобразованы, тогда появится сообщение об ошибке в заголовке темы.

Маршрутизация для маршрута, содержащего приведенный выше код, такова:

my $initdocs = $r->under->to('docroute#initdocs');
$initdocs->get('/showdocs')->to('docroute#showdocs');

Приведенный выше код находится в маршруте «initdocs».

Любая помощь приветствуется. Заранее спасибо.


person Hermes Conrad    schedule 21.10.2013    source источник
comment
просто к вашему сведению, в будущем добавление тега perl поможет большему количеству людей увидеть ваши трогательные вопросы.   -  person Joel Berger    schedule 25.10.2013


Ответы (2)


Я думаю, что вы пытаетесь вызвать процесс soffice (блокировки), не блокируя остальную часть потока сервера. Я не эксперт по AE, но я не думаю, что run_cmd делает это. Это ближе к тому, что делает fork_call. Возможно, вы хотите сделать что-то вроде этого:

#!/usr/bin/env perl

use Mojolicious::Lite;
use EV;
use AnyEvent::Util 'fork_call';

any '/' => sub {
  my $c = shift;
  $c->render_later;
  fork_call { `sleep 5 && echo 'hi'` } sub {
    my $data = shift;
    $c->render( text => $data );
  };
};

app->start;

В моем примере я просто делаю простой блокирующий вызов, но вы могли бы также легко вызвать soffice.

Теперь, поскольку вы говорите, что вам, возможно, придется преобразовать несколько разных файлов, прежде чем вернуться к клиенту, вы можете использовать отличный Mojo::IOLoop::Delay для управления процессами.

#!/usr/bin/env perl

use Mojolicious::Lite;
use EV;
use AnyEvent::Util 'fork_call';

my @jobs = (
  q{sleep 5 && echo 'hi'},
  q{sleep 5 && echo 'bye'},
);

any '/' => sub {
  my $c = shift;
  $c->render_later;
  my $delay = Mojo::IOLoop->delay;
  $delay->on( finish => sub { 
    shift; $c->render(text => join '', @_ );
  });
  fork_call { `$_` } $delay->begin(0) for @jobs;
};

app->start;

Еще раз, я просто захватываю вывод и отправляю его в вызов рендеринга, но обратите внимание, что он ждет завершения всех заданий, прежде чем вернуться. Что-то более близкое к вашему реальному варианту использования может быть:

#!/usr/bin/env perl

use Mojolicious::Lite;
use EV;
use AnyEvent::Util 'fork_call';
use Capture::Tiny 'capture';

any '/' => sub {
  my $c = shift;
  my $files = $c->every_param('file');
  $c->render_later;
  my $delay = Mojo::IOLoop->delay;
  $delay->on( finish => sub { 
    shift; $c->render( json => \@_ );
  });
  my $output_dir = './output_dir';
  for my $file (@$files) {
    my $cmd = "soffice --headless --convert-to html --outdir '$output_dir' '$file'";
    fork_call { [ capture { system $cmd } ] } $delay->begin(0);
  }
};

app->start;

Это запускает soffice для каждого имени файла, переданного в качестве параметра для маршрута (/?file=myfile&file=otherfile). Затем stdout, stderr и код возврата (ну, должно быть, я, очевидно, не запускал этот код) отображаются как json для клиента (вы могли бы легко зарегистрировать его).

person Joel Berger    schedule 24.10.2013
comment
Я просто хотел упомянуть (поскольку появился этот пост), что этот вопрос в конечном итоге вдохновил меня на создание этого модуля на cpan: metacpan.org/pod/Mojo::IOLoop::ForkCall - person Joel Berger; 14.06.2016

Создание однопоточного сервера с AnyEvent

Рекурсивная блокировка AnyEvent..

Если вы используете AnyEvent, вам обычно приходится иметь дело с CondVars. Есть две вещи, которые вы можете сделать с CondVar: либо вы регистрируете обратный вызов, который будет вызываться при срабатывании CondVar, либо вы вызываете recv, и он будет блокироваться до тех пор, пока не сработает CondVar. В маршруте вашего Mojo::Controller вы, вероятно, захотите заблокировать, пока не получите все данные, которые хотите отобразить своему пользователю.

Возьмем следующий (придуманный) пример, в котором используется CondVar:

непроверенный:

get '/' => sub {
    ...
    my $cv = AnyEvent->condvar;
    my $timer = AnyEvent->timer(after => 1, cb => sub { $cv->send(1) });
    my $result = $cv->recv;
    ...
};

Вы получите сообщение об ошибке времени выполнения «AnyEvent::CondVar: обнаружено ожидание рекурсивной блокировки». Возможно, это связано с тем, что Morbo также использует CondVar в качестве exit_guard, чтобы работать бесконечно долго (блокировка CondVar — это простой способ запустить основной цикл).

Мой подход заключается в использовании определенного цикла обработки событий, такого как EV. и вызовите цикл EV->loop вместо блокировки CondVar:

EV->loop
person Alex Tape    schedule 22.10.2013