【问题标题】:Using AnyEvent run_cmd in Mojolicious, I keep getting this error: "AnyEvent::CondVar: recursive blocking wait attempted"在 Mojolicious 中使用 AnyEvent run_cmd,我不断收到此错误:“AnyEvent::CondVar:递归阻塞等待尝试”
【发布时间】:2013-10-30 14:39:38
【问题描述】:

在 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”路径中。

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

【问题讨论】:

  • 仅供参考,以后添加perl 标签将帮助更多人看到您的有趣问题。

标签: perl asynchronous mojolicious anyevent


【解决方案1】:

我认为您正在尝试做的是调用 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;

这将对作为参数传递给路由 (/?file=myfile&file=otherfile) 的每个文件名运行 soffice。然后标准输出、标准错误和返回码(应该是,我显然没有运行这个)作为 json 呈现给客户端(你可以很容易地记录它)。

【讨论】:

【解决方案2】:

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->loop 而不是阻塞在 CondVar 上:

EV->loop

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-23
    • 1970-01-01
    • 2016-05-05
    • 1970-01-01
    相关资源
    最近更新 更多