Skip to content

Commit

Permalink
Add streaming support.
Browse files Browse the repository at this point in the history
  • Loading branch information
macournoyer committed Jan 30, 2013
1 parent 9ae51de commit e688a41
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 12 deletions.
1 change: 1 addition & 0 deletions lib/thin.rb
Expand Up @@ -11,6 +11,7 @@ module Thin
autoload :Command, "thin/command"
autoload :Connection, "thin/connection"
autoload :Daemonizable, "thin/daemonizing"
autoload :FastEnumerator, "thin/fast_enumerator"
autoload :Logging, "thin/logging"
autoload :Headers, "thin/headers"
autoload :Request, "thin/request"
Expand Down
29 changes: 17 additions & 12 deletions lib/thin/connection.rb
Expand Up @@ -102,24 +102,29 @@ def post_process(result)
@response.persistent! if @request.persistent?

# Send the response
@response.each do |chunk|
trace { chunk }
send_data chunk
responder = FastEnumerator.new(@response)

tick_look = EM.tick_loop do
if chunk = responder.next
trace chunk
send_data chunk
else
:stop
end
end

if @response.body.respond_to?(:callback) && @response.body.respond_to?(:errback)
# If the body is being deferred, then terminate afterward.
@response.body.callback &method(:terminate_request)
@response.body.errback &method(:terminate_request)
else
tick_look.on_stop method(:terminate_request)
end

rescue Exception
handle_error
# Close connection since we can't handle response gracefully
close_connection
ensure
# If the body is being deferred, then terminate afterward.
if @response.body.respond_to?(:callback) && @response.body.respond_to?(:errback)
@response.body.callback { terminate_request }
@response.body.errback { terminate_request }
else
# Don't terminate the response if we're going async.
terminate_request unless result && result.first == AsyncResponse.first
end
end

# Logs catched exception and closes the connection.
Expand Down
40 changes: 40 additions & 0 deletions lib/thin/fast_enumerator.rb
@@ -0,0 +1,40 @@
require 'fiber' if defined?(Fiber)

module Thin
# Turns out Enumerator#next is pretty slow and rolling our own w/ Fibers is a lot faster.
# Still not as fast as Enumerable#each but eh...
class FastEnumerator
if defined?(Fiber)

def initialize(array)
@fiber = Fiber.new do
array.each do |i|
Fiber.yield i
end
end
end

def next
nil unless @fiber.alive?
@fiber.resume
end

else

# Slow Enumerator based version for Rubies w/ no fibers.

def initialize(args)
@enum = array.each
end

def next
begin
@enum.next
rescue StopIteration
nil
end
end

end
end
end

0 comments on commit e688a41

Please sign in to comment.