1

I am writing a web application that uses both Sinatra—for general single-client synchronous gets—and Faye—for multiple-client asynchronous server-based broadcasts.

My (limited) understanding of EventMachine was that it would allow me to put both of these in a single process and get parallel requests handled for me. However, my testing shows that if either Sinatra or Faye takes a long time on a particular action (which may happen regularly with my real application) it blocks the other.

How can I rewrite the simple test application below so that if either sleep command is uncommented the Faye-pushes and the AJAX poll responses are not delayed?

%w[eventmachine thin sinatra faye json].each{ |lib| require lib }

def run!
  EM.run do
    Faye::WebSocket.load_adapter('thin')

    webapp = MyWebApp.new
    server = Faye::RackAdapter.new(mount:'/', timeout:25)

    dispatch = Rack::Builder.app do
      map('/'){     run webapp }
      map('/faye'){ run server }
    end

    Rack::Server.start({
      app:     dispatch,
      Host:    '0.0.0.0',
      Port:    8090,
      server:  'thin',
      signals: false,
    })
  end
end

class MyWebApp < Sinatra::Application
  # http://stackoverflow.com/q/10881594/405017
  configure{ set threaded:false }

  def initialize
    super
    @faye = Faye::Client.new("http://localhost:8090/faye")
    EM.add_periodic_timer(0.5) do
      # uncommenting the following line should not
      # prevent Sinatra from responding to "pull"
      # sleep 5
      @faye.publish( '/push', { faye:Time.now.to_f } )
    end
  end

  get ('/pull') do
    # uncommenting the following line should not
    # prevent Faye from sending "push" updates rapidly
    # sleep 5
    content_type :json
    { sinatra:Time.now.to_f }.to_json
  end

  get '/' do
    "<!DOCTYPE html>
    <html lang='en'><head>
      <meta charset='utf-8'>
      <title>PerfTest</title>
      <script src='https://code.jquery.com/jquery-2.2.0.min.js'></script>
      <script src='/faye/client.js'></script>
      <script>
      var faye = new Faye.Client('/faye', { retry:2, timeout:10 } );
      faye.subscribe('/push',console.log.bind(console));
      setInterval(function(){
        $.get('/pull',console.log.bind(console))
      }, 500 );
      </script>
    </head><body>
      Check the logs, yo.
    </body></html>"
  end
end

run!
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • Note that calling `sleep` is a bad way to test this - it will actually sleep the entire thread including your EventMachine reactor - remember EventMachine apps only run in a single thread. – Martin Konecny Jan 16 '16 at 04:20
  • 1
    @MartinKonecny How does `sleep` differ from, say, `999999.times{ Math.sqrt(rand) }` or `exec("sleep 5")`? Those also block any single-thread, right? That's what I'm trying to simulate, a blocking command that takes a long time. – Phrogz Jan 17 '16 at 05:41

1 Answers1

1

How does sleep differ from, say, 999999.times{ Math.sqrt(rand) } or exec("sleep 5")? Those also block any single-thread, right? That's what I'm trying to simulate, a blocking command that takes a long time.

Both cases would block your reactor/event queue. With the reactor pattern, you want to avoid any CPU intensive work, and focus purely on IO (i.e. network programming).

The reason why the single-threaded reactor pattern works so well with I/O is because IO is not CPU intensive - instead it just blocks your programs while the system kernel handles your I/O request.

The reactor pattern takes advantage of this by immediately switching your single thread to potentially work on something different (perhaps the response of some other request has completed) until the I/O operation is completed by the OS.

Once the OS has the result of your IO request, EventMachine finds the callback you had initially registered with your I/O request and passes it the response data.

So instead of something like

# block here for perhaps 50 ms
r = RestClient.get("http://www.google.ca") 
puts r.body

EventMachine is more like

# Absolutely no blocking
response = EventMachine::HttpRequest.new('http://google.ca/').get

# add to event queue for when kernel eventually delivers result
response.callback {
    puts http.response
}

In the first example, you would need the multi-threaded model for your web server, since a single thread making a network request can block for potentially seconds.

In the second example, you don't have blocking operations, so one thread works great (and is generally faster than a multi-thread app!)

If you ever do have a CPU intensive operation, EventMachine allows you to cheat a little bit, and start a new thread so that the reactor doesn't block. Read more about EM.defer here.

One final note is that this is the reason Node.js is so popular. For Ruby we need EventMachine + compatible libraries for the reactor pattern (can't just use the blocking RestClient for example), but Node.js and all of it's libraries are written from the start for the reactor design pattern (they are callback based).

Martin Konecny
  • 57,827
  • 19
  • 139
  • 159