X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=slides.html;h=161851a13bf2d9f2c16eb9e5a8068a2ae94d68cc;hb=afb7de1b61d2bf725c70e8f6be00592f1b762e82;hp=9eae8008ce65f65a0a2751af948d93738c50c4ee;hpb=1e5589cbb1518565f28a66bafd4882eec1d9acd5;p=chaz%2Ftalk-event-driven-programming-in-perl diff --git a/slides.html b/slides.html index 9eae800..161851a 100644 --- a/slides.html +++ b/slides.html @@ -54,6 +54,9 @@ my $pizza = prepare_and_bake($order, $ingredentials); print($pizza); ``` +??? +But some programs are long-lived. + --- ## Event-driven programs @@ -70,6 +73,19 @@ But there are some complications and things to knows, which is why this talk exi --- class: center, middle +![Help desk](img/helpdesk.jpg) + +.small[ +Image by Soniachat8. +This image is licensed under the [Creative Commons Attribution-Share Alike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/deed.en) license. +] + +--- +name: graph-eventloop +class: center, middle + +## Event loop + ![Event loop](img/eventloop.svg) ??? @@ -364,13 +380,55 @@ class: syscalls ### syscalls - [`pause`](http://man.he.net/man2/pause) - Sleeps until signal + +-- - [`select`](http://man.he.net/man2/select), [`poll`](http://man.he.net/man2/poll), [`epoll`](http://man.he.net/man7/epoll), [`kqueue`](https://www.freebsd.org/cgi/man.cgi?format=ascii&sektion=2&query=kqueue) - Monitor multiple file descriptors + +-- - [`clock_gettime`](http://man.he.net/man2/clock_gettime) - What time is it now? --- -class: ex-basicreactor +class: center, middle + +## Reactor pattern + +--- +## Reactor pattern + +.big[ +- Queues events asynchronously. +- Demultiplexes and dispatches synchronously. +] + +--- +name: graph-reactor +class: center, middle + +## Reactor pattern + +![Reactor](img/reactor.svg) + +--- +class: ex-basicreactor1 + +## Using a reactor + +```perl +my $reactor = My::Reactor->new; + +# Set up event handlers +$reactor->slurp_file($filepath, \&handle_slurp_event); +$reactor->timer(5, \&handle_timer_event); +$reactor->listen($socket, \&handle_new_connect_event); +... + +$reactor->run_loop; +``` + +--- +class: ex-basicreactor2 -## The basic reactor +## The basic reactor implementation ```perl our $timers = [...]; @@ -387,7 +445,25 @@ while (1) { ``` --- +class: ex-basicreactor2 + +## The basic reactor implementation + +```perl +our $timers = [...]; +our $io_handles = [...]; + +while (1) { + my $next_timer = find_next_timer($timers); + +* poll($io_handles, $next_timer->time_from_now); + + handle_ready_io_handles($io_handles); + handle_expired_timers($timers); +} +``` +--- ## Reactor examples on CPAN .big[ @@ -399,6 +475,394 @@ while (1) { - [`Mojo::Reactor::Poll`](https://metacpan.org/source/Mojo::Reactor::Poll) ] +??? +These links, which will be available to you with the slides, link directly to the source code of these modules on +metacpan so you can take a look at how they work. + +--- +## Reactors + +.col.big[ +- [`Cocoa::EventLoop`](https://metacpan.org/pod/Cocoa::EventLoop) +- [`EV`](https://metacpan.org/pod/EV) +- [`Event::Lib`](https://metacpan.org/pod/Event::Lib) +- [`Event`](https://metacpan.org/pod/Event) +- [`FLTK`](https://metacpan.org/pod/FLTK) +] +.col.big[ +- [`Glib`](https://metacpan.org/pod/Glib), [`Gtk`](https://metacpan.org/pod/Gtk), [`Gtk2`](https://metacpan.org/pod/Gtk2) +- [`Tk`](https://metacpan.org/pod/Tk) +- [`UV`](https://metacpan.org/pod/UV) +- [`Wx`](https://metacpan.org/pod/Wx) +- probably more... +] + +??? +I'm not going to go over any of these. + +- You can use any one of these directly. +- Some of that are better than others (obvious). +- Which one you choose may depend one what you're building. + - If you're building a GUI, your choice is made for you. + - High-concurrency network application, `EV` is a good choice. + +But actually you may not need to pick one... + +--- +class: center, middle + +## Reactor "front ends" + +??? +By my count there are four main front ends. + +--- +## Reactor "front ends" + +.big[ +- [**`POE`**](https://metacpan.org/pod/POE) - Portable multitasking and networking framework for any event loop (or Perl, Objects, and Events) +- [**`IO::Async`**](https://metacpan.org/pod/IO::Async) - Asynchronous event-driven programming +- [**`Mojo::IOLoop`**](https://metacpan.org/pod/Mojo::IOLoop) - Minimalistic event loop +- [**`AnyEvent`**](https://metacpan.org/pod/AnyEvent) - The DBI of event loop programming +] + +??? +The benefit of using one of these rather than the reactors themselves is that your code will automatically work with any +of a number of supported reactors. + +POE: +- The oldest, released 1998 +- Parallel processing was it's primary use. +- Everything center by the "wheel" (loop). +- You add "handles" to the object. +- Each type of handle has certain events it knows how to handle. + +--- +name: not-all-roses +class: center, middle + +![Thorns](img/thorn.jpg) + +## Watch out for the thorns... + +??? +There are some special considerations you need to take when writing event-driven code. + +--- +class: center, middle + +## Exceptions for error handling + +--- +class: center, middle + +### Problem: No exception handler up the call stack + +--- +class: ex-exceptions + +## Rule: Don't die/throw in event handlers. + +-- +### Error callback pattern + +```perl +do_something_asynchronously( + callback => sub { ... }, + on_error => sub { ... }, +); +``` + +--- +class: ex-exceptions + +## Rule: Don't die/throw in event handlers. + +### Use promises + +```perl +my $promise = do_something_asynchronously(); + +$promise->on_done(sub { ... }); +$promise->on_fail(sub { ... }); +``` + +??? +More on this later. + +--- +class: center, middle + +## `SIGPIPE` + +--- +class: sigpipe +## `SIGPIPE` + +- Sent to your program when it writes to a pipe that was closed. + +-- +- Default signal handler terminates the program. + +--- +class: ex-sigpipe + +## Solution: Ignore `SIGPIPE` + +```perl +$SIG{PIPE} = 'IGNORE'; +``` + +??? +Some event loops do this for you. + +-- +.big[ +Look for `EPIPE` from syscalls (like [`write`](http://man.he.net/man2/write)) instead. + +(You *are* checking return codes from your system calls... right?) +] + +--- +class: center, middle + +## Debugging event-driven code + +--- +class: ex-debugging + +## Debugging event-driven code + +.big[ +- Print debug info to `STDERR`. +] + +```bash +export PERL_FUTURE_DEBUG=1 +# and +export ANYEVENT_DEBUG=8 +# or +export MOJO_IOLOOP_DEBUG=1 +# etc. +``` + +--- +## Debugging event-driven code + +.big[ +- Print debug info to `STDERR`. +- Check out [`AnyEvent::Debug`](https://metacpan.org/pod/AnyEvent::Debug). +] + +--- +## Debugging event-driven code + +.big[ +- Print debug info to `STDERR`. +- Check out [`AnyEvent::Debug`](https://metacpan.org/pod/AnyEvent::Debug). +- Use [`perl5db.pl`](https://metacpan.org/pod/perl5db.pl) and other `Devel::` debuggers. +] + +??? +Traditional debuggers are still useful for event-driven code. +- Be sure to carefully avoid memory leaks -- they are more devastating in long-lived programs which event-driven +programs tend to be. + +--- +class: center, middle + +## Promises: +### Proxy objects for future values + +??? +Proxy objects for values that have not yet been retrieved or computed. + +- In some cases, promises can provide you an alternative, often more useful interface to callbacks. + +--- +class: ex-future + +## Basic usage (using [`Future`](https://metacpan.org/pod/Future)) + +```perl +my $future = fetch_remote_file($url); + +$future->on_done(sub($filename, $contents) { + print "Fetched $filename\n"; +}); +``` + +??? +The future is an object that stands in for the thing we actually want until the thing we want is available. + +-- +```perl +$future->on_fail(sub($error) { + warn $error; +}); +``` + +??? +If the operation that provides the `Future` isn't able to fulfill its promise to provide us what we want, the `Future` +may be set to a "fail" state. + +- For any operation that can fail (and that includes almost everything), it's a good idea to handle errors. + +--- +class: ex-future + +## Chaining futures + +```perl +my $future = fetch_remote_file($url) + ->then(sub($filename, $contents) { + my $another_future = write_local_file("/tmp/$filename", + $contents); + return $another_future; + }); + +$future->on_done(sub($filepath) { + print "Saved file to $filepath\n"; +}); + +$future->on_fail(sub($error) { + warn $error; +}); +``` + +--- +class: ex-future2 + +## Creating futures + +```perl +use AnyEvent::HTTP; +use Future; + +sub fetch_remote_file($url) { + my $future = Future->new; + + http_get $url => sub($data, $headers) { + if ($headers->{Status} =~ /^[23]/) { + my ($filename) = get_filename($headers); + $future->done($filename, $data); + } + else { + $future->fail("Failed to fetch file: $headers->{Reason}"); + } + }; + + return $future; +} +``` + +--- +class: center, middle + +Check out [Paul Evan's blog](http://leonerds-code.blogspot.com/2013/12/futures-advent-day-1.html) for more things you can do with `Future`s. + +And, of course, [read the pod](https://metacpan.org/pod/Future). + +??? +Paul did an advent calendar with short posts detailing the things you can do with `Future`. + +- It's really well done. + +--- +class: center, middle + +## There's also [`Future::AsyncAwait`](https://metacpan.org/pod/Future::AsyncAwait). + +??? +If you have used JavaScript recently, you may have used its "async/await" feature to clean up your non-blocking code. + +--- +class: center, middle + +### Yes, Perl can do it, too! + +--- +class: ex-asyncawait + +## Without async and await + +```perl +use Future; + +sub do_two_things { + return do_first_thing()->then(sub { + my $first = shift; + + return do_second_thing($first)->then(sub { + my $second = shift; + + return Future->done([$first, $second]); + }); + }); +} +``` + +--- +class: ex-asyncawait + +## With async and await + +```perl +use Future::AsyncAwait; + +async sub do_two_things +{ + my $first = await do_first_thing(); + + my $second = await do_second_thing($first); + + return [$first, $second]; +} +``` + +??? +There are caveats: Localized variable assignments don't work, nor anything that has implied local-like behavior. + +--- +class: center, middle + +## Finally, there's also [`Future::Utils`](https://metacpan.org/pod/Future::Utils). + +--- +class: ex-future2 + +## Call + +```perl +my $future = call { + do_stuff(); + + ... + + my $future = ...; + return $future; +}; +``` + +Any exceptions throw in the code block are caught and become a failed `Future`. + +--- +class: ex-future2 + +## Loops and stuff + +```perl +use Future::Utils qw(repeat); + +my $eventual_future = repeat { + my $trial_future = ... + return $trial_future; +} while => sub($last_trial_future) { return should_keep_going($last_trial_future) }; +``` + +--- +## Events in the world + --- class: center, middle name: conclusion