Let’s say we have this action:
This code is a scalability problem, because each request efectively blocks other requests from being handled. If your service receives more than one request per 5 seconds, your service will start failing to respond. But it doesn’t need to be like that. There is a way to tell Rails that it is okay to handle some other request while some other work is being done and then go back to that original request when all it’s ready.
It’s called concurrent mode.
In this example we will use Thin plus rails’s concurrent mode to handle request concurrently.
The first step is to add the Thin server to the Gemfile. A simple
gem 'thin' in the Gemfile will do.
The second step is to activate the concurrent mode in the rails environment. For example, in development:
Then set up the action that you want to handle asyncronously in this way:
Start the server like this:
bundle exec thin --threaded -p 5500 --threadpool-size 50 start
That means that Thin will be started in threaded mode, and it will use up to 50 threads. The
threaded option is important as Thin defaults to non threaded mode.
When the request is being handled, the
EM.defer block will be executed in a separate thread. At the same time the
throw :async will be executed, which basically tells the Thin server that this request will be handled asyncronously and that it should inmediately start working on a different request.
request.env['async.callback'].call response will communicate the response to the Thin server and send it back to the client.
Let me remark that, until
request.env['async.callback'].call response is executed, no response is sent back to the client.
There is one important gotcha with
throw :async: No rack middleware will be executed for the response. Thereby if you try to set up a cookie it will fail. But here is a way to fix that:
cookies.write(headers) will take care of setting the headers that needs to be set in order to make the changes in the cookies. It’s basically the code that gets executed when the Cookie middleware is executed.
And that’s it, so simple.
One way to test that you action can really handle concurrent requests to to query the url with multiple requests at the same time. You can do it with Apache HTTP server benchmarking tool, packaged with Mac OS.
So you can see that it took 5 seconds total to handle the 10 requests (each one taking 5 seconds) thereby proving that we can handle concurrents requests. This is a very useful model for handling web requests where one portion of the request may take a few seconds. For example at Haiku we use this technique when handling Google sign in. The portion of the code where we need to wait is simply executed within a Event Machine defered block.
Note: If you use Pow for your app, use the ip address as in the example, because Pow will only handle one request at a time.
You can see a full example here: https://github.com/Nerian/concurrency