From 39f625fff05d457b01f088017f463a86d3b6c626 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 16 May 2011 19:04:06 +0000 Subject: add "copy_stream" config directive This allows using IO::Splice.copy_stream from the "io_splice" RubyGem on recent Linux systems. This also allows users to disable copy_stream usage entirely and use traditional response_body.each calls which are compatible with all Rack servers (to workaround bugs in IO.copy_stream under 1.9.2-p180). --- lib/rainbows/configurator.rb | 39 ++++++++++++++++++++++++++++++ lib/rainbows/http_server.rb | 1 + lib/rainbows/response.rb | 21 ++++++++-------- lib/rainbows/writer_thread_pool/client.rb | 2 +- lib/rainbows/writer_thread_spawn/client.rb | 2 +- 5 files changed, 53 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/rainbows/configurator.rb b/lib/rainbows/configurator.rb index 29d905b..a1d90cb 100644 --- a/lib/rainbows/configurator.rb +++ b/lib/rainbows/configurator.rb @@ -27,6 +27,7 @@ module Rainbows::Configurator :keepalive_requests => 100, :client_max_body_size => 1024 * 1024, :client_header_buffer_size => 1024, + :copy_stream => IO.respond_to?(:copy_stream) ? IO : false, }) # Configures \Rainbows! with a given concurrency model to +use+ and @@ -161,6 +162,44 @@ module Rainbows::Configurator check! set_int(:client_header_buffer_size, bytes, 1) end + + # Allows overriding the +klass+ where the +copy_stream+ method is + # used to do efficient copying of regular files, pipes, and sockets. + # + # This is only used with multi-threaded concurrency models: + # + # * ThreadSpawn + # * ThreadPool + # * WriterThreadSpawn + # * WriterThreadPool + # * XEpollThreadSpawn + # * XEpollThreadPool + # + # Due to existing {bugs}[http://redmine.ruby-lang.org/search?q=copy_stream] + # in the Ruby IO.copy_stream implementation, \Rainbows! uses the + # "sendfile" RubyGem that instead of copy_stream to transfer regular files + # to clients. The "sendfile" RubyGem also supports more operating systems, + # and works with more concurrency models. + # + # Recent Linux 2.6 users may override this with "IO::Splice" from the + # "io_splice" RubyGem: + # + # require "io/splice" + # Rainbows! do + # copy_stream IO::Splice + # end + # + # Keep in mind that splice(2) itself is a relatively new system call + # and has been buggy in many older Linux kernels. + # + # Default: IO on Ruby 1.9+, false otherwise + def copy_stream(klass) + check! + if klass && ! klass.respond_to?(:copy_stream) + abort "#{klass} must respond to `copy_stream' or be `false'" + end + set[:copy_stream] = klass + end end # :enddoc: diff --git a/lib/rainbows/http_server.rb b/lib/rainbows/http_server.rb index 62a5927..0fbc38f 100644 --- a/lib/rainbows/http_server.rb +++ b/lib/rainbows/http_server.rb @@ -2,6 +2,7 @@ # :enddoc: class Rainbows::HttpServer < Unicorn::HttpServer + attr_accessor :copy_stream attr_accessor :worker_connections attr_accessor :keepalive_timeout attr_accessor :client_header_buffer_size diff --git a/lib/rainbows/response.rb b/lib/rainbows/response.rb index 65599e9..fac2c0e 100644 --- a/lib/rainbows/response.rb +++ b/lib/rainbows/response.rb @@ -6,6 +6,7 @@ module Rainbows::Response KeepAlive = "keep-alive" Content_Length = "Content-Length".freeze Transfer_Encoding = "Transfer-Encoding".freeze + Rainbows.config!(self, :copy_stream) # private file class for IO objects opened by Rainbows! itself (and not # the app or middleware) @@ -67,7 +68,7 @@ module Rainbows::Response end # generic response writer, used for most dynamically-generated responses - # and also when IO.copy_stream and/or IO#trysendfile is unavailable + # and also when copy_stream and/or IO#trysendfile is unavailable def write_response(status, headers, body, alive) write_headers(status, headers, alive) write_body_each(body) @@ -89,29 +90,29 @@ module Rainbows::Response include Sendfile end - if IO.respond_to?(:copy_stream) + if COPY_STREAM unless IO.method_defined?(:trysendfile) module CopyStream def write_body_file(body, range) - range ? IO.copy_stream(body, self, range[1], range[0]) : - IO.copy_stream(body, self, nil, 0) + range ? COPY_STREAM.copy_stream(body, self, range[1], range[0]) : + COPY_STREAM.copy_stream(body, self, nil, 0) end end include CopyStream end - # write_body_stream is an alias for write_body_each if IO.copy_stream + # write_body_stream is an alias for write_body_each if copy_stream # isn't used or available. def write_body_stream(body) - IO.copy_stream(io = body_to_io(body), self) + COPY_STREAM.copy_stream(io = body_to_io(body), self) ensure close_if_private(io) end - else # ! IO.respond_to?(:copy_stream) + else # ! COPY_STREAM alias write_body_stream write_body_each - end # ! IO.respond_to?(:copy_stream) + end # ! COPY_STREAM - if IO.method_defined?(:trysendfile) || IO.respond_to?(:copy_stream) + if IO.method_defined?(:trysendfile) || COPY_STREAM HTTP_RANGE = 'HTTP_RANGE' Content_Range = 'Content-Range'.freeze @@ -181,5 +182,5 @@ module Rainbows::Response end end include ToPath - end # IO.respond_to?(:copy_stream) || IO.method_defined?(:trysendfile) + end # COPY_STREAM || IO.method_defined?(:trysendfile) end diff --git a/lib/rainbows/writer_thread_pool/client.rb b/lib/rainbows/writer_thread_pool/client.rb index f02826e..fd537ed 100644 --- a/lib/rainbows/writer_thread_pool/client.rb +++ b/lib/rainbows/writer_thread_pool/client.rb @@ -18,7 +18,7 @@ class Rainbows::WriterThreadPool::Client < Struct.new(:to_io, :q) } end - if IO.respond_to?(:copy_stream) || IO.method_defined?(:trysendfile) + if Rainbows::Response::COPY_STREAM def write_response(status, headers, body, alive) if body.respond_to?(:close) write_response_close(status, headers, body, alive) diff --git a/lib/rainbows/writer_thread_spawn/client.rb b/lib/rainbows/writer_thread_spawn/client.rb index e5c8854..de51c17 100644 --- a/lib/rainbows/writer_thread_spawn/client.rb +++ b/lib/rainbows/writer_thread_spawn/client.rb @@ -21,7 +21,7 @@ class Rainbows::WriterThreadSpawn::Client < Struct.new(:to_io, :q, :thr) } end - if IO.respond_to?(:copy_stream) || IO.method_defined?(:trysendfile) + if Rainbows::Response::COPY_STREAM def write_response(status, headers, body, alive) self.q ||= queue_writer if body.respond_to?(:close) -- cgit v1.2.3-24-ge0c7