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 the problem. 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

where 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? Simply calling $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 /pause route. 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 $delay->wait. Try commenting the wait lines and try the app using say plackup.

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 out. That is unless you set your error handler to call render_exception for you. Do you always attach one? Did I in the /pause route?

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 delay helper. With it, the same code can be simple this.

The new delay helper does all the things above:

  • calls render_later to prevent automatic rendering
  • calls wait for portability
  • 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!

Enjoy!

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!

3 Comments

Can you explain the purpose of the two subs enclosed in the delay? Or is it just an example of two typically blocking actions that execute in order?

So would it be valid to say: $c->delay( sub { # step 1 }, sub { # step 2 }, );

Or are they both executed and delay waits until they both finish: $c->delay( sub { # this will execute }, sub { # this will execute at the same time }, ); # delay finishes when both are complete

Ok looking at the Mojolicious docs, it appears that these are intended to be chained actions:

http://mojolicio.us/perldoc/Mojolicious/Plugin/DefaultHelpers#delay

Disable automatic rendering and use “delay” in Mojo::IOLoop to manage callbacks and control the flow of events, which can help you avoid deep nested closures that often result from continuation-passing style. Also keeps a reference to “tx” in Mojolicious::Controller in case the underlying connection gets closed early, and calls “reply->exception” if an exception gets thrown in one of the steps, breaking the chain.

Leave a comment

About Joel Berger

user-pic As I delve into the deeper Perl magic I like to share what I can.