AnyEvent and Dancer: CondVars

In my last post, I explained how to get an AnyEvent->timer to work in a Dancer application.

There’s nothing wrong with timers, but if you are using AnyEvent, you usually have to deal with CondVars. There are two things you can do with a CondVar: Either you register a callback which will be called when the CondVar is triggered, or you call recv and it will block until the CondVar is triggered. In a route of your Dancer application, you probably want to block until you got all the data you want to display to your user.

Take the following (made-up) example which uses a CondVar:

get '/' => sub {
    my $cv = AnyEvent->condvar;
    my $timer = AnyEvent->timer(after => 1, cb => sub { $cv->send(1) });
    my $result = $cv->recv;
    template 'foo', { result => $result };
};

You will get a runtime error stating "AnyEvent::CondVar: recursive blocking wait detected". This is because Twiggy also uses a CondVar as exit_guard, to run infinitely long (blocking on a CondVar is an easy way to run the main loop).

The solution is to use a specific eventloop, such as EV, and call EV->loop instead of blocking on a CondVar:

#!/usr/bin/env perl
use strict;
use warnings;
use EV;
use Twiggy::Server;
use Dancer;
use npmd;

my $server = Twiggy::Server->new(
host => '0.0.0.0',
port => 5000
);

my $app = sub {
my $env = shift;
my $request = Dancer::Request->new( $env );
Dancer->dance( $request );
};

$server->register_service($app);

EV->loop

5 Comments

This seems like the wrong approach, since it is going to block all other requests on Twiggy. If you really want to take advantage of Twiggy and AnyEvent you should avoid blocking with CondVars in your request handlers.

Doing something like this would allow Twiggy to handle other requests while the timer is running.


my $app = sub {
  my $env = shift;
  return sub {
    my $respond = shift;
    AnyEvent->timer(after => 1, cb => sub {
      $respond->([200, [], ["hello world!"]]);
    });
  };
};

I don't know if Dancer can take advantage of PSGI's delayed responses yet. If not, it would certainly be something worth adding.

Yes, it definitely seems the wrong approach, if you really want to wait on condvars, looks at the Coro server, Corona (where you have access to Coro::rouse_cb and Coro::rouse_wait which do what you want in your first example, but only blocking the current co-routine, not the whole app, like this:


use Coro;
use Dancer;

get '/hello/:name' => sub {
my $timer = AnyEvent->timer( after => 15, cb => Coro::rouse_cb );
my ($status) = Coro::rouse_wait;
return "Why, hello there " . params->{name};
};

dance;


or else use callbacks and delayed writing. To make it cleaner, you could use AE's begin and end functions, which let you manage callbacks like condvars, call begin on an AE condvar, adding a callback, and then in your timer's handler call that condvar's end method.

Scratch the last part of my previous comment, Dancer doesn't officially support delayed responses yet, so something like Coro seems to be the way to do it. Perhaps they'll allow routes to return coderefs instead of stringifying them when they do support that.

Sorry, I missed your reply to this. That likely isn't working because the timer is going out of scope and being canceled. You can fix this by assigning it to a scalar like this:


my $app = sub {
  my $env = shift;
  return sub {
    my $respond = shift;
    my $t; $t = AnyEvent->timer(after => 1, cb => sub {
      undef $t;
      $respond->([200, [], ["hello world!"]]);
    });
  };
};

Leave a comment

About mstplbg

user-pic I blog about Perl, obviously.