Non-blocking Mojolicious apps are even easier now!
Hopefully by now you have seen that Mojolicious is a great way to build your non-blocking web (or even non-web) application. Mojolicious come with all kinds of non-blocking functionality out of the box. For more about that see my blog series on the topic. This post is an aside to show you the cool things happening in Mojolicious lately designed to make writing non-blocking apps easier.
Mojolicious is known for fast development and clean APIs. Mojo was that child with lots of excitement and energy, doing new and cool things, providing new and cool functionality, and yes, changing its mind on occasion. But Mojo is growing up and settling down a little bit. It recently went to its first conference and professional training. And it’s starting a family too!
Mojo is starting to feel more grown up, and grown-ups have responsibilities. To borrow one of Perl’s catch phrases, this more mature Mojo knows that it is not good enough anymore to just make things possible, it’s time to make them easy.
A lot of this came in the lead-up to the 5.0 release. Mojolicious major releases often include many breaking changes as new features are added, or old ones are removed. For 5.0 almost no breaking changes were needed. If we didn’t need to break anything, were there smaller changes that could help users?
Stopping the Leaks
One big problem in non-blocking applications is finding leaks. Perl uses reference counting to clean up memory. Non-blocking code uses callbacks and closures, which make it very easy to create reference cycles, which prevent memory from being cleaned up.
Consider the following example.
This script simply makes two sequential non-blocking calls to http://mojolicio.us, each time printing the return status. Not a very useful application, but straightforward right?
What you may not be able to notice is that this script has a bug; it leaks memory.
Writing good delay sequences can require an amount of
weaken-foo that most people,
certainly me included, do not possess.
I have included Devel::Cycle to demonstrate
To do so make a cpanfile with the line
requires 'Mojolicious', '== 4.94';
Install the wonderful Carton and run
carton install && carton exec perl cycle.pl
cycle.pl is the name of the example script.
When you do so, (installing and using a local copy of Mojolicious 4.94 to do so),
you will get this.
Using this output, you could find the cycles and weaken your code back to being leak-free. Or … you could upgrade Mojolicious, because as of 4.95 the delay system has been reworked to use inside-out storage, mitigating essentially all leak scenarios! In fact, it has been so useful, that we now recommend using Mojo::IOLoop::Delay for all non-blocking calls, even the simple ones, for the leak protection it provides.
Making it Portable
When using a delay to manage non-blocking callbacks, you need to be in a running ioloop.
But when you write a library that provides some non-blocking api, do you know if
the consuming user will have an ioloop running?
$delay->wait after setting up a delay will actually start an ioloop
and stop it again when the delay completes.
As I discuss here
there is a problem though; starting a delay if it is already running blows up.
This means that to write portable code you need to write
$delay->wait unless $delay->ioloop->is_running,
and in practice most people didn’t do that: some authors omitted the check, opening
the libraries to exception and others omitted the whole thing, requiring that the
user ensure there is a running loop.
As of Mojolicious 5.0,
$delay->wait will check the ioloop running state for you!
Now there is no reason not to write more portable code!
Making it Easy
So far I have been talking about concepts which were applicable in non-web contexts, but making a web-app out of these tools can be even harder. In the following example app, I highlight a few simple mistakes.
How do I prevent automatically rendering?
Mojolicious has a nice feature that if a controller doesn’t render anything, it will try to automatically render a template with the same name as the route. This almost always is a benefit, but sometimes when prototyping, you forget about some old template you made, and now when you try to render, since the delay takes some time, the controller will automatically render it and not wait.
This can be prevented by using
$c->render_later as I paranoidly do in all my
non-blocking controllers, just in case.
This is seen in the
If I comment out the
$c->render_later line, you don’t get what you mean.
What if the user uses Starman rather than Mojo’s Hypnotoad?
This is why the portability argument from above is important.
To prevent this bug, you really want
Try commenting the
wait lines and try the app using say
What happens if one of your delay steps dies?
In normal blocking control flow, if your controller dies, it implicitly calls
$c->render_exception which renders a 500 page.
When using a delay, the loop catches the error and the connection eventually times
That is unless you set your
error handler to call
render_exception for you.
Do you always attach one?
Did I in the
Try commenting out the
->on(error => ...) line, and request the
/error page to
see what I mean.
What if the user closes the connection before completion?
This one isn’t really a bug, but it looks like one.
The controller does not keep a strong reference to the current transaction, in
order to prevent leaks internally.
Without being careful, say by checking
if $c->tx before rendering, or by holding
a strong reference yourself (carefully!), you might get this in your error log:
Mojo::Reactor::Poll: Timer 714689c4838d3cb6662d790aa5a6f751 failed: Mojo::IOLoop::Delay: Can't call method "res" on an undefined value at /home/joel/Programs/Test/cycle/local/lib/perl5/Mojolicious/Controller.pm line 218.
Try removing the
if $c->tx, request the page in a browser and then in less than four seconds, stop the browser.
You should see the error in the server log.
Where did that come from? That’s an error in your app, right?
Ok, Enough Already!
Yes it’s hard to do this correctly! So what should the common web-app author do about it? Do they really need to learn to do all of these things? Every time?
Just upgrade to Mojolicious 5.11!
That version adds the handy
With it, the same code can be simple this.
The new delay helper does all the things above:
render_laterto prevent automatic rendering
- attaches an error handler
- keeps a strong reference to the transaction (safely)
- it even has the leak benefits from the top of the article!
This makes writing non-blocking web-apps almost as easy as writing blocking ones! A big shout out to fellow Mojolicious core developer Marcus Ramberg for getting that idea rolling at MojoConf!
This cycle has been especially satisfying for me and I believe for the other core developers as well. Making Mojolicious useful is about more than just making it powerful, its about making it easy too. We hope you enjoy the recent improvements!