X-Git-Url: https://git.brokenzipper.com/gitweb?a=blobdiff_plain;f=slides.html;h=745bf127db87dedbe3ae370155dfe02d9c935c1b;hb=HEAD;hp=8904bd24addaf2a9151822c1795c18636db47498;hpb=e9a895bf42ef9b706aba2d2770ae61dad28ea9a4;p=chaz%2Ftalk-event-driven-programming-in-perl diff --git a/slides.html b/slides.html index 8904bd2..745bf12 100644 --- a/slides.html +++ b/slides.html @@ -444,6 +444,12 @@ while (1) { } ``` +??? +Of course, you don't actually need to know anything about which syscalls are used and how a reactor actually works to do +event-driven programming. (and if any of this is going over your head, that's fine.) + +But I'm covering it because I think it's good for you. + --- class: ex-basicreactor2 @@ -464,7 +470,6 @@ while (1) { ``` --- - ## Reactor examples on CPAN .big[ @@ -480,6 +485,406 @@ while (1) { 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. + +--- +class: ex-poe + +## `POE` + +```perl +use POE; +use Time::HiRes qw(time); + +POE::Session->create( + inline_states => { + _start => sub { + $_[KERNEL]->delay(tick => 5); + }, + + tick => \&handle_timer_event, + }, +); + +POE::Kernel->run; +``` + +??? +POE: +- The oldest, released 1998 +- Parallel processing was it's primary use. + +--- +class: ex-poe + +## `POE` + +```perl +*use POE; +use Time::HiRes qw(time); + +POE::Session->create( + inline_states => { + _start => sub { + $_[KERNEL]->delay(tick => 5); + }, + + tick => \&handle_timer_event, + }, +); + +POE::Kernel->run; +``` + +??? +Using `POE` will implicitly load all the modules you'll need to actually do anything. +- In this case, `POE::Session` and `POE::Kernel`. + +--- +class: ex-poe + +## `POE` + +```perl +use POE; +use Time::HiRes qw(time); + +POE::Session->create( + inline_states => { + _start => sub { + $_[KERNEL]->delay(tick => 5); + }, + + tick => \&handle_timer_event, + }, +); + +*POE::Kernel->run; +``` + +??? +Run the reactor. + +In POE, the kernel is the thing that manages processes AKA sessions. + +--- +class: ex-poe + +## `POE` + +```perl +use POE; +use Time::HiRes qw(time); + +*POE::Session->create( +* inline_states => { +* _start => sub { +* $_[KERNEL]->delay(tick => 5); +* }, +* +* tick => \&handle_timer_event, +* }, +*); + +POE::Kernel->run; +``` + +??? + +Sessions can be created to do stuff. + +--- +class: ex-ioasync + +## `IO::Async` + +```perl +use IO::Async::Loop; +use IO::Async::Timer::Countdown; + +my $loop = IO::Async::Loop->new; + +my $timer = IO::Async::Timer::Countdown->new( + delay => 5, # seconds + on_expire => \&handle_timer_event, +); +$timer->start; + +$loop->add($timer); + +$loop->run; +``` + +--- +class: ex-ioasync + +## `IO::Async` + +```perl +use IO::Async::Loop; +use IO::Async::Timer::Countdown; + +*my $loop = IO::Async::Loop->new; + +my $timer = IO::Async::Timer::Countdown->new( + delay => 5, # seconds + on_expire => \&handle_timer_event, +); +$timer->start; + +$loop->add($timer); + +$loop->run; +``` + +??? +Create the loop +- IO::Async doesn't seem to have a concept of a "default" loop. + - The user has control over starting the loop, as usual, but it does mean that modules can't really register + themselves. + +--- +class: ex-ioasync + +## `IO::Async` + +```perl +use IO::Async::Loop; +use IO::Async::Timer::Countdown; + +my $loop = IO::Async::Loop->new; + +my $timer = IO::Async::Timer::Countdown->new( + delay => 5, # seconds + on_expire => \&handle_timer_event, +); +$timer->start; + +*$loop->add($timer); + +*$loop->run; +``` + +??? +Add your handler to the loop, and of course run the loop. + +--- +class: ex-ioasync + +## `IO::Async` + +```perl +use IO::Async::Loop; +use IO::Async::Timer::Countdown; + +my $loop = IO::Async::Loop->new; + +my $timer = IO::Async::Timer::Countdown->new( + delay => 5, # seconds + on_expire => \&handle_timer_event, +); +*$timer->start; + +$loop->add($timer); + +$loop->run; +``` + +??? +Remember to actually start your timer! The timer facilities in other loops seem to do this for you, but this does give you more control. +- For example, you can call `reset` on the timer to set it back it it's initial delay value. + +--- +class: ex-mojoioloop + +## `Mojo::IOLoop` + +```perl +use Mojo::IOLoop; + +Mojo::IOLoop->timer(5 => \&handle_timer_event); + +Mojo::IOLoop->start if ! Mojo::IOLoop->is_running; +``` + +--- +class: ex-mojoioloop + +## `Mojo::IOLoop` + +```perl +use Mojo::IOLoop; + +*Mojo::IOLoop->timer(5 => \&handle_timer_event); + +Mojo::IOLoop->start if ! Mojo::IOLoop->is_running; +``` + +??? +Create the timer... easy. + +--- +class: ex-mojoioloop + +## `Mojo::IOLoop` + +```perl +use Mojo::IOLoop; + +Mojo::IOLoop->timer(5 => \&handle_timer_event); + +*Mojo::IOLoop->start if ! Mojo::IOLoop->is_running; +``` + +??? +And start the loop. Very easy. + +- `Mojo::IOLoop` provides methods for creating network clients and servers without much code. +- Doesn't seem to have support for demultiplexing signals, but if this is important you can probably set that up + directly with the `EV` reactor back end. + +--- +class: ex-anyevent + +## `AnyEvent` + +```perl +use EV; +use AE; + +my $timer = AE::timer(5, 0 => \&handle_timer_event); + +EV::run(); +``` + +--- +class: ex-anyevent + +## `AnyEvent` + +```perl +*use EV; +use AE; + +my $timer = AE::timer(5, 0 => \&handle_timer_event); + +*EV::run(); +``` + +--- +class: ex-anyevent + +## `AnyEvent` + +```perl +*use Glib; +use AE; + +*my $loop = Glib::MainLoop->new; + +my $timer = AE::timer(5, 0 => \&handle_timer_event); + +*$loop->run; +``` + +--- +class: ex-anyevent + +## `AnyEvent` + +```perl +use Glib; +use AE; + +my $loop = Glib::MainLoop->new; + +*my $timer = AE::timer(5, 0 => \&handle_timer_event); + +$loop->run; +``` + +??? +Create your timer. + +By the way, the second argument to `AE::timer` represents the interval if you want a periodic timer. + +- Remember to save the return value of setting up your timer and other handles. + - That thing is actually called a "watcher", and the destruction of watcher objects is how event handlers get + unregistered. + +`AnyEvent` technically has two APIs. This is the "simplified" API. + +--- +class: ex-anyevent + +## `AnyEvent` + +```perl +use Glib; +*use AnyEvent; + +my $loop = Glib::MainLoop->new; + +*my $timer = AnyEvent->timer( +* after => 5, +* cb => \&handle_timer_event, +*); + +$loop->run; +``` + +??? +This is what the "complicated" API looks like. + --- name: not-all-roses class: center, middle @@ -530,6 +935,9 @@ $promise->on_done(sub { ... }); $promise->on_fail(sub { ... }); ``` +??? +More on this later. + --- class: center, middle @@ -566,7 +974,152 @@ Look for `EPIPE` from syscalls (like [`write`](http://man.he.net/man2/write)) in --- class: center, middle -## Use [`Future::AsyncAwait`](https://metacpan.org/pod/Future::AsyncAwait). +## 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. @@ -617,11 +1170,99 @@ async sub do_two_things ??? 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) }; +``` + +--- +class: center, middle + +## Final thoughts + +--- +class: center, middle + +### Proactor pattern + +??? +We've gone over the Reactor pattern quite a bit, and for good reason. + +It's the predominant implementation of event-driven code in userspace apps. + +But you should also know about the Proactor pattern. + +It's basically the same as the reactor pattern except the event handlers perform asynchronous operations. +- Can be done using special kernel facilities +- or by keeping a thread pool. + +One thing to keep in mind about the reactor pattern is that any sort of blocking that occurs by any event handlers will +slow everything down quite a bit. The proactor pattern can help avoid problems. + +--- +class: center, middle + +### Event-driven programming architecture + +??? +Making your apps reactive and able to generate and respond to events is the tip of a very large iceburg. + +- Pubsub systems can be used to distribute events at a massive scale. +- Message queues are components that can be plugged in to provide guaranteed delivery of events. + +Once your programs are event-driven, they're ready to be plugged into a whole world of other services. + +--- +class: center, middle + +### Lots of interconnected devices + +??? +This concept has a trendy name, but I can't say it because I've swarn off buzzwords. + +Event-driven programming and architecture is used as a foundation for building APIs and applications that scale, if +that's important to you. -## Events in the world +You can avoid coupling: When one of your devices has an event, it just needs to notify your world of devices and let the +devices decide what to do. +For example, if your car has connected sensors, it can notify your devices when you leave the car. Then your phone can +receive that event and notify you that you left your kid in the car. +So there are a lot of cool applications for this stuff. --- class: center, middle