about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-10-26 01:57:56 -0700
committerEric Wong <normalperson@yhbt.net>2009-10-26 02:26:22 -0700
commit8e9672d13ed07cc262894b4770c0b9c016359712 (patch)
treeaafa88a841ade6f076c46af19aa203b5bd8f5d26
parent9248ead0ae16091ea0c362930391834c0f32891c (diff)
downloadrainbows-8e9672d13ed07cc262894b4770c0b9c016359712.tar.gz
This is should be compatible with how the Thin webserver
provides async callback support.

See http://github.com/raggi/async_sinatra for the details
-rw-r--r--lib/rainbows/event_machine.rb15
-rw-r--r--t/async_sinatra.ru13
-rwxr-xr-xt/t0300-async_sinatra.sh65
3 files changed, 91 insertions, 2 deletions
diff --git a/lib/rainbows/event_machine.rb b/lib/rainbows/event_machine.rb
index 678a771..7009fce 100644
--- a/lib/rainbows/event_machine.rb
+++ b/lib/rainbows/event_machine.rb
@@ -31,6 +31,9 @@ module Rainbows
       include Rainbows::EvCore
       G = Rainbows::G
 
+      # Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
+      ASYNC_CALLBACK = 'async.callback'.freeze
+
       def initialize(io)
         @_io = io
       end
@@ -48,7 +51,15 @@ module Rainbows
           (@env[RACK_INPUT] = @input).rewind
           alive = @hp.keepalive?
           @env[REMOTE_ADDR] = @remote_addr
-          response = G.app.call(@env.update(RACK_DEFAULTS))
+          @env[ASYNC_CALLBACK] = @response_write ||= method(:response_write)
+
+          response = catch(:async) { G.app.call(@env.update(RACK_DEFAULTS)) }
+
+          # too tricky to support pipelining with :async since the
+          # second (pipelined) request could be a stuck behind a
+          # long-running async response
+          (response.nil? || -1 == response.first) and return @state = :close
+
           alive &&= G.alive
           out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
           response_write(response, out, alive)
@@ -64,7 +75,7 @@ module Rainbows
         end while true
       end
 
-      def response_write(response, out, alive)
+      def response_write(response, out = [], alive = false)
         body = response.last
         unless body.respond_to?(:to_path)
           HttpResponse.write(self, response, out)
diff --git a/t/async_sinatra.ru b/t/async_sinatra.ru
new file mode 100644
index 0000000..94a24b1
--- /dev/null
+++ b/t/async_sinatra.ru
@@ -0,0 +1,13 @@
+# See http://github.com/raggi/async_sinatra
+# gem install async_sinatra -v0.1.5
+require 'sinatra/async'
+
+class AsyncTest < Sinatra::Base
+  register Sinatra::Async
+
+  aget '/:n' do |n|
+    EM.add_timer(n.to_i) { body { "delayed for #{n} seconds\n" } }
+  end
+end
+
+run AsyncTest.new
diff --git a/t/t0300-async_sinatra.sh b/t/t0300-async_sinatra.sh
new file mode 100755
index 0000000..3cf729c
--- /dev/null
+++ b/t/t0300-async_sinatra.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+. ./test-lib.sh
+
+# n - number of seconds to sleep
+n=10
+CONFIG_RU=async_sinatra.ru
+case $model in
+EventMachine) ;;
+*)
+        t_info "skipping $T since it's not compatible with $model"
+        exit 0
+        ;;
+esac
+
+t_plan 7 "async_sinatra test for EM"
+
+t_begin "setup and start" && {
+        rainbows_setup
+        rtmpfiles a b c curl_err
+
+        # Async Sinatra does not support Rack::Lint
+        rainbows -E none -D $CONFIG_RU -c $unicorn_config
+        rainbows_wait_start
+}
+
+t_begin "send async requests off in parallel" && {
+        t0=$(date +%s)
+        ( curl --no-buffer -sSf http://$listen/$n 2>> $curl_err | utee $a) &
+        ( curl --no-buffer -sSf http://$listen/$n 2>> $curl_err | utee $b) &
+        ( curl --no-buffer -sSf http://$listen/$n 2>> $curl_err | utee $c) &
+}
+
+t_begin "ensure elapsed requests were processed in parallel" && {
+        wait
+        t1=$(date +%s)
+        elapsed=$(( $t1 - $t0 ))
+        echo "elapsed=$elapsed < 30"
+        test $elapsed -lt 30
+}
+
+t_begin "termination signal sent" && {
+        kill $rainbows_pid
+}
+
+dbgcat a
+dbgcat b
+dbgcat c
+dbgcat r_err
+dbgcat curl_err
+
+t_begin "no errors from curl" && {
+        test ! -s $curl_err
+}
+
+t_begin "no errors in stderr" && check_stderr
+
+dbgcat r_err
+
+t_begin "no responses are chunked" && {
+        test x"$(cat $a)" = x"delayed for $n seconds"
+        test x"$(cat $b)" = x"delayed for $n seconds"
+        test x"$(cat $c)" = x"delayed for $n seconds"
+}
+
+t_done