避免Mojolicious异步beviour?避免“ AnyEvent :: CondVar:尝试递归阻止等待”

时间:2019-06-04 16:03:05

标签: perl mojolicious anyevent

我们有一个已经使用AnyEvent的库。它在内部使用AnyEvent,最后返回一个值(同步-不带回调)。我可以通过Mojolicious使用此库吗?

它的作用类似于:

#!/usr/bin/perl
use strict;
use warnings;
use AnyEvent;
use Mojolicious::Lite;

# To the caller, getData is a synchronous sub that returns a value.
# The fact that it uses AnyEvent is an internal implementation detail of
# getData
sub getData {
    my $cv = AnyEvent->condvar;

    my $w = AnyEvent->timer (after => 5, cb => sub {
        # Perform many async operations, represented here by a single timer,
        # calculating a final result that is sent:
        $cv->send(42);
    });

    my $result = $cv->recv;
    # postProcess($result);
    return $result;
}

get '/' => sub {
    my ($c) = @_;
    $c->render(text => "Data is: " . getData());
};

app->start;

当我运行morbo app.pl并尝试同时从两个浏览器选项卡进行get '/'时,出现此错误:

AnyEvent::CondVar: recursive blocking wait attempted at /bla/bla/app.pl line 16.

我认为这是因为morbo在内部使用EV,因此当它分派处理第一个get '/'时,$cv->recv最终会被调用,返回到EV事件循环。 EV现在尝试处理第二个get '/',并再次调用$cv->resv,从而触发错误。

我知道我可以将$cv中的getData()重构为异步版本,但实际上在许多地方都调用了真正的“ getData”,并将所有对“ getData”的调用转换为异步代码不可行。

所以我的问题是:在使用getData() / Mojolicious时,有什么方法可以可靠地调用上面确切的morbo?我希望get '/'进行屏蔽,直到完成。

编辑:AnyEvent的WHAT TO DO IN A MODULE部分明确指出:

  

请不要在条件变量上调用-> recv,除非您知道-> send方法已被调用过。这是因为它将使整个程序停顿,并且使用事件的全部目的是保持交互性。

上面的

getData()违反了该规定。现在,我了解了AnyEvent文档中该部分的原因:-)

1 个答案:

答案 0 :(得分:3)

通过设置env var MOJO_REACTOR=Mojo::Reactor::Poll或先使用AnyEvent::Loop来确保Mojolicious和AnyEvent不使用同一主循环,可以避免此问题,以便AnyEvent使用其纯净的perl循环(首选,因为它未被用作主循环)。不幸的是,没有其他方法可以使它使用单独的实例化循环,例如Mojo::UserAgent blocking requests的功能。

请注意,通常,您希望多个循环使用者共享主循环。这是一种奇怪的情况,您想要内部消费者阻止。


一个更“异步”的长期解决方案可能是简单地允许操作共享主循环,因为Mojolicious是为非阻塞响应操作而设计的。为此,您可以先确保两者实际上共享EV主循环(例如通过设置MOJO_REACTOR=Mojo::Reactor::EV),然后更改函数或创建函数的新版本以返回{{3} },该函数将异步地填充结果,并将依赖该结果的任何其他功能链接到该诺言中。当然,您的功能比您在此处使用的单个计时器更复杂,但是仍然值得考虑-它将允许应用程序在等待AnyEvent操作的结果时继续满足其他请求。

sub getData_p {
  my $p = Mojo::Promise->new;
  my $w; # keep a strong reference to $w for AnyEvent's reasons
  $w = AnyEvent->timer(after => 5, cb => sub { $p->done(42); undef $w });
  return $p;
}

get '/' => sub {
  my ($c) = @_;
  my $tx = $c->render_later->tx; # keep strong reference to $tx
  getData_p()->then(sub { $c->render(text => "Data is: $_[0]") })
    ->catch(sub { $c->reply->exception($_[0]); undef $tx });
};