How is ReactiveMongo implemented so that it is considered non-blocking?
Asked Answered
S

1

14

Reading the documentation about the Play Framework and ReactiveMongo leads me to believe that ReactiveMongo works in such a way that it uses few threads and never blocks.

However, it seems that the communication from the Play application to the Mongo server would have to happen on some thread somewhere. How is this implemented? Links to the source code for Play, ReactiveMongo, Akka, etc. would also be very appreciated.

The Play Framework includes some documentation about this on this page about thread pools. It starts off:

Play framework is, from the bottom up, an asynchronous web framework. Streams are handled asynchronously using iteratees. Thread pools in Play are tuned to use fewer threads than in traditional web frameworks, since IO in play-core never blocks.

It then talks a little bit about ReactiveMongo:

The most common place that a typical Play application will block is when it’s talking to a database. Unfortunately, none of the major databases provide asynchronous database drivers for the JVM, so for most databases, your only option is to using blocking IO. A notable exception to this is ReactiveMongo, a driver for MongoDB that uses Play’s Iteratee library to talk to MongoDB.

Following is a note about using Futures:

Note that you may be tempted to therefore wrap your blocking code in Futures. This does not make it non blocking, it just means the blocking will happen in a different thread. You still need to make sure that the thread pool that you are using there has enough threads to handle the blocking.

There is a similar note in the Play documentation on the page Handling Asynchronous Results:

You can’t magically turn synchronous IO into asynchronous by wrapping it in a Future. If you can’t change the application’s architecture to avoid blocking operations, at some point that operation will have to be executed, and that thread is going to block. So in addition to enclosing the operation in a Future, it’s necessary to configure it to run in a separate execution context that has been configured with enough threads to deal with the expected concurrency.

The documentation seems to be saying that ReactiveMongo is non-blocking, so you don't have to worry about it eating up a lot of the threads in your thread pool. But ReactiveMongo has to communicate with the Mongo server somewhere.

How is this communication implemented so that Mongo doesn't use up threads from Play's default thread pool?

Once again, links to the specific files in Play, ReactiveMongo, Akka, etc, would be very appreciated.

Saloon answered 2/9, 2014 at 7:40 Comment(3)
You have to understand how Future is working, that's the key of not blocking.Barnardo
The one note says in addition to enclosing the operation in a Future, it’s necessary to configure it to run in a separate execution context that has been configured with enough threads to deal with the expected concurrency. This makes it sound like it will just block on another thread. Do you know of any resources that would make this more understandable?Saloon
@applicius, futures futures are just a high-level tool for concurrency, they are not really related to non-blocking I/O which is the basis of ReactiveMongo "reactivity", though they certainly could help in using ReactiveMongo.Dorothadorothea
D
12

Yes, indeed, you still need to use threads to perform any kind of work, including communication with the database. What's important is how exactly this communication happens.

ReactiveMongo "does not use threads" in a sense that it does not use blocking I/O. Usual Java I/O facilities like java.io.InputStream are blocking; this means that reading from such an InputStream or writing to OutputStream blocks the thread until the "other side" provides the required data or is ready to accept it. For network communication this means that threads will be blocked.

However, Java provides NIO API which supports non-blocking and asynchronous I/O. I don't want to get into its details right now, but the basic idea, naturally, is that non-blocking I/O allow not to block threads which need to exchange some data with the outside world: for example, these threads can poll the data source to check if there is some data available, and if there is none, they return to the thread pool and can be used for other tasks. Of course, down there these facilities are provided by the underlying OS.

Exact implementation details of non-blocking I/O is usually hidden inside high-level libraries like Netty because it is not at all nice to use. Netty (which is exactly the library ReactiveMongo uses), for example, provides nice asynchronous callback-like API which is really easy to use but is also powerful and expressive enough to allow building complex I/O-heavy applications with high throughput.

So, ReactiveMongo uses Netty to talk with Mongo database server, and because Netty is an implementation of asynchronous network I/O, ReactiveMongo really does not need to block threads for a long time.

Dorothadorothea answered 2/9, 2014 at 8:59 Comment(4)
Would you be able to add links to the specific ReactiveMongo source code that is using Netty to do this non-blocking IO?Saloon
Also, on Linux, is the Java NIO API implemented with the C functions like select() and epoll()?Saloon
@illabout, it is contained somewhere here but it seems to be spread over different source files. However, exact bits you're looking for seem to be here, closer to the end.Dorothadorothea
@illabout, it depends on exact JVM implementation. I don't know which API popular JVMs use, but I suspect that they do use something like epoll(). It is unlikely that they use select() since it is old and not recommended, but everything can happen.Dorothadorothea

© 2022 - 2024 — McMap. All rights reserved.