-
Notifications
You must be signed in to change notification settings - Fork 581
Blocking vs non blocking 101
The concept itself isn't particularly hard, compare it to... well let's say this;
- You put a kettle of water on the stove, and you wait until you see it boiling, then make tea
- You put a kettle of water on the stove, and you go to your room and reply to emails on a mailinglist until you hear the kettle whistle, then you go back to the stove, get the kettle, and make tea
The difference is subtle but #1 is blocking and #2 isn't. Figure out what the subtle difference is, and you're well on your way to wrapping your head around non-block-fu :)
When an operation is "blocking" what it literally means is that the operation blocks further execution of the program. For example opening a socket with Socket is a blocking operation, since the actual connection attempt blocks until a result is available. This also applies to files and reading data from them.
Non-blocking is a method where you can keep up executing the program; an "old" method of doing this is to use the select() system call. What happens is that instead of reading data from a socket until you run out is that you use select() to find out which file descriptors are "ready" for reading or writing, if a fd is ready for reading you can then read data from it.
Of course there are better methods available these days, but those are implementation details. The thing you have to understand is that non-blocking requires a little bit of thought beforehand. There is no "magic sauce" that will suddenly make code non-blocking.
Yes, morbo and hypnotoad are non-blocking because they have been designed to be; courtesy of Mojo::IOLoop, which is an event loop (well... in "big picture" terms). An event loop basically boils down to:
while(1) {
do_some_timer_stuff;
check_any_fds_for_readability;
handle_readable_fds;
check_any_fds_for_writabiliy;
handle_writable_fds;
}
So, you do still need to actually inform the event loop that it needs to check for something. Now Mojo::IOLoop does let you do this on plain "Socket" connections, but you'd have to get the file descriptor, then use Mojo::IOLoop's functionality to get it to be taken up into the checks.
That's why Mojo::IOLoop->client works out of the box, because it does that part already, for you.
The other key part is that any event loop will be using callbacks - because you obviously don't want to be looping through a bunch of if blocks to see if somethings' readable, Mojo::IOLoop already does that for you, so when things are in fact readable, it will trigger a callback - and inside the callback you can handle whatever you need to be doing.
But... you can in fact block inside an event loop. This is considered to be one of the pitfalls, because blocking inside the event loop will pretty much halt the entire event loop for the duration of the blocking taking place; that's why any time you are considering doing anything non-blocking, you need to make sure the entire code path leading in, through, and out of it is also non-blocking, otherwise you're not getting any benefits out of it at all, and in fact are probably causing a hell of a lot of issues at that point.
Silly example time; imagine you want to write a little proxy that just grabs a single page. And it's a huge page, like, 5Mb of data; so in your controller you do this:
sub sillyhugedataproxy {
my $self = shift;
if(my $tx = $self->ua->get('http://www.superhugepage.com/blah.html')) {
if(my $res = $tx->success) {
$self->render(text => $res->body);
} else {
$self->render(text => 'error');
}
}
}
That will not be non-blocking - mostly because Mojo::UserAgent is built to allow both styles and if you never pass it a callback it will assume you want to block. The above example also is the typical way of doing it with LWP (except you substitute the self->ua->get bit for the appropriate LWP incantations).
Now try this on for size:
sub sillyhugedataproxy {
my $self = shift;
$self->render_later;
$self->ua->get('http://www.superhugepage.com/blah.html' => sub {
my ($ua, $tx) = (@_);
if(my $res = $tx->success) {
$self->render(text => $res->body);
} else {
$self->render(text => 'error');
}
});
}
That is non-blocking. The sub will be exited before the the result has been completely fetched, but thanks to 'render_later' Mojolicious knows it can expect a response to be rendered at some later point in time; the callback passed to ua->get is called once all data has been read and the request parsed, the reading of said data is done in a non-blocking fashion (if you want the details, see the code ;)) - and that's where you then render your response.
The above pattern is what you'd use to talk to a backend API over HTTP. You can also do this for MongoDB as long as you use the Mango driver (because sri made it and it's also designed to support non-blocking out of the box); for other things such as ZMQ or RabbitMQ you will have to find a module that implements the appropriate protocol and does things in a non-blocking fashion; for example AnyEvent::RabbitMQ can do it for RabbitMQ.
-- Courtesy of Ben van Staveren
It's all about the underlying syscalls, here 's a pretty decent explanation i just found.
(Gotta love the Way Back Machine!!)
-- Courtesy of Sebastian Riedel