04 Jun

Pusher & Async With Thin

Pusher (aka Rack::Comet)

I’ve been playing around with Orbited, APE and the like, but they all have lots of code and kind of reinvent the wheels in some ways (reimplementing an HTTP server, daemon, etc). So just for fun, I’ve hacked a simple AJAX push server (aka Comet) on top of Thin using the new Async Response feature.

To be honest, after revisiting Orbited a couple times, it’s pretty cool! It’s easily extensible, based on Twisted. Use this if you want something serious.

Async Response in Thin

So, all this to talk to you about Thin Async Response feature. Coded by James Tucker a while ago and merged in Thin in version 1.2 (released around March 16th).

Async response is the way to do scalable concurrency. The downside is that you have to design your code accordingly, based on events. And you remember not to use any blocking calls, right?

class AsyncApp
  AsyncResponse = [-1, {}, []].freeze
    
  def call(env)
    body = DeferrableBody.new
    
    # Get the headers out there asap, let the client know we're alive...
    EM.next_tick { env['async.callback'].call [200, {'Content-Type' => 'text/plain'}, body] }
    
    # Semi-emulate a long db request, instead of a timer, in reality we'd be 
    # waiting for the response data. Whilst this happens, other connections 
    # can be serviced.
    # This could be any callback based thing though, a deferrable waiting on 
    # IO data, a db request, an http request, an smtp send, whatever.
    EM.add_timer(1) do
      body.call ["Woah, async!\n"]
      
      EM.next_tick do
        # This could actually happen any time, you could spawn off to new 
        # threads, pause as a good looking lady walks by, whatever.
        # Just shows off how we can defer chunks of data in the body, you can
        # even call this many times.
        body.call ["Cheers then!"]
        body.succeed
      end
    end
    
    AsyncResponse # Tells Thin to not close the connection and continue it's work on other request
  end
end

A DeferrableBody is a response body that can be rendered iteratively.

class DeferrableBody
  include EventMachine::Deferrable

  def call(body)
    body.each do |chunk|
      @body_callback.call(chunk)
    end
  end

  def each(&blk)
    @body_callback = blk
  end
end

Works With Rails Too!

You can use it inside Rails too, with throw :async to simulate returning AsyncResponse in the previous example.

But Tastes Better in Sinatra

James also built this nice wrapper for Sinatra, called async-sinatra:

require 'sinatra/async'

class AsyncTest < Sinatra::Base
  register Sinatra::Async

  aget '/' do
    body "hello async"
  end

  aget '/delay/:n' do |n|
    EM.add_timer(n.to_i) { body { "delayed for #{n} seconds" } }
  end
end

Scale To The Moon

Using that in combination with other EventMachine based libraries, you’re sure to scale to the moon running your website on a Pentium II.

  • em-mysql – Don’t block while querying your DB
  • em-http-request – Don’t block while querying other websites

Big thanks to James for implementing this feature!

Like what you just read? Subscribe to my newsletter to learn how I make a living selling my products online.

Or if you're into coding, join my free class on rebuilding a Ruby web server.

blog comments powered by Disqus