Is Sinatra multi threaded?

ch4nd4n picture ch4nd4n · Jun 8, 2011 · Viewed 21.8k times · Source

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?

Answer

Konstantin Haase picture Konstantin Haase · Jun 8, 2011

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).