Asynchronous programming patterns in Perl

115
ASYNCHRONOUS PROGRAMMING WITH MOJO::IOLOOP::DELAY Everything you wished your dad had told you about event loops and asynchronous programming

description

Why is blocking IO so bad? Why is non-blocking code with callbacks so bad? Here are a few ways to deal with both problems in Perl.

Transcript of Asynchronous programming patterns in Perl

Page 1: Asynchronous programming patterns in Perl

ASYNCHRONOUS PROGRAMMING!WITH!MOJO::IOLOOP::DELAYEverything you wished your dad had told you about event loops and asynchronous programming

Page 2: Asynchronous programming patterns in Perl

THE PLAN

Blocking vs non-blocking!

Microservices!

Event loops (!)!

Event-driven code (")!

Other options!

Mojo::IOLoop::Delay

I promise these are the only emoji characters I’ll use the entire presentation.

Page 3: Asynchronous programming patterns in Perl

LET’S ORDER BREAKFAST!!

pancakes!

soft-boiled egg!

orange juice

Take a break

Are you hungry? I’m hungry.

Page 4: Asynchronous programming patterns in Perl

BLOCKING CHEF

mix pancakes heat skillet cook

pancakes boil water cook eggs cut juice

* Here’s blocking chef: he likes to look busy, so he’s always doing something. He’s going to do everything. * And what’s the problem here? It takes too long. What happens? * Blocking chef gets fired. Har.

Page 5: Asynchronous programming patterns in Perl

NON-BLOCKING CHEF

mix pancakes

heat skillet cook pancakes

boil water cook eggs

cut juice

Non-blocking chef is smarter and better looking. He can tell the difference between things he has to wait for, and things he can start and then be notified when they’re ready. !NB chef turns on the skillet and puts the water on to boil. He has alarms that tell him when the skillet is hot, when the water has boiled, and when the eggs are done.

Page 6: Asynchronous programming patterns in Perl

NUMBERS EVERYONE SHOULD KNOW—JEFF DEAN

L1 cache reference 0.5 ns!

Branch mispredict 5 ns!

L2 cache reference 7 ns!

Mutex lock/unlock 100 ns!

Main memory reference 100 ns!

Compress 1K bytes with Zippy 10,000 ns!

Send 2K bytes over 1 Gbps network 20,000 ns

Read 1 MB sequentially from memory 250,000 ns!

Round trip within same datacenter 500,000 ns!

Disk seek 10,000,000 ns

Read 1 MB sequentially from network 10,000,000 ns

Read 1 MB sequentially from disk 30,000,000 ns

Send packet CA→Netherlands→CA 150,000,000 ns

Why is this so bad? In human time, 30,000,000 ns doesn’t take long, but in computer time, it’s an eternity. While the network fetch happened, I could have been doing millions of cycles of real work. Blocking IO operations are terrible.

Page 7: Asynchronous programming patterns in Perl

sets up a “route” (url handler) for GET /foo

use Mojolicious::Lite; !get '/foo' => sub { my $c = shift; $c->render( json => { foo => “bar” }); }; !app->start;

MOJOLICIOUS::LITE

get '/foo' => sub { my $c = shift; $c->render( json => { foo => “bar” }); };

* Here is a controller * Here is where we start the event loop * Here is a handle to the controller object * Here is how we render output to the client (browser, etc.) * Here is how we do a JSON serialization of a Perl hash reference * It’s all simple and minimal.

Page 8: Asynchronous programming patterns in Perl

sets up a “route” (url handler) for GET /foo

start the daemon and run the event loop

use Mojolicious::Lite; !get '/foo' => sub { my $c = shift; $c->render( json => { foo => “bar” }); }; !app->start;

MOJOLICIOUS::LITE

app->start;

* Here is a controller * Here is where we start the event loop * Here is a handle to the controller object * Here is how we render output to the client (browser, etc.) * Here is how we do a JSON serialization of a Perl hash reference * It’s all simple and minimal.

Page 9: Asynchronous programming patterns in Perl

sets up a “route” (url handler) for GET /foo

start the daemon and run the event loop

$c = “controller” (MVC pattern)

use Mojolicious::Lite; !get '/foo' => sub { my $c = shift; $c->render( json => { foo => “bar” }); }; !app->start;

MOJOLICIOUS::LITE

my $c = shift;

* Here is a controller * Here is where we start the event loop * Here is a handle to the controller object * Here is how we render output to the client (browser, etc.) * Here is how we do a JSON serialization of a Perl hash reference * It’s all simple and minimal.

Page 10: Asynchronous programming patterns in Perl

sets up a “route” (url handler) for GET /foo

start the daemon and run the event loop

$c = “controller” (MVC pattern)

invoke render()

use Mojolicious::Lite; !get '/foo' => sub { my $c = shift; $c->render( json => { foo => “bar” }); }; !app->start;

MOJOLICIOUS::LITE

$c->render( json => { foo => “bar” });

* Here is a controller * Here is where we start the event loop * Here is a handle to the controller object * Here is how we render output to the client (browser, etc.) * Here is how we do a JSON serialization of a Perl hash reference * It’s all simple and minimal.

Page 11: Asynchronous programming patterns in Perl

sets up a “route” (url handler) for GET /foo

start the daemon and run the event loop

$c = “controller” (MVC pattern)

invoke render()

set content-type to application/json and serialize the hashref

use Mojolicious::Lite; !get '/foo' => sub { my $c = shift; $c->render( json => { foo => “bar” }); }; !app->start;

MOJOLICIOUS::LITE

json => { foo => “bar” }

* Here is a controller * Here is where we start the event loop * Here is a handle to the controller object * Here is how we render output to the client (browser, etc.) * Here is how we do a JSON serialization of a Perl hash reference * It’s all simple and minimal.

Page 12: Asynchronous programming patterns in Perl

MOJOLICIOUS::LITE

command-line GET!

start daemon!

start pre-forking daemon!

list routes!

run tests

$ ./my-app get /foo {"foo":"bar"}

$ ./my-app daemon Server available at http://127.0.0.1:3000.

$ hypnotoad ./my-app

$ ./my-app test

$ ./my-app routes

Some other Mojolicious goodies.

Page 13: Asynchronous programming patterns in Perl

MICROSERVICE ARCHITECTURE

Microservices: small HTTP (or other TCP) services!

do one thing, do it well!

small is beautiful!

composition!

text (JSON) is the universal interface!

Speak HTTP everywhere

* Microservices are the network extension of the Unix philosophy. * Composition: we can compose powerful functions from simpler functions. * We’re going to do a little of that tonight.

Page 14: Asynchronous programming patterns in Perl

MICROSERVICE EXAMPLE

1. get our source IP address!

2. using the IP address, get our geolocation!

3. using the geolocation:!

• get the weather!

• get the moon phase!

• get the current air quality index (AQI)

This is what we’re going to eventually build.

Page 15: Asynchronous programming patterns in Perl

MICROSERVICE: MY IP ADDRESS

GET http://demo:8082/my-ip

{"ip":"71.199.21.36"}

I wrote this simple service to return your source IP, or the IP you’re connecting from.

Page 16: Asynchronous programming patterns in Perl

MICROSERVICE: GEOLOCATION

{ "query" : "71.199.21.36", "country" : "United States", "timezone" : "America/Denver", "region" : "UT", "city" : "American Fork", "zip" : “84003", "lat" : 40.393, "lon" : -111.7838 }

GET http://ip-api.com/json/71.199.21.36

This service takes an IP address and returns the location where that IP address originates.

Page 17: Asynchronous programming patterns in Perl

MICROSERVICE: MOON PHASE

GET http://demo:8080/pom

{ "text" : "94.0%, waning gibbous", "phase" : "0.5787", "illumination" : "0.9401", "age" : "17.0893" }

I wrote this service using the venerable Astro::MoonPhase.

Page 18: Asynchronous programming patterns in Perl

MICROSERVICE: WEATHER

GET http://demo:8084/weather/40.2960,-111.6946

{ "windBearing" : 85, "icon" : "clear-day", "pressure" : 1016.52, "windSpeed" : 3.73, "cloudCover" : 0, "summary" : "Clear", "dewPoint" : 35.35, "humidity" : 0.65, "temperature" : 46.41, }

This service is the excellent forecast.io; there’s a ton more information returned here, but the essence of what we’ll be using is here.

Page 19: Asynchronous programming patterns in Perl

MICROSERVICE: AIR QUALITY

GET http://demo:8083/aqi/40.296/-111.6946

{ "pollutant" : "small particulate matter", "index" : 22 }

This service gives us the air quality index on a scale of 0 to 500 I believe.

Page 20: Asynchronous programming patterns in Perl

MICROSERVICE: SLOW

GET http://demo:8081/slow/3

{"waited":3}

I wrote this service to show exaggerated latency

Page 21: Asynchronous programming patterns in Perl

A BLOCKING MOJO APPuse Mojolicious::Lite; !get '/weather' => sub { my $c = shift; ! my $ua = $c->ua; ! my $tx1 = $ua->get('http://demo:8080/pom'); my $pom = $tx1->res->json('/text'); ! my $tx2 = $ua->get('http://demo:8081/slow/2'); my $slow = $tx2->res->json('/waited'); ! my $tx3 = $ua->get('http://demo:8082/my-ip'); my $ip = $tx3->res->json('/ip'); ! $c->render(json => { pom => $pom, waited => $slow, ip => $ip }); }; !app->start;

my $tx1 = $ua->get('http://demo:8080/pom'); my $pom = $tx1->res->json('/text');

* here’s our app * we make a call to POM * then we call slow, etc.

Page 22: Asynchronous programming patterns in Perl

HOW DOES IT PERFORM?

$ wrk -c 1 -d 10 -t 1 http://localhost:3000/weather Running 10s test @ http://localhost:3000/weather 1 threads and 1 connections Thread Stats Avg Stdev Max +/- Stdev Latency 2.14s 0.00us 2.14s 100.00% Req/Sec 0.00 0.00 0.00 100.00% 4 requests in 10.01s, 0.88KB read Socket errors: connect 0, read 0, write 0, timeout 1 Requests/sec: 0.40 Transfer/sec: 89.94B

poorly: 0.4 req/sec

You might see that I’ve limited the connections down to one; if I went higher than this, wrk’s stats messed up and I couldn’t get any requests per second. Even higher, this application blocks and can only handle one connection at a time anyway.

Page 23: Asynchronous programming patterns in Perl

SOLUTIONS?

Got any ideas?

hypnotoad!

still doesn’t scale well…

Hypnotoad is like Apache: fork off some daemons and hope there are enough of them to handle the load. If we pass our max clients, the next client to connect is going to have to wait.

Page 24: Asynchronous programming patterns in Perl

ASYNCHRONOUS EXAMPLE: SIGNAL HANDLERS

$SIG{INT} = sub { say "Got an INT signal!"; exit }; !while(1) { sleep }

What is an example of a common asynchronous thing we see when we write programs?

Page 25: Asynchronous programming patterns in Perl

ASYNCHRONOUS EXAMPLE: JAVASCRIPT CALLBACKS

<script type=“text/javascript"> ! $(document).ready(function() { jQuery.ajax(“http://www.perl.org”, { success: function(data) { alert("loaded!") } }) }) !</script>

jQuery.ajax(“http://www.perl.org”, { success: function(data) { alert("loaded!") } })

If you’ve used jQuery, you’ve almost certainly written a callback. The browser has an event loop that is continually checking for UI events such as mouse clicks, DOM events, or in this case, an ajax load to complete.

Page 26: Asynchronous programming patterns in Perl

ASYNCHRONOUS EXAMPLE: JAVASCRIPT CALLBACKS

<script type=“text/javascript"> ! $(document).ready(function() { jQuery.ajax(“http://www.perl.org”, { success: function(data) { alert("loaded!") } }) }) !</script>

success: function(data) { alert("loaded!") }

If you’ve used jQuery, you’ve almost certainly written a callback. The browser has an event loop that is continually checking for UI events such as mouse clicks, DOM events, or in this case, an ajax load to complete.

Page 27: Asynchronous programming patterns in Perl

EVENT LOOPS!not parallel!

not threaded!

event-driven!

non-blocking

a- “not” + synchronus “together-time”

Speaking of event loops… we’re talking about: not parallel programming: multiple cores on one problem not threaded programming: multiple threads on one problem event-driven: our code runs when events occur non-blocking: we don’t have to wait for any I/O

Page 28: Asynchronous programming patterns in Perl

EVENT LOOPS ARE MYSTERIOUS

Event!

EV!

POE!

IO::Async!

Mojolicious

use Event 'loop'; my $loop = loop();

use EV; EV::run;

use POE; POE::Kernel->run();

use IO::Async::Loop; my $loop = IO::Async::Loop->new; $loop->run;

use Mojolicious::Lite app->start;

Here are some event loops I’ve used over the years, and I never really knew what was going on under the hood. It turns out, it’s pretty simple.

Page 29: Asynchronous programming patterns in Perl

HOW EVENT LOOPS WORK*my @events = (); my %watchers = ( network => sub { say "Got network data: " . shift } ); !while(sleep 1) { if (my $evt = shift @events) { my ($event, $data) = @$evt; $watchers{$event}->($data); } ! check_events(); } !sub check_events { if (my $data = network_event() and $watchers{network}) { return [network => $data]; } ! if (my $data = timer_event() and $watchers{timer}) { return [timer => $data]; } ! return (); }

* “A little inaccuracy sometimes saves tons of explanation.”—H.H. Munroe

my @events = (); my %watchers = ( network => sub { say "Got network data: " . shift } ); !while(sleep 1) { push @events, check_events(); ! if (my $evt = shift @events) { my ($event, $data) = @$evt; $watchers{$event}->($data); } }

my @events = (); my %watchers = ( network => sub { say "Got network data: " . shift } );

We loop forever waiting for events to happen. When an event happens, we run some code that was waiting for it and then we loop some more. !What kinds of events can we watch for? Timers, signals, disk IO, network IO, even custom events that we create ourselves. !Event loops allow us to get lots of things done while we otherwise might be waiting for slow, blocking things. For example, it only takes a few microseconds to build a web request and put it on the wire, but it takes many milliseconds, sometimes seconds, for the response to come back. That's an eternity for the computer. Why don't we instead fan out as many requests as we have resources for and then handle the responses when they come back? Disk reads are the same: they're often worse than network requests—waiting for I/O in general means you're not getting your money's worth from your CPU. Remember blocking chef.

Page 30: Asynchronous programming patterns in Perl

get '/weather' => sub { my $c = shift; ! my $tx1 = $c->ua->get(‘http://demo:8080/pom'); my $pom = $tx1->res->json('/text'); ! my $tx2 = $c->ua->get(‘http://demo:8081/slow/2'); my $slow = $tx2->res->json('/waited'); !};

get '/weather' => sub { my $c = shift; $c->render_later; ! $c->ua->get(‘http://demo:8080/pom' => sub { my $tx = pop; my $pom = $tx->res->json('/text'); ! $c->ua->get(‘http://demo:8081/slow/2' => sub { my $slow = pop->res->json('/waited'); ... }); }); };

blocking

non-blocking

Here’s our same application but converted to non-blocking mode using callbacks. !In event loops, functions no longer return anything, or at least anything useful. All of the useful stuff is passed to the callback instead. !In event loops, when we want to force an ordering, we have to put the dependent code inside the callback also. !Note that event-driven code doesn’t run faster, it just doesn’t block. An individual request still takes the same amount of time, but the event loop isn’t blocked while waiting for slow requests, and can handle many more at the same time.

Page 31: Asynchronous programming patterns in Perl

HOW DOES IT PERFORM?

$ wrk -c 10000 -d 10 -t 100 http://localhost:3000/weather Running 10s test @ http://localhost:3000/weather 100 threads and 10000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 3.31s 1.87s 7.88s 51.25% Req/Sec 28.50 17.21 53.00 75.00% 1314 requests in 10.01s, 258.28KB read Socket errors: connect 0, read 12, write 0, timeout 47624 Requests/sec: 131.31 Transfer/sec: 25.81KB

better: 131 req/sec!300x speedup!

* note: each individual request still takes 2s, but we can now handle hundreds or thousands of these simultaneously because we’re not blocking on the slow calls.

Page 32: Asynchronous programming patterns in Perl

DRAWBACKS

Continuation-passing style!

event-loop concurrency!

flow control, error handling

* CPS is hard to read and work with * We achieve concurrency only in the main event loop, not within the handler itself; e.g., even if we had things that *could* happen at the same time, we

can’t do that. * Control over flow

Page 33: Asynchronous programming patterns in Perl

get '/weather' => sub { my $c = shift; $c->render_later; ! my $ua = $c->ua; ! $ua->get('http://demo:8080/pom' => sub { my $tx = pop; my $pom = $tx->res->json('/text'); ! $ua->get('http://demo:8081/slow/2' => sub { my $tx = pop; my $slow = $tx->res->json(‘/waited'); ! $ua->get('http://demo:8082/my-ip' => sub { my $tx = pop; my $ip = $tx->res->json(‘/ip'); ! $c->render(json => { pom => $pom, waited => $slow, ip => $ip }, status => 200); }); }); }); };

This is a picture of the famous pyramid of death aka callback-hell.

Page 34: Asynchronous programming patterns in Perl

Thanks to tempire for the meme

Page 35: Asynchronous programming patterns in Perl

OTHER SOLUTIONS

What now?

AnyEvent!

Promises!

Mojo::IOLoop::Delay

Page 36: Asynchronous programming patterns in Perl

ANYEVENT PRIMER

ad-hoc flow control!

powerful primitives

CondVars give us ad hoc flow control in any event loop. They’re powerful primitives and you can build your own event system on top of them.

Page 37: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 38: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

my $cv = AE::cv;

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 39: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 0cb: undef

$cv

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 40: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 0cb: undef

$cv

$cv->cb(sub { say “all urls done” });

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 41: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 0cb: sub { … }

$cv

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 42: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 0cb: sub { … }

$cv

$cv->begin;

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 43: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 1cb: sub { … }

$cv

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 44: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 1cb: sub { … }

$cv$ua->get($url1

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 45: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 1cb: sub { … }

$cv$ua->get($url1

$ fetch-urls

$url1 request…

(walk through as if you were the Perl interpreter)

Page 46: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 1cb: sub { … }

$cv$ua->get($url1

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 47: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 1cb: sub { … }

$cv

$cv->begin;

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 48: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 2cb: sub { … }

$cv

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 49: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 2cb: sub { … }

$cv$ua->get($url2

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 50: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 2cb: sub { … }

$cv$ua->get($url2

$ fetch-urls

$url2 request…

(walk through as if you were the Perl interpreter)

Page 51: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 2cb: sub { … }

$cv$ua->get($url2

$ fetch-urls

(walk through as if you were the Perl interpreter)

Page 52: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 2cb: sub { … }

$cv

$ fetch-urls

say “running...”

(walk through as if you were the Perl interpreter)

Page 53: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 2cb: sub { … }

$cv

$ fetch-urls running...$ fetch-urls

say “running...”

(walk through as if you were the Perl interpreter)

Page 54: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 2cb: sub { … }

$cv

$ fetch-urls running...$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 55: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 2cb: sub { … }

$cv wait…

$ fetch-urls running...$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 56: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 2cb: sub { … }

$cv wait…receive $url2 response

$ fetch-urls running...$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 57: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 2cb: sub { … }

$cvsub { say “url2 done”; $cv->end }

wait…receive $url2 response

run get() callback

$ fetch-urls running... url2 done

$ fetch-urls running...$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 58: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

counter: 2cb: sub { … }

$cvsub { say “url2 done”; $cv->end }

wait…receive $url2 response

run get() callback$cv->end

$ fetch-urls running... url2 done

$ fetch-urls running...$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 59: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

wait…receive $url2 response

run get() callback$cv->endcounter: 1

cb: sub { … }

$cv

$ fetch-urls running... url2 done

$ fetch-urls running...$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 60: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

wait…receive $url2 response

run get() callback$cv->end

wait…counter: 1cb: sub { … }

$cv

$ fetch-urls running... url2 done

$ fetch-urls running...$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 61: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

wait…receive $url2 response

receive $url1 response

run get() callback$cv->end

wait…counter: 1cb: sub { … }

$cv

$ fetch-urls running... url2 done

$ fetch-urls running...$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 62: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

sub { say “url1 done”; $cv->end }

wait…receive $url2 response

receive $url1 response

run get() callback$cv->end

wait…

run get() callback

counter: 1cb: sub { … }

$cv

$ fetch-urls running... url2 done url1 done

$ fetch-urls running... url2 done

$ fetch-urls running...$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 63: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

sub { say “url1 done”; $cv->end }

wait…receive $url2 response

receive $url1 response

run get() callback$cv->end

wait…

run get() callback$cv->end

counter: 1cb: sub { … }

$cv

$ fetch-urls running... url2 done url1 done

$ fetch-urls running... url2 done

$ fetch-urls running...$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 64: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

sub { say “url1 done”; $cv->end }

wait…receive $url2 response

receive $url1 response

run get() callback$cv->end

wait…

run get() callback$cv->endcounter: 0

cb: sub { … }

$cv

$ fetch-urls running... url2 done url1 done

$ fetch-urls running... url2 done

$ fetch-urls running...$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 65: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

sub { say “url1 done”; $cv->end }

wait…receive $url2 response

receive $url1 response

run get() callback$cv->end

wait…

run get() callback$cv->endcounter: 0

cb: sub { … }

$cv

$ fetch-urls running... url2 done url1 done

$ fetch-urls running... url2 done

$ fetch-urls running...$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 66: Asynchronous programming patterns in Perl

use AnyEvent; use EV; !my $cv = AE::cv; !$cv->cb(sub { say “all urls done” }); !!$cv->begin; $ua->get($url1 => sub { say “url1 done”; $cv->end }); !$cv->begin; $ua->get($url2 => sub { say “url2 done”; $cv->end }); !say “running...” EV::run

sub { say “url1 done”; $cv->end }

wait…receive $url2 response

receive $url1 response

run get() callback$cv->end

wait…

run get() callback$cv->endcounter: 0

cb: sub { … }

$cv

$ fetch-urls running... url2 done url1 done all urls done

$ fetch-urls running... url2 done url1 done

$ fetch-urls running... url2 done

$ fetch-urls running...

sub { say “all urls done” }

$ fetch-urls

EV::run

(walk through as if you were the Perl interpreter)

Page 67: Asynchronous programming patterns in Perl

use Mojolicious::Lite; use AnyEvent; !get '/weather' => sub { my $c = shift; $c->render_later; ! my %res = (); ! my $cv = AnyEvent->condvar; $cv->cb(sub { $c->render( json => \%res, status => 200 ) } ); ! $cv->begin; $c->ua->get( 'http://demo:8080/pom' => sub { $res{pom} = pop->res->json('/text'); $cv->end } ); ! $cv->begin; $c->ua->get( 'http://demo:8081/slow/2' => sub { $res{slow} = pop->res->json('/waited'); $cv->end } ); ! $cv->begin; $c->ua->get( 'http://demo:8082/my-ip' => sub { $res{ip} = pop->res->json('/ip'); $cv->end } ); }; !app->start;

ANYEVENT

* we’ll set up a hash to catch responses, make condvar, etc. * This is raw AnyEvent programming * We could add lots of abstractions here to make this cleaner * We avoid CPS, but we have global %res * Performs well

Page 68: Asynchronous programming patterns in Perl

ANYEVENT REVIEW

primitives!

flow!

flexible!

framework agnostic

* AnyEvent provides low-level event primitives with condvars * flow is hard to see * 100% flexible to any kind of pattern, but you have to DIY * powerful and fast

Page 69: Asynchronous programming patterns in Perl

use Mojolicious::Lite; use Promises backend => ['EV'], qw/deferred collect/; !helper get_url => sub { my $c = shift; my $url = shift; my $deferred = deferred; $c->ua->get($url => sub { $deferred->resolve( pop->res->json ) }); $deferred->promise; }; !get '/weather' => sub { my $c = shift; ! collect( $c->get_url('http://demo:8080/pom'), $c->get_url('http://demo:8081/slow/2'), $c->get_url('http://demo:8082/my-ip'), )->then( sub { my ( $pom, $slow, $ip ) = map { $_->[0] } @_; $c->render(json => {pom => $pom->{text}, ip => $ip->{ip}, slow => $slow->{waited}}); }, sub { warn @_ } ); }; !app->start;

PROMISES

* The Promises module is nice if you’re coming from a JavaScript background * Better built-in error handling than AE * Better flow than AE out of the box * Performs well * Requires named functions to avoid lots of boilerplate

Page 70: Asynchronous programming patterns in Perl

PROMISES REVIEW

additional abstractions!

promises pattern!

framework agnostic

* Additional abstractions: as you move toward functional programming, Promises gets complex, but not as bad as callbacks * The promises pattern is not always intuitive * Hard to change from blocking to promises (my experience) * Lots to love, actually—framework agnostic

Page 71: Asynchronous programming patterns in Perl

MOJO::!IOLOOP::DELAY

And now the moment you’ve all been waiting for is here…

Mojo::IOLoop::Delay combines the flexibility of condvars and the ease of use of Promises.

Page 72: Asynchronous programming patterns in Perl

CALLBACK PROBLEMS

callback hell!

continuation-passing style!

flow control

Page 73: Asynchronous programming patterns in Perl

MOJO::IOLOOP::DELAY++

flat call chains!

powerful primitives like AnyEvent!

clean abstractions like Promises!

mix-n-match sequential/concurrent patterns

* Ideal choice for Mojolicious applications

Page 74: Asynchronous programming patterns in Perl

BOILERPLATE

my $delay = Mojo::IOLoop::Delay->new; $delay->steps( sub { my $d = shift; (non-blocking call) }, sub { my $d = shift; (non-blocking results) }, );

* we create the $delay object first * we call its steps() method * steps always happen sequentially

Page 75: Asynchronous programming patterns in Perl

STEPS

Steps are just subroutines you define!

Each step receives the $delay object and all of the arguments passed to it from the previous step!

The next step will not run until:!

the current step has created one or more step callbacks!

each step callback has been invoked

Page 76: Asynchronous programming patterns in Perl

CALLBACK REDUX

get '/weather' => sub { my $c = shift; $c->render_later; ! $c->ua->get(‘http://demo:8080/pom' => sub { my ($ua, $tx) = @_; my $pom = $tx->res->json('/text'); ! $c->ua->get(‘http://demo:8081/slow/2' => sub { my ($ua, $tx) = @_; my $slow = $tx->res->json('/waited'); ... }); }); };

my ($ua, $tx) = @_;

* Remember that the get() callback receives the useragent object and the transaction object.

Page 77: Asynchronous programming patterns in Perl

STEP PATTERNS

$delay->steps( sub { my $d = shift; my $end = $d->begin(0); ! $c->ua->get($url => sub { $end->(@_, ‘foo’) }); }, sub { my $d = shift; my ($ua, $tx, $foo) = @_; ! ... }, );

my $end = $d->begin(0);

make a step callback

* we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * our useragent callback is smaller * we move the result processing down to the next step * step callbacks pass their arguments to the next step

Page 78: Asynchronous programming patterns in Perl

STEP PATTERNS

$delay->steps( sub { my $d = shift; my $end = $d->begin(0); ! $c->ua->get($url => sub { $end->(@_, ‘foo’) }); }, sub { my $d = shift; my ($ua, $tx, $foo) = @_; ! ... }, );

$end->(@_, ‘foo’)

invoke the step callback

* we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * our useragent callback is smaller * we move the result processing down to the next step * step callbacks pass their arguments to the next step

Page 79: Asynchronous programming patterns in Perl

STEP PATTERNS

$delay->steps( sub { my $d = shift; my $end = $d->begin(0); ! $c->ua->get($url => sub { $end->(@_, ‘foo’) }); }, sub { my $d = shift; my ($ua, $tx, $foo) = @_; ! ... }, );

@_, ‘foo’

step callback arguments!pass to the next step

* we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * our useragent callback is smaller * we move the result processing down to the next step * step callbacks pass their arguments to the next step

Page 80: Asynchronous programming patterns in Perl

STEP PATTERNS

$delay->steps( sub { my $d = shift; my $end = $d->begin(0); ! $c->ua->get($url => sub { $end->(@_, ‘foo’) }); }, sub { my $d = shift; my ($ua, $tx, $foo) = @_; ! ... }, );

my ($ua, $tx, $foo) = @_;

step callback arguments!pass to the next step

* we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * our useragent callback is smaller * we move the result processing down to the next step * step callbacks pass their arguments to the next step

Page 81: Asynchronous programming patterns in Perl

COMPARE TO CONDVARS

AnyEvent condition variables have begin() and end() methods!

Mojo::IOLoop::Delay objects have only a begin() method which returns a subroutine reference. It is roughly equivalent to this using AnyEvent condvars:

sub begin { my $cv = shift; $cv->{counter}++; return sub { $cv->end }; }

* If we were to implement Mojo::IOLoop::Delay using AnyEvent primitives, we might come up with something like this.

Page 82: Asynchronous programming patterns in Perl

STEP PATTERNS

$delay->steps( sub { my $d = shift; my $end = $d->begin(1); ! $c->ua->get($url => sub { $end->(@_) }); }, sub { my $d = shift; my ($tx) = @_; ! ... }, );

my $end = $d->begin(1);

make a step callback

* we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * the (1) argument tells begin() how many arguments to shift off * this makes our second step cleaner * you can see our second step now only receives the $tx, not the $ua

Page 83: Asynchronous programming patterns in Perl

STEP PATTERNS

$delay->steps( sub { my $d = shift; my $end = $d->begin(1); ! $c->ua->get($url => sub { $end->(@_) }); }, sub { my $d = shift; my ($tx) = @_; ! ... }, );

$end->(@_)

invoke the step callback

* we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * the (1) argument tells begin() how many arguments to shift off * this makes our second step cleaner * you can see our second step now only receives the $tx, not the $ua

Page 84: Asynchronous programming patterns in Perl

STEP PATTERNS

$delay->steps( sub { my $d = shift; my $end = $d->begin(1); ! $c->ua->get($url => sub { $end->(@_) }); }, sub { my $d = shift; my ($tx) = @_; ! ... }, );

my ($tx) = @_;

* we create the step callback at the start of the step * we invoke step callback inside the non-blocking callback * the (1) argument tells begin() how many arguments to shift off * this makes our second step cleaner * you can see our second step now only receives the $tx, not the $ua

Page 85: Asynchronous programming patterns in Perl

STEP PATTERNS

$delay->steps( sub { my $d = shift; my $end = $d->begin; ! $c->ua->get($url => sub { $end->(@_) }); }, sub { my $d = shift; my ($tx) = @_; ! ... }, );

my $end = $d->begin;

make a step callback

* it turns out that “1” is the default for begin(); we can leave it off * Note that Mojo::IOLoop::Delay’s begin() function is richer than AnyEvent’s—it not only increments an internal counter, but it also returns the very

callback to invoke to decrement the counter.

Page 86: Asynchronous programming patterns in Perl

STEP PATTERNS

$delay->steps( sub { my $d = shift; my $end = $d->begin; ! $c->ua->get($url => sub { $end->(@_) }); }, sub { my $d = shift; my ($tx) = @_; ! ... }, );

$end->(@_)

invoke the step callback

* it turns out that “1” is the default for begin(); we can leave it off * Note that Mojo::IOLoop::Delay’s begin() function is richer than AnyEvent’s—it not only increments an internal counter, but it also returns the very

callback to invoke to decrement the counter.

Page 87: Asynchronous programming patterns in Perl

STEP PATTERNS

$delay->steps( sub { my $d = shift; $c->ua->get($url => $d->begin); }, sub { my $d = shift; my $tx = shift; ! ... }, );

$d->begin

$d->begin creates a step callback

The step callback is invoked when the!user-agent transaction response happens.

* If all you’re doing in your non-blocking callback is invoking the step callback, you can save yourself some time and boilerplate * This is a common pattern

Page 88: Asynchronous programming patterns in Perl

GETTING TO THE NEXT STEP

steps that make asynchronous calls must create a step callback before the asynchronous call, then invoke it after the call completes (i.e., in the callback):

sub { my $d = shift; my $end = $d->begin(1); $c->ua->get($url => sub { $end->(@_) }); }

sub { my $d = shift; my $end = $d->begin; $c->ua->get($url => $end); }

sub { my $d = shift; $c->ua->get($url => $d->begin); }

or save yourself some typing and create the step callback in the asynchronous call:

* these are functionally equivalent * use the top left version if you need to modify the arguments before they’re passed to the next step * use the bottom version for clarity * remember: begin() creates a step callback!

Page 89: Asynchronous programming patterns in Perl

GETTING TO THE NEXT STEP

steps that do not make asynchronous calls may use pass()

sub { my $d = shift; $d->pass(@stuff); }

sub { my $d = shift; $d->begin(0)->(@stuff); }

$d->begin(0)

* This lower one is interesting: we make a step callback here (click), then we immediately invoke it * If you’re not familiar with functional programming in Perl, this can be a little mind bending.

Page 90: Asynchronous programming patterns in Perl

GETTING TO THE NEXT STEP

steps that do not make asynchronous calls may use pass()

sub { my $d = shift; $d->pass(@stuff); }

sub { my $d = shift; $d->begin(0)->(@stuff); }

$d->begin(0)->(@stuff);

* This lower one is interesting: we make a step callback here (click), then we immediately invoke it * If you’re not familiar with functional programming in Perl, this can be a little mind bending.

Page 91: Asynchronous programming patterns in Perl

QUIZ: 1 OF 4

$delay->steps( sub { my $d = shift; ! $c->ua->get($url => $d->begin); }, sub { my $d = shift; my (?) = @_; ! ... }, );

## $tx

Will the second step ever be called? What arguments will it receive?

Page 92: Asynchronous programming patterns in Perl

QUIZ: 2 OF 4

$delay->steps( sub { my $d = shift; ! $c->ua->get($url => sub { $d->begin }); }, sub { my $d = shift; my (?) = @_; ! ... }, );

Will the second step ever be called? What arguments will it receive? Not reached! The steps callback created by $d->begin isn’t invoked until the non-blocking callback runs and by then the delay has stopped. The step callback must be created before the non-blocking call is made.

Page 93: Asynchronous programming patterns in Perl

QUIZ: 3 OF 4

$delay->steps( sub { my $d = shift; my $end = $d->begin(0); ! $c->ua->get($url => sub { $end->(@_) }); }, sub { my $d = shift; my (?) = @_; ! ... }, );

## ($ua, $tx)

Will the second step ever be called? What arguments will it receive?

Page 94: Asynchronous programming patterns in Perl

QUIZ: 4 OF 4

$delay->steps( sub { my $d = shift; my $end = $d->begin; ! $c->ua->get($url => sub { $end->(@_) }); }, sub { my $d = shift; my (?) = @_; ! ... }, );

## $tx

Will the second step ever be called? What arguments will it receive?

Page 95: Asynchronous programming patterns in Perl

QUIZ: 5 OF 4

$delay->steps( sub { my $d = shift; my $end = $d->begin(0); ! $c->ua->get($url => sub { $end->(@_) if @_ }); }, sub { my $d = shift; my (?) = @_; ! ... }, );

Will the second step ever be called? What arguments will it receive? * Maybe, if there are arguments from the callback. * This is risky. * remember: you must invoke $d->begin or $d->pass before you leave the step

Page 96: Asynchronous programming patterns in Perl

QUIZ: EXTRA CREDIT

$delay->steps( sub { my $d = shift; $d->pass(1, 2, 3); }, sub { my $d = shift; my (@nums) = @_; ! ... }, );

Will the second step ever be called? What arguments will it receive?

Page 97: Asynchronous programming patterns in Perl

QUIZ: EXTRA CREDIT

$delay->steps( sub { my $d = shift; $d->begin(1, 2, 3); }, sub { my $d = shift; my (@nums) = @_; ! ... }, );

Will the second step ever be called? What arguments will it receive? * Trick question! The answer is “no” because the step callback is never invoked.

Page 98: Asynchronous programming patterns in Perl

QUIZ: EXTRA CREDIT

$delay->steps( sub { my $d = shift; $d->begin->(1, 2, 3); }, sub { my $d = shift; my (@nums) = @_; ! ... }, );

Will the second step ever be called? What arguments will it receive?

Page 99: Asynchronous programming patterns in Perl

QUIZ: EXTRA CREDIT

$delay->steps( sub { my $d = shift; $d->begin(0)->(1, 2, 3); }, sub { my $d = shift; my (@nums) = @_; ! ... }, );

Will the second step ever be called? What arguments will it receive?

Page 100: Asynchronous programming patterns in Perl

FINISHING UP

Good job!

Page 101: Asynchronous programming patterns in Perl

CONCURRENCYuse Mojolicious::Lite; !get '/weather' => sub { my $c = shift; ! Mojo::IOLoop::Delay->new->steps( sub { my $d = shift; $c->ua->get( 'http://demo:8080/pom' => $d->begin ); $c->ua->get( 'http://demo:8081/slow/2' => $d->begin ); $c->ua->get( 'http://demo:8082/my-ip' => $d->begin ); }, sub { my $d = shift; $c->render( json => { moon => shift->res->json('/text'), slow => shift->res->json('/waited'), ip => shift->res->json('/ip') }); } ); }; !app->start;

* We can make multiple step callbacks! The next step will not run until all step callbacks have been invoked. * Notice how succinct our non-blocking callbacks are. We can make multiple, non-blocking calls in the same step, and they’re all run concurrently. * The next step will receive the results in the order they were invoked. In this case, we get three transaction objects and we can just shift them off in our render()

Page 102: Asynchronous programming patterns in Perl

SERIALIZATIONMojo::IOLoop::Delay->new->steps( sub { my $d = shift; $c->ua->get( 'http://demo:8082/my-ip' => $d->begin ); }, sub { my $d = shift; my $ip = shift->res->json('/ip'); ! $c->ua->get( "http://demo:8080/pom?ip=$ip" => $d->begin ); }, sub { my $d = shift; my $pom = shift->res->json('/text'); ! $c->ua->get( 'http://demo:8081/slow/2' => $d->begin ); }, sub { my $d = shift; $c->render( json => { slow => shift->res->json('/waited') } ); } );

* Here we demonstrate how do make non-blocking calls in a particular order, and only after the previous non-blocking call has finished. * We might want to do this if we have a call to make that depends on a previous non-blocking call. This is pretty common. * You can see the chain of events, and passed arguments.

Page 103: Asynchronous programming patterns in Perl

MICROSERVICE EXAMPLE

1. get our source IP address!

2. using the IP address, get our geolocation!

3. using the geolocation:!

• get the weather!

• get the moon phase!

• get the current air quality index (AQI)

Page 104: Asynchronous programming patterns in Perl

get '/weather' => sub { my $c = shift; $c->render_later; ! Mojo::IOLoop::Delay->new->steps( sub { $c->ua->get('http://demo:8082/my-ip' => shift->begin); }, ! sub { my $d = shift; my $ip = shift->res->json('/ip'); $c->ua->get("http://ip-api.com/json/$ip" => $d->begin); }, ! sub { my $d = shift; my $tx = shift; my ( $lat, $lon ) = ( $tx->res->json('/lat'), $tx->res->json('/lon') ); ! $c->ua->get('http://demo:8080/pom' => $d->begin); $c->ua->get("http://demo:8083/aqi/$lat/$lon" => $d->begin); $c->ua->get("http://demo:8084/weather/$lat/$lon" => $d->begin); }, ! sub { my $d = shift; ! $c->render(json => { moon => shift->res->json('/text'), aqi => shift->res->json('/index'), temp => shift->res->json('/temp') }); } ); };

* Here is the final example; we’re pulling out all the stops and making a mixture of sequential, dependent calls and concurrent, independent calls. * This isn’t bad, and I feel yields one of the clearest and least boilerplate-y approaches to event loop programming. * But what bothers me here is we have to pass our arguments from one step to another, or we have to use a variable outside of the delay object. * We can do a better.

Page 105: Asynchronous programming patterns in Perl

DELAY “STASH” VIA DATA()

scratch space!

keeps state

* data() gives us a scratch space or “stash” attached to the delay object * it keeps state for the life of the delay object, then cleans up when the delay is finished * solves the AnyEvent problem of keeping around external data structures * solves the step() problem of having to collect and pass arguments from step to step

Page 106: Asynchronous programming patterns in Perl

Mojo::IOLoop::Delay->new->steps( sub { $c->ua->get('http://demo:8082/my-ip' => shift->begin); }, ! sub { my $d = shift; $d->data(ip => shift->res->json('/ip')); $c->ua->get("http://ip-api.com/json/" . $d->data('ip') => $d->begin); }, ! sub { my $d = shift; my $tx = shift; my ( $lat, $lon ) = ( $tx->res->json('/lat'), $tx->res->json('/lon') ); $d->data(lat => $lat, lon = $lon); ! $c->ua->get('http://demo:8080/pom' => $d->begin); $c->ua->get("http://demo:8083/aqi/$lat/$lon" => $d->begin); $c->ua->get("http://demo:8084/weather/$lat/$lon" => $d->begin); }, ! sub { my $d = shift; $d->data( moon => shift->res->json('/text'), aqi => shift->res->json('/index'), temp => shift->res->json('/temp') ); ! $c->render(json => $d->data); ## also includes ip, lat, lon } );

$d->data(ip => shift->res->json('/ip'));

$d->data(lat => $lat, lon = $lon);

$d->data( moon => shift->res->json('/text'), aqi => shift->res->json('/index'), temp => shift->res->json('/temp') );

* here we store the results of each step in the data() method for the delay * this adds a little bulk without much benefit here * but it allows us to do other interesting things

Page 107: Asynchronous programming patterns in Perl

Mojo::IOLoop::Delay->new->steps( sub { $c->ua->get('http://demo:8082/my-ip' => shift->begin); }, ! sub { my $d = shift; $d->data(ip => shift->res->json('/ip')); $c->ua->get("http://ip-api.com/json/" . $d->data('ip') => $d->begin); }, ! sub { my $d = shift; my $tx = shift; my ( $lat, $lon ) = ( $tx->res->json('/lat'), $tx->res->json('/lon') ); $d->data(lat => $lat, lon = $lon); ! $c->ua->get('http://demo:8080/pom' => $d->begin); $c->ua->get("http://demo:8083/aqi/$lat/$lon" => $d->begin); $c->ua->get("http://demo:8084/weather/$lat/$lon" => $d->begin); }, ! sub { my $d = shift; $d->data( moon => shift->res->json('/text'), aqi => shift->res->json('/index'), temp => shift->res->json('/temp') ); ! $c->render(json => $d->data); ## also includes ip, lat, lon } );

$c->render(json => $d->data);

* here we store the results of each step in the data() method for the delay * this adds a little bulk without much benefit here * but it allows us to do other interesting things

Page 108: Asynchronous programming patterns in Perl

ABSTRACTING STEPShelper req_steps => sub { my ( $c, $name, $url ) = @_; ( sub { $c->ua->get( $url => shift->begin ) }, sub { shift->data( $name => shift->res->json("/$name") )->pass } ); }; !get '/weather' => sub { my $c = shift; $c->render_later; ! Mojo::IOLoop::Delay->new->steps( $c->req_steps( text => 'http://demo:8080/pom' ), $c->req_steps( waited => 'http://demo:8081/slow/2' ), $c->req_steps( ip => 'http://demo:8082/my-ip' ), sub { my $d = shift; $c->render( json => $d->data ); } ); };

* here we have a helper that returns two steps * the first step makes a non-blocking call to the URL we specify * the second step stores the results in data() with the key we gave it

* then in our controller, we call the helper for each URL, and in the last step we collect the results and render * this is great if your HTTP requests are simple, but we can do even better

Page 109: Asynchronous programming patterns in Perl

ABSTRACTING DELAYS

Can we call a delay from within a step?

* the Mojo::IOLoop is a singleton, so all events from all Delay objects are sent to the same place

Page 110: Asynchronous programming patterns in Perl

IDEAL FRAMEWORK

get '/weather' => sub { my $c = shift; $c->render_later; ! Mojo::IOLoop::Delay->new->steps( sub { get geolocation }, sub { get weather }, sub { my $d = shift; $c->render( json => shift ); } ); };

* here’s our framework we want in pseudo-code * get the geo-location * get the weather * render the response

* remember: steps receive data from the previous step if it was passed via pass() or the step callback. * what does get geolocation look like?

Page 111: Asynchronous programming patterns in Perl

GEOLOCATION STEPhelper 'get.geolocation' => sub { my $c = shift; ! sub { my $delay = shift; my $data = shift; my $delay_end = $delay->begin(0); ! Mojo::IOLoop::Delay->new->steps( sub { $c->ua->get('http://demo:8082/my-ip' => shift->begin); }, sub { my $d = shift; $d->data(ip => shift->res->json('/ip')); $c->ua->get("http://ip-api.com/json/" . $d->data('ip') => $d->begin); }, sub { my $d = shift; my $tx = shift; $d->data(lat => $tx->res->json('/lat'), lon => $tx->res->json('/lon')); $delay->data('get.geolocation' => $d->data); $delay_end->($d->data); } ); } };

* Here is our helper; remember that this helper will be running inside of a Delay object, so it needs to return a step, or sub ref. The step receives the delay object, and possibly some data from the previous step, which we can’t see here. We then run $delay->begin(0) since we may be making non-blocking calls and we want to not move on to the next step until they’re all done.

* Now we create our own delay object to: * get the IP * get the geolocation based on the IP * store the IP and lat/long into the delay data() store * pass that delay data store to the outer delay data store * pass the delay data store along to the next step, which we haven’t written yet

Page 112: Asynchronous programming patterns in Perl

WEATHER STEP Mojo::IOLoop::Delay->new->steps( sub { my $d = shift; my $tx = shift; my ( $lat, $lon ) = ( $data->{lat}, $data->{lon} ); ! $c->ua->get('http://demo:8080/pom' => $d->begin); $c->ua->get("http://demo:8083/aqi/$lat/$lon" => $d->begin); $c->ua->get("http://demo:8084/weather/$lat/$lon" => $d->begin); }, sub { my $d = shift; my ($pom_tx, $aqi_tx, $weather_tx) = @_; ! $d->data(pom => $pom_tx->res->json(‘/text'), aqi => $aqi_tx->res->json(‘/index'), temp => $weather_tx->res->json('/currently/temperature'), summary => $weather_tx->res->json('/currently/summary')); $delay->data('get.weather' => $d->data); $delay_end->($d->data); } );

* this weather step follows exactly the same pattern * we create a step/subref (not shown) * in the step we create our own Delay object * the first step uses the data from the “previous” step, which was our geolocation step * the last step marshals all the responses and returns another data structure

Page 113: Asynchronous programming patterns in Perl

OUTER DELAY AND CONTROLLER

get '/weather' => sub { my $c = shift; $c->render_later; ! Mojo::IOLoop::Delay->new->steps( $c->get->geolocation, $c->get->weather, sub { my $d = shift; $c->render( json => shift ); } ); };

* Now we’re cooking with panache! * We can see the power of this pattern:

* we could build libraries of these functions * each has its own function signatures: expected input and outputs, those could be checked, add error handling, etc.

* The price of this simplicity was a bit of boilerplate in the helpers, but that could be easily abstracted with a plugin * tempire will get right on that

Page 114: Asynchronous programming patterns in Perl

MOJO::IOLOOP::DELAYis teh awesome!

Page 115: Asynchronous programming patterns in Perl

HIRE ME!

I’m looking for something great—with Perl!!

Email: [email protected]!

Voice/sms: +1.801.796.2484