How can I serve requests concurrently with Rails 4?
Asked Answered
S

3

34

I'm trying to serve multiple requests concurrently in Rails 4, something I was able to do very easily with config.threadsafe! and Puma in Rails 3.

Say I have this controller

class ConcurrentController < ApplicationController
  def index
    sleep 10000
  end

  def show
  end
end

I used to be able to just start puma with puma -t 2:16 -p 3000 (for min 2 threads) and hit index and then show and still have show render properly.

In Rails 4, if I attempt to do the same thing Puma now locks on the index request and show never gets rendered. When I hit Ctrl-C for the server Puma gives me this error:

Rack app error: #<ThreadError: Attempt to unlock a mutex which is locked by another thread>

What am I missing here to get concurrency to work with Rails 4? config.threadsafe! is supposed to not be needed (and doesn't make a difference even if I try)

Showmanship answered 6/2, 2014 at 14:14 Comment(5)
Have you tried running this with the production environment?Outstrip
There should be no difference with Rails 4. Indeed Rails 4 is multithreaded by default. There must be something else in your environment/configuration that is causing the difference. How are you starting puma? With that command at the command line? Anything else relevant you can share from your development.rb file?Dellinger
@fredrik - Please review my answer. I took the time to explain the option that allows concurrency and provided some references. Unfortunately, although your answer can lead to a practical solution, it is not entirely correct.Dogeared
I use rails 5.0.3 with config.cache_classes = false in config/environments/development.rb by default, but don't get this problem.Louiselouisette
Try using multiple workers instead of just multiple threads, with -w 2. That will spin up two instances of the app to handle requests.Malposition
D
38

I invite you to read about the configuration options of config.threadsafe! in this article Removing config.threadsafe! It will help you to understand better the options of config.threadsafe!, in particular to allow concurrency.

In Rails 4 config.threadsafe! is set by default.


Now to the answer

In Rails 4 requests are wrapped around a Mutex by the Rack::Lock middleware in DEV environments by default.

If you want to enable concurrency you can set config.allow_concurrency=true. This will disable the Rack::Lock middleware. I would not delete it as mentioned in another answer to your question; that looks like a hack to me.

Note: If you have config.cache_classes=true then an assignment to config.allow_concurrency (Rack::Lock request mutex) won't take effect, concurrent requests are allowed by default. If you have config.cache_classes=false, then you can set config.allow_concurrency to either true or false. In DEV environment you would want to have it like this

config.cache_classes=false
config.allow_concurrency=true

The statement: Which means that if config.cache_classes = false (which it is by default in dev env) we can't have concurrent requests. is not correct.

Appendix

You can refer to this answer, which sets up an experiment testing concurrency using MRI and JRuby. The results are surprising. MRI was faster than JRuby.

The experiment with MRI concurrency is on GitHub. The experiment only tests concurrent request. There are no race conditions in the controller. However, I think it is not too difficult to implement example from the article above to test race conditions in a controller.

Dogeared answered 14/5, 2015 at 2:2 Comment(2)
This seems correct and I will mark it as the correct answer, although I haven't taken the time to test it. I will note though, that config.allow_concurrency is a wholly undocumented feature[1]. It appears in the documentation for Rails 3[2], but not for Rails 4.Showmanship
Yes, that's right. We are left with not much documentation about the future use of that configuration option. Also, I just remembered, consider to set the connection pool of your database connection to the same size as the maximum number of threads. In your case 16.Dogeared
S
23

It seems that by default, in Rails 4, concurrent requests are not enabled in the Development environment.

I found this quote in the documentation.

Rack::Lock wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when config.cache_classes is false.

Which means that if config.cache_classes = false (which it is by default in dev env) we can't have concurrent requests.

Concurrent requests do work with my example in production environment.

One solution is to set config.cache_classes = true in the development environment, but then the code doesn't reload on a change, which doesn't really work for development.

The second kind of hacky solution is to disable the Rack::Lock middleware in development.

So if you were to add in development.rb the following line:

config.middleware.delete Rack::Lock

you should be able to have concurrent requests in development environment with cache classes disabled.

Showmanship answered 12/5, 2014 at 11:33 Comment(7)
Where you using MRI Ruby or JRuby? I was under the impression that multi-threaded didn't work in an example like this with MRI Ruby.Impatiens
I can see with Puma there is a clustered mode (to specify workers) : github.com/puma/puma#clustered-mode but I can't get this example working (with a "sleep")Impatiens
What are the drawbacks of deleting this middleware @Fredrik?Atlas
@JoãoPauloMotta Please see my answer. I proved a link to a blog post that discusses your question. The drawback is that you have to take care of race conditions within your controllers.Dogeared
Delete Rack::Lock didn't work for me well. It loads the page, but it gives me timeout to connect database: "ActiveRecord::ConnectionNotEstablished (ActiveRecord::ConnectionNotEstablished):"Kania
@AndreFigueiredo are you sure you've configured your database connection pool to have as many threads as you are trying to run in your app?Showmanship
Yes, I'ts configured right. Without Delete Rack:Lock it works, With, it fails. Even static resources, fail to load.Kania
B
-1

you can try unicorn this is very simple in development mode:

http://dave.is/unicorn.html

Bodega answered 9/5, 2014 at 11:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.