50

Is Sinatra multi-threaded? I read else where that "sinatra is multi-threaded by default", what does that imply?

Consider this example

get "/multithread" do
  t1 = Thread.new{
    puts "sleeping for 10 sec"
    sleep 10
    # Actually make a call to Third party API using HTTP NET or whatever.
  }
  t1.join
  "multi thread"
end

get "/dummy" do
  "dummy"
end

If I access "/multithread" and "/dummy" subsequently in another tab or browser then nothing can be served(in this case for 10 seconds) till "/multithread" request is completed. In case activity freezes application becomes unresponsive.

How can we work around this without spawning another instance of the application?

ch4nd4n
  • 4,110
  • 2
  • 21
  • 43
  • 1
    Of course, you can't go on because of `.join` which will block until every thread is finished - see: http://ruby-doc.org/core-1.9/classes/Thread.html#M001331 – asaaki Jun 08 '11 at 13:01
  • Well that was example, in reality I may be making a call to read a file or a URI using HTTP Net and not inside a thread specifically. What's the work around if I don't want other request to be blocked out. – ch4nd4n Jun 08 '11 at 13:16
  • 3
    Without spawning more instances I can't see any easy solution. Normally you would use thin or unicorn to have multiple instances. If you only want to have some work in the background (so it doesn't matter if your result of the call to external resource is shown immediately), you should really use background jobs (resque, delayed jobs, ...), and if these jobs finished, the results can be shown in a further request. The general problem is, that in most cases Ruby apps cannot be really multi-threaded, because MRI doesn't support multiple cores yet. Spawning/Forking is a work around. – asaaki Jun 08 '11 at 15:34
  • Threads, processes, thread-safety, blocking/non-blocking IO is a complex topic. – asaaki Jun 08 '11 at 15:37
  • Thanks asaaki. It looks like we would have to launch multiple instance of Sinatra. Requests (thread code in the example code above) are REST calls and if there is latency in any REST end point all further requests freezes. – ch4nd4n Jun 09 '11 at 04:42

5 Answers5

96

tl;dr Sinatra works well with Threads, but you will probably have to use a different web server.

Sinatra itself does not impose any concurrency model, it does not even handle concurrency. This is done by the Rack handler (web server), like Thin, WEBrick or Passenger. Sinatra itself is thread-safe, meaning that if your Rack handler uses multiple threads to server requests, it works just fine. However, since Ruby 1.8 only supports green threads and Ruby 1.9 has a global VM lock, threads are not that widely used for concurrency, since on both versions, Threads will not run truly in parallel. The will, however, on JRuby or the upcoming Rubinius 2.0 (both alternative Ruby implementations).

Most existing Rack handlers that use threads will use a thread pool in order to reuse threads instead of actually creating a thread for each incoming request, since thread creation is not for free, esp. on 1.9 where threads map 1:1 to native threads. Green threads have far less overhead, which is why fibers, which are basically cooperatively scheduled green threads, as used by the above mentioned sinatra-synchrony, became so popular recently. You should be aware that any network communication will have to go through EventMachine, so you cannot use the mysql gem, for instance, to talk to your database.

Fibers scale well for network intense processing, but fail miserably for heavy computations. You are less likely to run into race conditions, a common pitfall with concurrency, if you use fibers, as they only do a context switch at clearly defined points (with synchony, whenever you wait for IO). There is a third common concurrency model: Processes. You can use preforking server or fire up multiple processes yourself. While this seems a bad idea at first glance, it has some advantages: On the normal Ruby implementation, this is the only way to use all your CPUs simultaniously. And you avoid shared state, so no race conditions by definition. Also, multiprocess apps scale easily over multiple machines. Keep in mind that you can combine multiple process with other concurrency models (evented, cooperative, preemptive).

The choice is mainly made by the server and middleware you use:

  • Multi-Process, non-preforking: Mongrel, Thin, WEBrick, Zbatery
  • Multi-Process, preforking: Unicorn, Rainbows, Passenger
  • Evented (suited for sinatra-synchrony): Thin, Rainbows, Zbatery
  • Threaded: Net::HTTP::Server, Threaded Mongrel, Puma, Rainbows, Zbatery, Thin[1], Phusion Passenger Enterprise >= 4

[1] since Sinatra 1.3.0, Thin will be started in threaded mode, if it is started by Sinatra (i.e. with ruby app.rb, but not with the thin command, nor with rackup).

Hongli
  • 18,682
  • 15
  • 79
  • 107
Konstantin Haase
  • 25,687
  • 2
  • 57
  • 59
  • Thanks to make things clear. Good explanation of the different models. Like you mentioned in the third paragraph processes are a good solution and I think widely used for scaling apps. / Sub-question: Which setup do you prefer? – asaaki Jun 08 '11 at 18:15
  • Thanks Konstantin for the elaboration. – ch4nd4n Jun 09 '11 at 04:46
  • I usually prefer preforking and, if it suits the infrastructure, use evented IO (with callbacks, not with fibers). I have no issue with callbacks and see no real advantages in em-synchrony/sinatra-synchrony, since they do not implement transparent futures/promises. But that's just me, I guess. – Konstantin Haase Jun 09 '11 at 07:18
  • Preforking is good, I used it, too. Do you have any documents/articles why callback based evented IO is better than with fibers? (Should I open a new question for this topic?) – asaaki Jun 09 '11 at 10:26
  • Zbatery (and Rainbows) does work with the ```ThreadPool``` strategy too. I wonder how is it in comparison to Puma? – brutuscat Mar 09 '12 at 09:38
  • 1
    You're right, brutuscat, updated the list. Also, since Sinatra 1.3.0, Thin will be started in threaded mode, if it is started by Sinatra (i.e. with `ruby app.rb`, but not with the `thin` command, nor with `rackup`). – Konstantin Haase Mar 10 '12 at 10:19
  • A useful comment on Thin's community forum: https://groups.google.com/forum/?fromgroups=#!topic/thin-ruby/uKLD-qob6Kc This says that Thin is multi-threaded but once you get into your app processing a request it's essentially single threaded unless you specifically make it concurrent. – webjprgm Dec 10 '12 at 18:23
  • 1
    [Phusion Passenger Enterprise 4](https://www.phusionpassenger.com) is also multithreaded. Or actually, it's hybrid multiprocess/multithreaded so you can get the best of both worlds. Highly recommended, it's used by the likes of New York Times, Symantec, Pixar, AirBnB, etc. – Hongli May 06 '13 at 09:30
  • > since Sinatra 1.3.0, Thin will be started in threaded mode, if it is started by Sinatra (i.e. with ruby app.rb, but not with the thin command, nor with rackup) @KonstantinHaase, can you explain the reasoning behind that choice? I couldn't find any mention of it in the [CHANGES](https://github.com/sinatra/sinatra/blob/master/CHANGES#L305-L431). – pje Jul 15 '14 at 22:59
6

While googling around, found this gem:

sinatra-synchrony

which might help you, because it touches you question.

There is also a benchmark, they did nearly the same thing like you want (external calls).

Conclusion: EventMachine is the answer here!

asaaki
  • 1,970
  • 18
  • 22
  • 1
    I did the benchmark, too. My result was 26.6 secs without and 4.4 secs with sinatra-synchrony gem - that was 16x faster! – asaaki Jun 08 '11 at 15:55
  • It's 4 years later, and seems like the author hates EventMachine now: https://github.com/kyledrake/sinatra-synchrony – Martin Konecny Jan 28 '15 at 04:56
4

Thought I might elaborate for people who come across this. Sinatra includes this little chunk of code:

   server.threaded = settings.threaded if server.respond_to? :threaded=    

Sinatra will detect what gem you have installed for a webserver (aka, thin, puma, whatever.) and if it responds to "threaded" will set it to be threaded if requested. Neat.

Joel Jackson
  • 1,060
  • 1
  • 10
  • 24
1

After making some changes to code I was able to run padrino/sinatra application on mizuno . Initially I tried to run Padrino application on jRuby but it was simply too unstable and I did not investigate as to why. I was facing JVM crashes when running on jRuby. I also went through this article, which makes me think why even choose Ruby if deployment can be anything but easy.

Is there any discussion on deployment of applications in ruby? Or can I spawn a new thread :)

ch4nd4n
  • 4,110
  • 2
  • 21
  • 43
1

I've been getting in to JRuby myself lately and I am extremely surprised how simple it is to switch from MRI to JRuby. It pretty much involves swapping out a few gems (in most cases).

You should take a look at the combination JRuby and Trinidad (App Server). Torquebox also seems to be an interesting all-in-one solution, it comes with a lot more than just an app server.

If you want to have an app server that supports threading, and you're familiar with Mongrel, Thin, Unicorn, etc, then Trinidad is probably the easiest to migrate to since it's practically identical from the users perspective. Loving it so far!

Michael van Rooijen
  • 6,683
  • 5
  • 37
  • 33
  • I figured out that eventually. By default thin is single threaded, you need to enable threaded mode when starting application. – ch4nd4n Sep 12 '11 at 10:06
  • I believe threading in Thin is experimental. I recall people mentioning that it isn't a good idea to use Thin in such a way, since it's an evented app server. However, just recently Puma 1.0.0 was released and this should be an interesting option now. It does a lot less than Trinidad/TorqueBox and should be very familiar to Rubyists. Also, it works with MRI, JRuby and Rubinius, but you of course get most of it by using a Ruby implementation that allows for true threading like JRuby and Rubinius. – Michael van Rooijen Apr 01 '12 at 10:40