Asynchronous Dancer2

A long awaited feature in Dancer2 is asynchronous (delayed) streaming responses. These are a lot of words, but what they eventually translate to is the ability to respond without blocking additional actions on the same thread. That still doesn't mean much. Let's see if we can clarify that.

Blocking and non-blocking outside web

By default, all code in Perl is run synchronously, or in other words, blocking. This means that no single action runs until the previous action ends.

for ( 1 .. 10 ) {
    my $result = run_action();
    ...
}

If we wish to run more than one action at the same time, and assuming they are not dependent on each other, we can use multiple processes:

for ( 1 .. 10 ) {
    my $pid = fork();
    defined $pid
        or die "Cannot fork: $!\n";

    if ( $pid == 0 ) {
        my $result = run_action();
        ...
    } else {
        # this is the parent
        # which is meaningless here...
    }
}

This will run each of them separately. If we have an event loop, we can schedule these actions using the event loop and provide a callback for handling the results:

for ( 1 .. 10 ) {
    # this isn't a real function
    # it depends on your event loop of choice
    schedule_action_on_event_loop( sub {
        my $result = shift;
        ...
    } );
}

Within the web environment

When working on a website, any response which takes a while to complete will hang the entire thread waiting for the action to finish and the user to receive a response. Additionally, any request which requires multiple blocking functions, it will take longer to complete, since we wait for each request.

In order to handle this, a common technique is to use asynchronous syntax. Not all event loops support them and many provide their own event loop in order to handle it, forcing you to use their syntax only.

Dancer2 recently added asynchronous syntax which is not bound to any existing event loop, making it possible to pick your own.

Asynchronous in Dancer2

Let's assume we want to run a web server with a single thread that does not fork. We can handle multiple users by responding asynchronously without forcing the next user to wait in line:

get '/' => sub {
    delayed {
        flush;
        content template 'index';
        done;
    };
};

This is a simple endpoint that doesn't do anything special, but it allows us to understand the existing syntax before we try something more complicated.

The delayed keyword begins an asynchronous response. It registers within your web server (which should be an asynchronous web server, mind you) a response to run asynchronously.

The flush keyword sends the headers to the user, allowing you to begin sending content to the user. This is when you begin to stream data.

The content keyword sends data to the user. Every time you call content, no matter how many times, you will stream additional data. This data will be sent while being able to handle additional connections and clients within the single process of the web server.

The done keyword is simply the way to tell the server to close the connection with the user. You can then continue to run code, but the user will no longer be connected to your web server.

So when is this really useful? Let's imagine a situation in which we would like to make several requests to remote servers, and then send the user all of them. We will use AnyEvent as our event loop of choice (which will run with the Twiggy web server.

use Dancer2;
use AnyEvent;
use AnyEvent::HTTP;

my @urls = qw<...>;

get '/' => sub {
    delayed {
        flush;

        # keep track of responses with a condvar
        my $cv = AnyEvent->condvar;

        # decide what happens when all responses arrive
        $cv->cb( delayed { done; } );

        foreach my $url (@urls) {
            $cv->begin;
            http_get $url, delayed {
                my ( $headers, $body ) = @_;
                content $body;
                $cv->end;
            };
        }
    };
};

You will notice several new things here.

Firstly, we're using a condition variable (condvar), which helps us track all requests. Each time we are about to issue one, we count it using begin. Every time we receive a response, we track it with end.

You might also notice we replaced every sub call with delayed. The way delayed works is that it creates an asynchronous sub that has the DSL available. This means that, simply put, every time you wish to have a sub call and use Dancer2 keywords inside it, just replace the word sub with delayed. Everything else is the same.

And lastly, we set the done keyword before we run the requests, which doesn't necessarily make much sense, because it happens after. This is how it works with asynchronous programming.

Take into account that other event loop might have other syntax, but they should all be similar enough.

Future

We have plans on introducing websockets in the future, and they as well, will be asynchronous and streaming.

Author

This article has been written by Sawyer X for the Perl Dancer Advent Calendar 2016.

Copyright

No copyright retained. Enjoy.

Sawyer X.