mperham / rack-fiber_pool

Rack middleware to execute each request in a Fiber
MIT License
244 stars 24 forks source link

Don't know how to use the library effectively #2

Closed openhood closed 14 years ago

openhood commented 14 years ago

Hello,

I set up this very small example:

require "rubygems"
require "sinatra/base"
require "redis"

$LOAD_PATH << File.expand_path("../rack-fiber_pool/lib", __FILE__)
require 'rack/fiber_pool'

module CometTest
  class App < Sinatra::Base
    use Rack::FiberPool
    puts ">> #{REDIS = Redis.new(:thread_safe => true)}"

    get "/set/:key/:value" do |key, value|
      REDIS[key] = value
    end

    get "/get/:key" do |key|
      REDIS[key].to_s
    end

    get '/get_if_modified/:key/:value' do |key, value|
      redis_value = REDIS[key]
      if redis_value==value
        n = 0
        sleep 0.25 while (redis_value = REDIS[key])==value && (n+=1) < 480
      end
      redis_value
    end
  end
end

This is exactly the place I would need fibers because with threads it works but I can only create 950 threads or so before it crashes, whereas I can create many more fibers without issue. It does really seem to run requests inside fibers, and send the proper async info to thin. However as soon as the sleep command is running, no more request can be done which make the whole fiber-thing pretty useless. Is there a special thing I should be using instead of sleep to make the current fiber sleep while allow other fibers to run? Here is a simplified example showing the same exact issue:

require "rubygems"
require "sinatra/base"
$LOAD_PATH << File.expand_path("../rack-fiber_pool/lib", __FILE__)
require 'rack/fiber_pool'
module Test
  class App < Sinatra::Base
    use Rack::FiberPool
    get "/instant" do
      "Ping!"
    end
    get "/slower" do
      sleep 10
      "Pong!"
    end
  end
end
run Test::App

If I call "/instant" I get the response immediately. If however I call "/slower" and in another tab I try to call "/instant", I don't get response from "/instant" until "/slower" has finished. What am I doing wrong? Perhaps could you add an additional example (a bit like the previous one) in the README showing how putting each request in a fiber can be used to achieve concurrency.

mperham commented 14 years ago

First, all network access should go through EM. Using the stock Redis client will kill your scalability. You ideally should use a fibered, EM-aware Redis client. I'm not aware of one.

sleep() puts the current thread to sleep. Since there's only one thread in your process, this halts all processing - which is what you are seeing. You need to use the proper EM facilities to do the same thing. Replace "sleep" with "Fiber.sleep" in your code above, with this monkeypatch.

class Fiber
  def self.sleep(sec)
    f = Fiber.current
    EM.add_timer(sec) do
      f.resume
    end
    Fiber.yield
  end
end

None of this has to do with rack-fiber_pool, which is why it's not in the readme.

JonathanTron commented 14 years ago

You can find an EM-Redis lib here : http://github.com/madsimian/em-redis with a more recent fork here http://github.com/superfeedr/em-redis.

mperham commented 14 years ago

That's an EM-aware client but it is not Fiber-aware. Here's how I 'fibered' em-jack:

http://github.com/mperham/em-jack/commit/09ed5e3917933809942a23bdfa5ff7bafae6b7c6

Note the code in the spec: it's purely procedural, despite using EM under the covers.

    bean = EMJack::Connection.new
    bean.fiber!
    bean.put("hello!")
    job = bean.reserve
    job.body.should == "hello!"
    job.delete
openhood commented 14 years ago

I suspect redis to be so fast that em-redis would not be any faster, but it's impossible to be sure without benchmarks.

mperham commented 14 years ago

It's not speed, it's context-switching. EM allows your process to work on other things while waiting on network traffic. Keep studying EM and you'll grok it eventually.

openhood commented 14 years ago

Ok thanks a lot for your explanations.