【问题标题】:Avoid Mojolicious async beviour? Avoid "AnyEvent::CondVar: recursive blocking wait attempted"避免 Mojolicious 异步行为?避免“AnyEvent::CondVar:递归阻塞等待尝试”
【发布时间】:2019-06-04 16:03:05
【问题描述】:

我们已经有一个使用 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,从而触发错误。

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

所以我的问题是:有什么方法可以在使用morbo/Mojolicious 时可靠地调用上面的确切getData()?我希望 get '/' 在完成之前阻止。

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

永远不要在条件变量上调用 ->recv,除非你知道已经调用了 ->send 方法。这是因为它会使整个程序停顿,而使用事件的全部意义在于保持交互性。

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

【问题讨论】:

    标签: perl mojolicious anyevent


    【解决方案1】:

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

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


    更“异步”的长期解决方案可能是简单地允许操作共享主循环,因为 Mojolicious 是为非阻塞响应操作而设计的。您可以首先确保两者实际上共享 EV 主循环(例如通过设置 MOJO_REACTOR=Mojo::Reactor::EV),然后更改您的函数,或创建该函数的新版本,以返回一个 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 });
    };
    

    【讨论】:

    • 是的,我确实认为我对这个敬酒。 EV 实际上允许使用 my $loop = new EV::Loop [$flags] 进行多个循环,但 AnyEvent::Impl::EV 不支持。我想有人可以写AnyEvent::Impl::EV::Alt 允许这样做。但是有一些限制......我确实使用Promises 编写了一个概念验证,所以我们同意正确的方法。但是我们真正的“getData()”在很多地方被调用,重构它以使用 Promise 意味着每个调用者(递归地)也需要类似地重构。
    • 是的,在这种情况下,一个新的返回承诺的变体是唯一明智的选择。但我相信这也不是微不足道的。
    猜你喜欢
    • 2013-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多