Skip to content

Instantly share code, notes, and snippets.

@brianmed
Last active January 28, 2020 10:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brianmed/45784c1c49c12780722ee8f57f083fd1 to your computer and use it in GitHub Desktop.
Save brianmed/45784c1c49c12780722ee8f57f083fd1 to your computer and use it in GitHub Desktop.
Hopefully a fun read about asynchronous, non-blocking operations in Mojolicious with concurrent events via Mojo::IOLoop
use Mojo::Base -strict;
use Mojo::IOLoop;
use Mojo::IOLoop::Delay;
say("Starting");
Mojo::IOLoop::Delay->new->steps(
sub {
my $delay = shift;
Mojo::IOLoop->timer(3 => $delay->begin);
say("Second step in 3 seconds.");
},
sub {
my ($delay, @args) = @_;
say("Now in second step.");
},
)->wait;
say("Stopping");
use Mojo::Base -strict;
use Mojo::IOLoop;
use Mojo::IOLoop::Delay;
say("Starting");
my $id = Mojo::IOLoop->recurring(0.5 => sub {
my $loop = shift;
say("recurring: " . scalar(localtime(time)));
});
Mojo::IOLoop::Delay->new->steps(
sub {
my $delay = shift;
Mojo::IOLoop->timer(6 => $delay->begin);
say("Second step in 6 seconds.");
},
sub {
my ($delay, @args) = @_;
Mojo::IOLoop->timer(1 => $delay->begin);
Mojo::IOLoop->timer(3 => $delay->begin);
say("Third step in 3 seconds.");
},
sub {
my ($delay, @args) = @_;
say("Now in second step.");
},
)->wait;
Mojo::IOLoop->remove($id);
say("Stopping");
This is an attempt at explaining an asynchronous event loop in perl via
Mojo::IOLoop.
First, lets define asynchronous:
1. not occurring at the same time [2]
And, then a definition of an event loop:
An event loop is basically a loop that continually tests for external events
and executes the appropriate callbacks to handle them, it is often the main
loop in a program. Non-blocking tests for readability/writability of file
descriptors and timers are commonly used events for highly scalable network
servers, because they allow a single process to handle thousands of client
connections concurrently. [1]
So, we have a set of events from multiple sources that will transpire over a
given time frame and we desire processing them in a manner that most
efficiently utilizes resources. For example, if there are several inbound
network requests (e.g. browser requests) then the application requests via the
OS that each socket operation be put into the event loop's notification queue
where they are processed as time allows.
This event loop queuing process is called a non-blocking operation.
A non-blocking operation on the other hand lets the calling subroutine
continue execution even though the subroutine is not yet finished. Instead
of waiting, the calling subroutine passes along a callback to be executed
once the subroutine is finished, this is called continuation-passing
style. [3]
This event inspection and event processing is asynchronous, non-blocking and
allows for the facade of processing events at the exact same time, which is
the definition we will use for concurrent.
So, given these defintions and understanding of event loops and queues, how do
we utilze these in perl? The answer is, in part, Mojo::IOLoop.
Mojo::IOLoop is a very minimalistic event loop based on Mojo::Reactor, it
has been reduced to the absolute minimal feature set required to build
solid and scalable non-blocking TCP clients and servers. [4]
Let's look at a code example utilizing Mojo::IOLoop::Delay a very svelte
wrapper that will... manage callbacks and control the flow of events [5].
A first example with a timer:
====
1 use Mojo::Base -strict;
2 use Mojo::IOLoop;
3 use Mojo::IOLoop::Delay;
4
5 say("Starting");
6
7 Mojo::IOLoop::Delay->new->steps(
8 sub {
9 my $delay = shift;
10
11 Mojo::IOLoop->timer(3 => $delay->begin);
12
13 say("Second step in 3 seconds.");
14 },
15 sub {
16 my ($delay, @args) = @_;
17
18 say("Now in second step.");
19 },
20 )->wait;
21
22 say("Stopping");
====
What should be noted is that the steps in the delay will be executed at a
later time; however, they will be executed in the order that they are defined.
Basically, sub at line 8 and 15 are added into the event loop's queue.
The next example will introduce concurrent events via a recurring timer and
Mojo::IOLoop::Delay.
====
use Mojo::Base -strict;
use Mojo::IOLoop;
use Mojo::IOLoop::Delay;
say("Starting");
my $id = Mojo::IOLoop->recurring(0.7 => sub {
my $loop = shift;
say("recurring: " . scalar(localtime(time)));
});
Mojo::IOLoop::Delay->new->steps(
sub {
my $delay = shift;
Mojo::IOLoop->timer(6 => $delay->begin);
say("Second step in 6 seconds.");
},
sub {
my ($delay, @args) = @_;
Mojo::IOLoop->timer(1 => $delay->begin);
Mojo::IOLoop->timer(3 => $delay->begin);
say("Third step in 3 seconds.");
},
sub {
my ($delay, @args) = @_;
say("Now in second step.");
},
)->wait;
Mojo::IOLoop->remove($id);
say("Stopping");
====
In the above example, our event loop has a recurring timer and a set of
serialized subs which contain timers that determine when the next step will
take place. Of particular interest is that the recurring timer is happening
concurrently while the Mojo::IOLoop::Delay's steps are in progress.
Truly, a cool feat.
[1]: http://mojolicious.org/perldoc/Mojolicious/Guides/FAQ#What-is-an-event-loop
[2]: http://www.dictionary.com/browse/asynchronous
[3]: http://mojolicious.org/perldoc/Mojolicious/Guides/FAQ#What-is-the-difference-between-blocking-and-non-blocking-operations
[4]: http://mojolicious.org/perldoc/Mojo/IOLoop
[5]: http://mojolicious.org/perldoc/Mojo/IOLoop/Delay
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment