Ruby 1.9 Fibers + AJAX = HTTP Binding?

So, you’re probably aware that ruby 1.9 adds Fibers, a lightweight concurrency library.

There’s some cool stuff using this, and I’m sure that you could use that to implement the following, but it felt better rolling it from scratch.

So, the use case:

Assume that I am a user at a web frontend watching the status of my application. I don’t want to poll the app every N seconds to get the data from it, but I still want as-it-happens updates to the data on my screen. Say, every time a ‘Widget’ model is created I want to update a certain div with the new Widget.count.

To do that, we’d need to send an AJAX call to the server, which is going to purposefully hang the response and then wake it up again whenever our event occurs.

The following code should do just that:

require 'socket'
 
server = TCPServer.new('127.0.0.1', '8080')
#fork do
module FiberPool
  class << self
    def fire event
      $queued_fibers ||= {}
      $queued_fibers[event] ||= []
      while(!$queued_fibers[event].empty?) do
        x = $queued_fibers[event].shift
        puts "\tFiring #{x.inspect}"
        x.resume
      end
      $queued_fibers[event] = []
    end
  end
end
$events = []
$queued_fibers = {}
f = Thread.fork do
  loop do
    begin
      session = server.accept_nonblock
      $queued_fibers['respond'] ||= []
      $queued_fibers['respond'] << Fiber.new {
          puts "Firing a fiber: #{session.gets}"
          session.puts "Hello Fiber!"
          session.close
        }
    rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR
 
    ensure
      FiberPool.fire $events.shift unless $events.empty?
    end
  end
end
 
at_exit {Thread.kill(f)}

I’ve used some global variables here as a way of passing messages across threads, but in reality you’d use a Redis database or something instead.

So, every time we get a request on 8080, we hang the request into its own fiber. The server continually checks the $events variable (or just as easily, a redis event queue) for events. Then, as soon as we see the ‘respond’ event, we wake up all of those threads and fire them off.

For demonstration purposes, append the following code, giving the terminal control over the ‘event sending.’

begin
  loop do
    puts "Register an event:"
    event = gets.chomp
    puts "got event #{event}"
    if event == 'show'
      puts "Queued Fibers = #{$queued_fibers.inspect}"
      puts "Events        = #{$events.inspect}"
    else
      $events << event
    end
  end
rescue Interrupt
  puts "\nCaught interrupt, exiting"
end

Save all that to a file, run it, and point your web browser to localhost:8080. It should hang, until you go to your command line and type ‘respond’ as the event to raise, at which point the web browser should immediately see ‘Hello Fiber!’

Now, imagine that you’ve made the request through a new Ajax.updater and the response is a JSON map of DOMID => Value pairs computed by the server, and you see why this is exciting. You can set up your Widget.after_create to throw a new event to redis, at which point all of your clients will receive a response to this AJAX call, updating their page. You can set up the ‘onsuccess’ callback to make another request, and you’ve bound a dom object to a server-side map.

If you’re used to programming GUIs in the desktop, or IPhone, you’ll notice this is pretty much the way that those work. A change in the state of the bound object triggers an update in the view, and the view can send events to the controller to change the bound objet.

The gap between the real-time GUIs of application development and request-based transactions of web development just got a hell of a lot smaller.

References:
http://www.double.co.nz/pdf/continuations.pdf

2 Comments

  1. Max
    Posted August 9, 2009 at 12:01 pm | Permalink

    Soo…wouldn’t this make a DoS/DDoS attack trivially easy to pull off?

  2. admin
    Posted August 10, 2009 at 7:44 am | Permalink

    If all you did was spin-wait all of those connections until something happened, then sure! There might be some way to do some hacking to ’serialize’ the TCP session (The packet headers) so that when the event goes off and you want to continue, you pull the session information from Redis or Memory and respond in kind.

    Of course, that would set up the possibility of an attacker ‘bombing’ the server, trying to get it to respond to a bunch of requests all at once, so you could do some metering there…

    It might be more trouble than its worth, but I thought the ‘hanging response’ would be a cool way to send the client some updates. What are some other ideas that don’t involve constant polling of the webapp?

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*