Ejercicio: Race Conditions. Concurrencia

Supongamos el siguiente programa rack en el que se incrementa la variable @some_key (Véase GitHub: crguezl/rack-race-conditions):

[~/local/src/ruby/sinatra/rack/rack-appvswebserver(icon)]$ cat configapp.ru 
class Persistence

  def call(env)
    
    res = Rack::Response.new
    req = Rack::Request.new env

    @some_key ||= 0
    @some_key = @some_key + 1 

    res.write("@some_key = #{@some_key}\n")

    res.finish
  end

end

run Persistence.new

Supongamos que arranco el servidor:

[~/local/src/ruby/sinatra/rack/rack-appvswebserver(master)]$ rackup configapp.ru >> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:9292, CTRL+C to stop

Nótese que con thin arrancado desde rack se tienen los valores de env para las claves:

rack.multithread => false
rack.multiprocess => false
lo que indica que el servidor no está soportando multithreading ni multiproceso.

Responda a estas preguntas:

  1. ¿Que valores de @some_key serán mostrados cuando me conecto a localhost:9292?
  2. ¿Y si recargo la página varias veces?
  3. ¿Y si abro un nuevo navegador o ventana de incógnito en la misma URL?
  4. ¿Y si re-arranco el servidor?
  5. ¿Como afectaría a la conducta que el servidor fuera multithreading?
    [~/local/src/ruby/sinatra/rack/rack-appvswebserver(icon)]$ rvm use jruby-1.7.3
    Using /Users/casiano/.rvm/gems/jruby-1.7.3
    [~/local/src/ruby/sinatra/rack/rack-appvswebserver(icon)]$ rackup configapp.ru 
    Puma 2.6.0 starting...
    * Min threads: 0, max threads: 16
    * Environment: development
    * Listening on tcp://0.0.0.0:9292
    rack.multithread => true
    rack.multiprocess => false
    
    [~/local/src/ruby/sinatra/rack/rack-appvswebserver(icon)]$ cat Rakefile 
    desc "run the server"
    task :default do
      sh <<-"EOS"
      #rvm use jruby-1.7.3 &&
      #ruby -v &&
      rackup -s puma configapp.ru 
      EOS
    end
    
    desc "run the client"
    task :client do
      pids = []
      (0...100).each do
        pids << fork do
          sh %q{curl -v 'http://localhost:9292' >> salida 2>> logs}
        end
      end
      puts pids
    end
    
    desc "remove output and logs"
    task :clean do
      sh "rm -f salida logs"
    end
    

De acuerdo a una respuesta en StackOverflow a la pregunta: Is Sinatra multi-threaded? I read else where that "sinatra is multi-threaded by default", what does that imply?

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

  1. Multi-Process, non-preforking: Mongrel, Thin, WEBrick, Zbatery
  2. Multi-Process, preforking: Unicorn, Rainbows, Passenger
  3. Evented (suited for sinatra-synchrony): Thin, Rainbows, Zbatery
  4. Threaded: Net::HTTP::Server, Threaded Mongrel, Puma, Rainbows, Zbatery, Phusion Passenger Enterprise >= 4
  5. 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).

Casiano Rodriguez León 2015-06-18