在Mojolicious中使用AnyEvent run_cmd,我不断收到此错误:“AnyEvent :: CondVar:递归阻塞等待尝试”

时间:2013-10-21 22:17:43

标签: perl asynchronous mojolicious anyevent

在一个Mojolicious应用程序中,我试图在点击链接时将ODT文件转换为HTML。我使用shell命令“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”路径中。

感谢任何帮助。提前谢谢。

2 个答案:

答案 0 :(得分:4)

我认为你要做的是调用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;

这会将每个文件名作为param传递给路由(/?file=myfile&file=otherfile)。然后stdout,stderr和返回代码(好吧应该是,我显然没有运行这个)作为json呈现给客户端(你可以轻松地记录它)。

答案 1 :(得分:3)

Creating single Thread Server with AnyEvent

AnyEvent recursive Blocking..

如果您使用的是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->循环而不是阻止CondVar:

EV->loop