避免 Mojolicious 异步行为?避免 "AnyEvent::CondVar: recursive blocking wait attempted"

Avoid Mojolicious async beviour? Avoid "AnyEvent::CondVar: recursive blocking wait attempted"

我们有一个已经使用 AnyEvent 的库。它在内部使用 AnyEvent,最后它 returns 一个值( 同步 - 不带回调)。有什么方法可以将这个库与 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,触发错误。

我知道我可以重构 getData() 中的 $cv 来制作一个异步版本,但实际上真正的 "getData" 在很多地方被调用并将所有调用转向 "getData" 转换成异步代码是不可行的。

所以我的问题是:有什么方法可以在使用 morbo/Mojolicious 时可靠地调用上面的 getData()?我想 get '/' 阻止直到它完成。

编辑: AnyEvent 的 WHAT TO DO IN A MODULE 部分明确表示:

Never call ->recv on a condition variable unless you know that the ->send method has been called on it already. This is because it will stall the whole program, and the whole point of using events is to stay interactive.

上面的

getData() 违反了这一点。现在我明白了 AnyEvent 文档那部分的原因:-)

您可以通过设置环境变量 MOJO_REACTOR=Mojo::Reactor::Poll 或使用 AnyEvent::Loop before anything else so that AnyEvent uses its pure-perl loop (preferred, since it is not being used as the main loop). Unfortunately there is no way to cause it to use a separately instantiated loop otherwise, such as how Mojo::UserAgent blocking requests 函数来确保 Mojolicious 和 AnyEvent 不使用相同的主循环来避免此问题。

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


一个更 "async" 长期的解决方案可能是简单地允许操作共享主循环,因为 Mojolicious 是为 non-blocking 响应操作而设计的。为此,您可以首先确保两者确实共享 EV 主循环(例如通过设置 MOJO_REACTOR=Mojo::Reactor::EV),然后更改您的函数,或创建函数的新版本,以 return a promise 该函数将异步填充结果,并将依赖该结果的任何其他功能链接到该承诺之外。当然,您的功能比您在此处使用的单个计时器更复杂,但它仍然值得考虑 - 它将允许应用程序在等待 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 });
};