2 <html><head><meta charset=
"utf-8"><title>Event-driven Programming in Perl
</title><link rel=
"stylesheet" href=
"css/common.css"><link rel=
"stylesheet" href=
"css/slides.css"></head><body><textarea id=
"source">
7 # Event-driven Programming in Perl
12 - Hi. I'm Charles McGarvey.
13 - Been a Perl programmer for a long time...
20 - My employer is hiring.
21 - It's a pretty cool employer...
24 ## "Normal" [userspace] programs
30 4. Write results as output.
35 background-image: url(img/pizza.jpg)
40 # orderpizza --crust-type=thin \
41 --toppings=cheese,pepperoni,mushrooms
46 # source code of the orderpizza program
48 my $order = get_order_from_args(@ARGV);
50 my $ingredients = gather_ingredients($order);
52 my $pizza = prepare_and_bake($order, $ingredentials);
58 But some programs are long-lived.
61 ## Event-driven programs
65 2. When events happen, some code runs and does something.
69 At it's core, event-driven programming is this simple.
71 But there are some complications and things to knows, which is why this talk exists.
76 ![Help desk](img/helpdesk.jpg)
80 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.
89 ![Event loop](img/eventloop.svg)
92 We'll refine this model as we go.
95 class: ex-hwinterrupts
97 ## Hardware interrupts
100 # cat /proc/interrupts
102 0:
51 0 IR-IO-APIC
2-edge timer
103 1:
685006 5 IR-IO-APIC
1-edge i8042
104 8:
0 0 IR-IO-APIC
8-edge rtc0
105 9:
1724419 6314 IR-IO-APIC
9-fasteoi acpi
106 12:
12300601 138 IR-IO-APIC
12-edge i8042
107 16:
0 0 IR-IO-APIC
16-fasteoi i801_smbus
108 120:
0 0 DMAR-MSI
0-edge dmar0
109 121:
0 0 DMAR-MSI
1-edge dmar1
110 122:
7009890 45112 IR-PCI-MSI
327680-edge xhci_hcd
111 123:
44 3 IR-PCI-MSI
360448-edge mei_me
112 124:
509 0 IR-PCI-MSI
1048576-edge rtsx_pci
113 125:
80130 0 IR-PCI-MSI
2621440-edge nvme0q0, nvme0q1
114 126:
121892439 2961357 IR-PCI-MSI
32768-edge i915
115 127:
49 100 IR-PCI-MSI
514048-edge snd_hda_intel:card0
116 128:
0 79412 IR-PCI-MSI
2621441-edge nvme0q2
122 When a HW interrupt happens, the appropriate code to run is locate in the interrupt service routine
127 ## Your first event-driven program
131 print "Press
<Enter> to continue.";
134 handle_enter_keypress_event();
141 ## Your first event-driven program
145 print "Press
<Enter> to continue.";
148 handle_enter_keypress_event();
159 ## Your first event-driven program
163 print "Press
<Enter> to continue.";
166 handle_enter_keypress_event();
178 ## Your first event-driven program
182 print "Press
<Enter> to continue.";
185 *handle_enter_keypress_event();
203 $SIG{USR1} = sub { handle_usr1_signal_event() };
204 $SIG{ALRM} = sub { handle_timer_event() };
205 $SIG{QUIT} = sub { exit() };
209 while (
1) { POSIX::pause() }
220 *$SIG{USR1} = sub { handle_usr1_signal_event() };
221 *$SIG{ALRM} = sub { handle_timer_event() };
222 *$SIG{QUIT} = sub { exit() };
226 while (
1) { POSIX::pause() }
230 Notice that this program is capable of handling more than just one event handler at a time.
240 $SIG{USR1} = sub { handle_usr1_signal_event() };
241 $SIG{ALRM} = sub { handle_timer_event() };
242 $SIG{QUIT} = sub { exit() };
246 *while (
1) { POSIX::pause() }
250 - Program yields time to the kernel
254 This example has a loop, but it's not really doing anything.
256 But actually it is doing something very important: It's yielding its processing time to the kernel.
258 And kernel informs the program of events.
260 You might be able to use `sleep` as well, but some implementations of libc
261 in the past have implemented `sleep` using `alarm`.
263 Don't know how common that is nowadays, but old habbits...
268 ## Graphical user interface example
273 my $window = Gtk3::Window-
>new;
275 $window-
>signal_connect('key-press-event' =
> \&handle_keypress_event);
276 $window-
>signal_connect('delete-event' =
> \&Gtk3::main_quit);
284 A user interface happens to be a good place to use evented code because humans are spontaneous and unpredictable.
289 ## Graphical user interface example
294 my $window = Gtk3::Window-
>new;
296 *$window-
>signal_connect('key-press-event' =
> \&handle_keypress_event);
297 *$window-
>signal_connect('delete-event' =
> \&Gtk3::main_quit);
311 ## Graphical user interface example
316 my $window = Gtk3::Window-
>new;
318 $window-
>signal_connect('key-press-event' =
> \&handle_keypress_event);
319 $window-
>signal_connect('delete-event' =
> \&Gtk3::main_quit);
328 - Yield and wait for events
339 Data available on a socket, or a new connection.
341 Server or client, data across a wire cannot typically be relied upon to arrive in a predictable fashion, so an
342 event-driven architect makes a lot of sense for network applications.
348 As far as my program is concerned, it can receive a signal or message from another program at any time.
354 If I need something to happen to happen in five minutes or at a specific absolute time, using the idea of an alarm clock
355 is tempting. I can set an alarm and pretend that the clock itself is a source of events.
361 Human beings of course are masters of spontaneity.
363 Are they going to press a button on the keyboard next, or move the mouse? If my program is connected to a microphone,
364 maybe the human is going to start talking to the program. The program has to be ready for anything, so defining and
365 accepting "events" for all the different ways that a human can interact with the program is a good way to go.
368 - Anything that can happen spontaneously in the real world.
371 Lots of other external systems (besides humans) can "generate" events.
373 Lot of this requires kernel facilities. Speaking of which, how are these types of things implemented?
378 ## How event-driven userspace code works
382 - [`pause`](http://man.he.net/man2/pause) - Sleeps until signal
385 - [`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
388 - [`clock_gettime`](http://man.he.net/man2/clock_gettime) - What time is it now?
391 class: center, middle
399 - Queues events asynchronously.
400 - Demultiplexes and dispatches synchronously.
405 class: center, middle
409 ![Reactor](img/reactor.svg)
412 class: ex-basicreactor1
417 my $reactor = My::Reactor-
>new;
419 # Set up event handlers
420 $reactor-
>slurp_file($filepath, \&handle_slurp_event);
421 $reactor-
>timer(
5, \&handle_timer_event);
422 $reactor-
>listen($socket, \&handle_new_connect_event);
429 class: ex-basicreactor2
431 ## The basic reactor implementation
435 our $io_handles = [...];
438 my $next_timer = find_next_timer($timers);
440 poll($io_handles, $next_timer-
>time_from_now);
442 handle_ready_io_handles($io_handles);
443 handle_expired_timers($timers);
448 class: ex-basicreactor2
450 ## The basic reactor implementation
454 our $io_handles = [...];
457 my $next_timer = find_next_timer($timers);
459 * poll($io_handles, $next_timer-
>time_from_now);
461 handle_ready_io_handles($io_handles);
462 handle_expired_timers($timers);
467 ## Reactor examples on CPAN
470 - [`POE::Loop::IO_Poll`](https://metacpan.org/source/POE::Loop::IO_Poll)
471 - [`POE::Loop::Select`](https://metacpan.org/source/POE::Loop::Select)
472 - [`AnyEvent::Loop`](https://metacpan.org/source/AnyEvent::Loop)
473 - [`IO::Async::Loop::Poll`](https://metacpan.org/source/IO::Async::Loop::Poll)
474 - [`IO::Async::Loop::Select`](https://metacpan.org/source/IO::Async::Loop::Select)
475 - [`Mojo::Reactor::Poll`](https://metacpan.org/source/Mojo::Reactor::Poll)
479 These links, which will be available to you with the slides, link directly to the source code of these modules on
480 metacpan so you can take a look at how they work.
486 - [`Cocoa::EventLoop`](https://metacpan.org/pod/Cocoa::EventLoop)
487 - [`EV`](https://metacpan.org/pod/EV)
488 - [`Event::Lib`](https://metacpan.org/pod/Event::Lib)
489 - [`Event`](https://metacpan.org/pod/Event)
490 - [`FLTK`](https://metacpan.org/pod/FLTK)
493 - [`Glib`](https://metacpan.org/pod/Glib), [`Gtk`](https://metacpan.org/pod/Gtk), [`Gtk2`](https://metacpan.org/pod/Gtk2)
494 - [`Tk`](https://metacpan.org/pod/Tk)
495 - [`UV`](https://metacpan.org/pod/UV)
496 - [`Wx`](https://metacpan.org/pod/Wx)
501 I'm not going to go over any of these.
503 - You can use any one of these directly.
504 - Some of that are better than others (obvious).
505 - Which one you choose may depend one what you're building.
506 - If you're building a GUI, your choice is made for you.
507 - High-concurrency network application, `EV` is a good choice.
509 But actually you may not need to pick one...
512 class: center, middle
514 ## Reactor "front ends"
517 By my count there are four main front ends.
520 ## Reactor "front ends"
523 - [**`POE`**](https://metacpan.org/pod/POE) - Portable multitasking and networking framework for any event loop (or Perl, Objects, and Events)
524 - [**`IO::Async`**](https://metacpan.org/pod/IO::Async) - Asynchronous event-driven programming
525 - [**`Mojo::IOLoop`**](https://metacpan.org/pod/Mojo::IOLoop) - Minimalistic event loop
526 - [**`AnyEvent`**](https://metacpan.org/pod/AnyEvent) - The DBI of event loop programming
530 The benefit of using one of these rather than the reactors themselves is that your code will automatically work with any
531 of a number of supported reactors.
540 use Time::HiRes qw(time);
542 POE::Session-
>create(
545 $_[KERNEL]-
>delay(tick =
> 5);
548 tick =
> \&handle_timer_event,
557 - The oldest, released
1998
558 - Parallel processing was it's primary use.
567 use Time::HiRes qw(time);
569 POE::Session-
>create(
572 $_[KERNEL]-
>delay(tick =
> 5);
575 tick =
> \&handle_timer_event,
583 Using `POE` will implicitly load all the modules you'll need to actually do anything.
584 - In this case, `POE::Session` and `POE::Kernel`.
593 use Time::HiRes qw(time);
595 POE::Session-
>create(
598 $_[KERNEL]-
>delay(tick =
> 5);
601 tick =
> \&handle_timer_event,
611 In POE, the kernel is the thing that manages processes AKA sessions.
620 use Time::HiRes qw(time);
622 *POE::Session-
>create(
625 * $_[KERNEL]-
>delay(tick =
> 5);
628 * tick =
> \&handle_timer_event,
637 Sessions can be created to do stuff.
646 use IO::Async::Timer::Countdown;
648 my $loop = IO::Async::Loop-
>new;
650 my $timer = IO::Async::Timer::Countdown-
>new(
651 delay =
> 5, # seconds
652 on_expire =
> \&handle_timer_event,
668 use IO::Async::Timer::Countdown;
670 *my $loop = IO::Async::Loop-
>new;
672 my $timer = IO::Async::Timer::Countdown-
>new(
673 delay =
> 5, # seconds
674 on_expire =
> \&handle_timer_event,
685 - IO::Async doesn't seem to have a concept of a "default" loop.
686 - The user has control over starting the loop, as usual, but it does mean that modules can't really register
696 use IO::Async::Timer::Countdown;
698 my $loop = IO::Async::Loop-
>new;
700 my $timer = IO::Async::Timer::Countdown-
>new(
701 delay =
> 5, # seconds
702 on_expire =
> \&handle_timer_event,
712 Add your handler to the loop, and of course run the loop.
721 use IO::Async::Timer::Countdown;
723 my $loop = IO::Async::Loop-
>new;
725 my $timer = IO::Async::Timer::Countdown-
>new(
726 delay =
> 5, # seconds
727 on_expire =
> \&handle_timer_event,
737 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.
738 - For example, you can call `reset` on the timer to set it back it it's initial delay value.
748 Mojo::IOLoop-
>timer(
5 =
> \&handle_timer_event);
750 Mojo::IOLoop-
>start if ! Mojo::IOLoop-
>is_running;
761 *Mojo::IOLoop-
>timer(
5 =
> \&handle_timer_event);
763 Mojo::IOLoop-
>start if ! Mojo::IOLoop-
>is_running;
767 Create the timer... easy.
777 Mojo::IOLoop-
>timer(
5 =
> \&handle_timer_event);
779 *Mojo::IOLoop-
>start if ! Mojo::IOLoop-
>is_running;
783 And start the loop. Very easy.
785 - `Mojo::IOLoop` provides methods for creating network clients and servers without much code.
786 - Doesn't seem to have support for demultiplexing signals, but if this is important you can probably set that up
787 directly with the `EV` reactor back end.
798 my $timer = AE::timer(
5,
0 =
> \&handle_timer_event);
812 my $timer = AE::timer(
5,
0 =
> \&handle_timer_event);
826 *my $loop = Glib::MainLoop-
>new;
828 my $timer = AE::timer(
5,
0 =
> \&handle_timer_event);
842 my $loop = Glib::MainLoop-
>new;
844 *my $timer = AE::timer(
5,
0 =
> \&handle_timer_event);
852 By the way, the second argument to `AE::timer` represents the interval if you want a periodic timer.
854 - Remember to save the return value of setting up your timer and other handles.
855 - That thing is actually called a "watcher", and the destruction of watcher objects is how event handlers get
858 `AnyEvent` technically has two APIs. This is the "simplified" API.
869 my $loop = Glib::MainLoop-
>new;
871 *my $timer = AnyEvent-
>timer(
873 * cb =
> \&handle_timer_event,
880 This is what the "complicated" API looks like.
884 class: center, middle
886 ![Thorns](img/thorn.jpg)
888 ## Watch out for the thorns...
891 There are some special considerations you need to take when writing event-driven code.
894 class: center, middle
896 ## Exceptions for error handling
899 class: center, middle
901 ### Problem: No exception handler up the call stack
906 ## Rule: Don't die/throw in event handlers.
909 ### Error callback pattern
912 do_something_asynchronously(
913 callback =
> sub { ... },
914 on_error =
> sub { ... },
921 ## Rule: Don't die/throw in event handlers.
926 my $promise = do_something_asynchronously();
928 $promise-
>on_done(sub { ... });
929 $promise-
>on_fail(sub { ... });
936 class: center, middle
944 - Sent to your program when it writes to a pipe that was closed.
947 - Default signal handler terminates the program.
952 ## Solution: Ignore `SIGPIPE`
955 $SIG{PIPE} = 'IGNORE';
959 Some event loops do this for you.
963 Look for `EPIPE` from syscalls (like [`write`](http://man.he.net/man2/write)) instead.
965 (You *are* checking return codes from your system calls... right?)
969 class: center, middle
971 ## Debugging event-driven code
976 ## Debugging event-driven code
979 - Print debug info to `STDERR`.
983 export PERL_FUTURE_DEBUG=
1
985 export ANYEVENT_DEBUG=
8
987 export MOJO_IOLOOP_DEBUG=
1
992 ## Debugging event-driven code
995 - Print debug info to `STDERR`.
996 - Check out [`AnyEvent::Debug`](https://metacpan.org/pod/AnyEvent::Debug).
1000 ## Debugging event-driven code
1003 - Print debug info to `STDERR`.
1004 - Check out [`AnyEvent::Debug`](https://metacpan.org/pod/AnyEvent::Debug).
1005 - Use [`perl5db.pl`](https://metacpan.org/pod/perl5db.pl) and other `Devel::` debuggers.
1009 Traditional debuggers are still useful for event-driven code.
1010 - Be sure to carefully avoid memory leaks -- they are more devastating in long-lived programs which event-driven
1011 programs tend to be.
1014 class: center, middle
1017 ### Proxy objects for future values
1020 Proxy objects for values that have not yet been retrieved or computed.
1022 - In some cases, promises can provide you an alternative, often more useful interface to callbacks.
1027 ## Basic usage (using [`Future`](https://metacpan.org/pod/Future))
1030 my $future = fetch_remote_file($url);
1032 $future-
>on_done(sub($filename, $contents) {
1033 print "Fetched $filename\n";
1038 The future is an object that stands in for the thing we actually want until the thing we want is available.
1042 $future-
>on_fail(sub($error) {
1048 If the operation that provides the `Future` isn't able to fulfill its promise to provide us what we want, the `Future`
1049 may be set to a "fail" state.
1051 - For any operation that can fail (and that includes almost everything), it's a good idea to handle errors.
1059 my $future = fetch_remote_file($url)
1060 -
>then(sub($filename, $contents) {
1061 my $another_future = write_local_file("/tmp/$filename",
1063 return $another_future;
1066 $future-
>on_done(sub($filepath) {
1067 print "Saved file to $filepath\n";
1070 $future-
>on_fail(sub($error) {
1084 sub fetch_remote_file($url) {
1085 my $future = Future-
>new;
1087 http_get $url =
> sub($data, $headers) {
1088 if ($headers-
>{Status} =~ /^[
23]/) {
1089 my ($filename) = get_filename($headers);
1090 $future-
>done($filename, $data);
1093 $future-
>fail("Failed to fetch file: $headers-
>{Reason}");
1102 class: center, middle
1104 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.
1106 And, of course, [read the pod](https://metacpan.org/pod/Future).
1109 Paul did an advent calendar with short posts detailing the things you can do with `Future`.
1111 - It's really well done.
1114 class: center, middle
1116 ## There's also [`Future::AsyncAwait`](https://metacpan.org/pod/Future::AsyncAwait).
1119 If you have used JavaScript recently, you may have used its "async/await" feature to clean up your non-blocking code.
1122 class: center, middle
1124 ### Yes, Perl can do it, too!
1127 class: ex-asyncawait
1129 ## Without async and await
1135 return do_first_thing()-
>then(sub {
1138 return do_second_thing($first)-
>then(sub {
1141 return Future-
>done([$first, $second]);
1148 class: ex-asyncawait
1150 ## With async and await
1153 use Future::AsyncAwait;
1155 async sub do_two_things
1157 my $first = await do_first_thing();
1159 my $second = await do_second_thing($first);
1161 return [$first, $second];
1166 There are caveats: Localized variable assignments don't work, nor anything that has implied local-like behavior.
1169 class: center, middle
1171 ## Finally, there's also [`Future::Utils`](https://metacpan.org/pod/Future::Utils).
1189 Any exceptions throw in the code block are caught and become a failed `Future`.
1197 use Future::Utils qw(repeat);
1199 my $eventual_future = repeat {
1200 my $trial_future = ...
1201 return $trial_future;
1202 } while =
> sub($last_trial_future) { return should_keep_going($last_trial_future) };
1206 ## Events in the world
1208 - Interconnected devices
1211 - Events are everywhere in the world, and our devices can tell other devices about them.
1212 - Interconnected devices (IoT)
1215 class: center, middle
1223 class: center, middle
1228 </textarea><script src=
"https://gnab.github.io/remark/downloads/remark-latest.min.js"></script><script>var slideshow = remark.create({countIncrementalSlides: true, highlightLanguage: '', highlightLines: true, highlightStyle: 'hybrid', ratio: '
16:
9', /*slideNumberFormat: '',*/ navigation: {scroll: false, touch: false, click: false}})
</script><script src=
"js/common.js"></script><script src=
"js/slides.js"></script></body></html>
1229 <!-- vim: set ts=4 sts=4 sw=4 tw=120 et ft=markdown nowrap: -->