rainbows 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. data/GIT-VERSION-GEN +1 -1
  2. data/GNUmakefile +2 -3
  3. data/Rakefile +5 -2
  4. data/lib/rainbows.rb +72 -54
  5. data/lib/rainbows/base.rb +7 -9
  6. data/lib/rainbows/client.rb +25 -4
  7. data/lib/rainbows/const.rb +1 -1
  8. data/lib/rainbows/coolio.rb +6 -3
  9. data/lib/rainbows/coolio/client.rb +78 -57
  10. data/lib/rainbows/coolio/core.rb +1 -4
  11. data/lib/rainbows/coolio/heartbeat.rb +2 -3
  12. data/lib/rainbows/coolio/master.rb +3 -2
  13. data/lib/rainbows/coolio/{deferred_chunk_response.rb → response_chunk_pipe.rb} +1 -2
  14. data/lib/rainbows/coolio/{deferred_response.rb → response_pipe.rb} +1 -1
  15. data/lib/rainbows/coolio/thread_client.rb +4 -6
  16. data/lib/rainbows/coolio_fiber_spawn.rb +1 -1
  17. data/lib/rainbows/coolio_thread_pool.rb +1 -1
  18. data/lib/rainbows/coolio_thread_pool/watcher.rb +2 -4
  19. data/lib/rainbows/coolio_thread_spawn.rb +1 -1
  20. data/lib/rainbows/dev_fd_response.rb +2 -2
  21. data/lib/rainbows/error.rb +5 -7
  22. data/lib/rainbows/ev_core.rb +20 -7
  23. data/lib/rainbows/event_machine.rb +6 -5
  24. data/lib/rainbows/event_machine/client.rb +46 -53
  25. data/lib/rainbows/event_machine/response_pipe.rb +2 -3
  26. data/lib/rainbows/fiber/base.rb +5 -5
  27. data/lib/rainbows/fiber/body.rb +4 -13
  28. data/lib/rainbows/fiber/coolio/heartbeat.rb +1 -3
  29. data/lib/rainbows/fiber/coolio/server.rb +4 -7
  30. data/lib/rainbows/fiber_pool.rb +1 -1
  31. data/lib/rainbows/fiber_spawn.rb +2 -2
  32. data/lib/rainbows/http_parser.rb +12 -0
  33. data/lib/rainbows/http_server.rb +5 -7
  34. data/lib/rainbows/max_body.rb +2 -2
  35. data/lib/rainbows/never_block/core.rb +1 -1
  36. data/lib/rainbows/process_client.rb +15 -29
  37. data/lib/rainbows/queue_pool.rb +1 -3
  38. data/lib/rainbows/rack_input.rb +3 -3
  39. data/lib/rainbows/response.rb +164 -38
  40. data/lib/rainbows/revactor.rb +5 -65
  41. data/lib/rainbows/revactor/client.rb +60 -0
  42. data/lib/rainbows/revactor/{body.rb → client/methods.rb} +14 -14
  43. data/lib/rainbows/revactor/{tee_socket.rb → client/tee_socket.rb} +1 -1
  44. data/lib/rainbows/sendfile.rb +1 -2
  45. data/lib/rainbows/server_token.rb +1 -1
  46. data/lib/rainbows/thread_pool.rb +9 -9
  47. data/lib/rainbows/thread_spawn.rb +7 -6
  48. data/lib/rainbows/thread_timeout.rb +1 -1
  49. data/lib/rainbows/writer_thread_pool.rb +9 -25
  50. data/lib/rainbows/writer_thread_pool/client.rb +44 -1
  51. data/lib/rainbows/writer_thread_spawn.rb +2 -11
  52. data/lib/rainbows/writer_thread_spawn/client.rb +53 -13
  53. data/rainbows.gemspec +3 -12
  54. data/t/async_chunk_app.ru +62 -0
  55. data/t/byte-range-common.sh +142 -0
  56. data/t/t0022-copy_stream-byte-range.sh +2 -111
  57. data/t/t0023-sendfile-byte-range.sh +2 -32
  58. data/t/t0025-write-on-close.sh +23 -0
  59. data/t/t0040-keepalive_requests-setting.sh +0 -5
  60. data/t/t0402-async-keepalive.sh +146 -0
  61. data/t/t0500-cramp-streaming.sh +2 -0
  62. data/t/t0501-cramp-rainsocket.sh +2 -0
  63. data/t/t9000-rack-app-pool.sh +1 -1
  64. data/t/test_isolate.rb +5 -10
  65. data/t/test_isolate_cramp.rb +26 -0
  66. data/t/write-on-close.ru +11 -0
  67. metadata +33 -30
  68. data/lib/rainbows/coolio/sendfile.rb +0 -17
  69. data/lib/rainbows/response/body.rb +0 -127
  70. data/lib/rainbows/response/range.rb +0 -34
  71. data/lib/rainbows/timed_read.rb +0 -28
data/GIT-VERSION-GEN CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v2.1.0.GIT
4
+ DEF_VER=v3.0.0.GIT
5
5
 
6
6
  LF='
7
7
  '
data/GNUmakefile CHANGED
@@ -9,9 +9,6 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
9
9
  @./GIT-VERSION-GEN
10
10
  -include GIT-VERSION-FILE
11
11
  -include local.mk
12
- ifeq ($(DLEXT),) # "so" for Linux
13
- DLEXT := $(shell $(RUBY) -rrbconfig -e 'puts Config::CONFIG["DLEXT"]')
14
- endif
15
12
  ifeq ($(RUBY_VERSION),)
16
13
  RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
17
14
  endif
@@ -45,6 +42,8 @@ clean:
45
42
  man html:
46
43
  $(MAKE) -C Documentation install-$@
47
44
 
45
+ pkg_extra := GIT-VERSION-FILE ChangeLog LATEST NEWS $(man1_paths)
46
+
48
47
  ChangeLog: GIT-VERSION-FILE .wrongdoc.yml
49
48
  wrongdoc prepare
50
49
 
data/Rakefile CHANGED
@@ -81,9 +81,12 @@ task :fm_update do
81
81
  uri = URI.parse('http://freshmeat.net/projects/rainbows/releases.json')
82
82
  rc = Net::Netrc.locate('rainbows-fm') or abort "~/.netrc not found"
83
83
  api_token = rc.password
84
- changelog = tags.find { |t| t[:tag] == "v#{version}" }[:body]
84
+ _, subject, body = `git cat-file tag v#{version}`.split(/\n\n/, 3)
85
85
  tmp = Tempfile.new('fm-changelog')
86
- tmp.syswrite(changelog)
86
+ tmp.puts subject
87
+ tmp.puts
88
+ tmp.puts body
89
+ tmp.flush
87
90
  system(ENV["VISUAL"], tmp.path) or abort "#{ENV["VISUAL"]} failed: #$?"
88
91
  changelog = File.read(tmp.path).strip
89
92
 
data/lib/rainbows.rb CHANGED
@@ -1,31 +1,19 @@
1
1
  # -*- encoding: binary -*-
2
2
  require 'kgio'
3
3
  require 'unicorn'
4
- # the value passed to TCP_DEFER_ACCEPT actually matters in Linux 2.6.32+
5
- Unicorn::SocketHelper::DEFAULTS[:tcp_defer_accept] = 60
4
+ Unicorn::SocketHelper::DEFAULTS.merge!({
5
+ # the value passed to TCP_DEFER_ACCEPT actually matters in Linux 2.6.32+
6
+ :tcp_defer_accept => 60,
6
7
 
7
- module Rainbows
8
+ # keep-alive performance sucks without this due to
9
+ # write(headers)-write(body)-read
10
+ # because we always write headers and bodies with two calls
11
+ :tcp_nodelay => true,
12
+ })
8
13
 
9
- # global vars because class/instance variables are confusing me :<
10
- # this struct is only accessed inside workers and thus private to each
11
- # G.cur may not be used in the network concurrency model
12
- # :stopdoc:
13
- class State < Struct.new(:alive,:m,:cur,:kato,:server,:tmp,:expire)
14
- def tick
15
- tmp.chmod(self.m = m == 0 ? 1 : 0)
16
- exit!(2) if expire && Time.now >= expire
17
- alive && server.master_pid == Process.ppid or quit!
18
- end
14
+ module Rainbows
19
15
 
20
- def quit!
21
- self.alive = false
22
- self.expire ||= Time.now + (server.timeout * 2.0)
23
- server.class.const_get(:LISTENERS).map! { |s| s.close rescue nil }
24
- false
25
- end
26
- end
27
- G = State.new(true, 0, 0, 5)
28
- O = {}
16
+ O = {} # :nodoc:
29
17
  class Response416 < RangeError; end
30
18
 
31
19
  # map of numeric file descriptors to IO objects to avoid using IO.new
@@ -36,10 +24,12 @@ module Rainbows
36
24
  # :startdoc:
37
25
 
38
26
  require 'rainbows/const'
27
+ require 'rainbows/http_parser'
39
28
  require 'rainbows/http_server'
40
- require 'rainbows/response'
41
- require 'rainbows/client'
42
- require 'rainbows/process_client'
29
+ autoload :RackInput, 'rainbows/rack_input'
30
+ autoload :Response, 'rainbows/response'
31
+ autoload :ProcessClient, 'rainbows/process_client'
32
+ autoload :Client, 'rainbows/client'
43
33
  autoload :Base, 'rainbows/base'
44
34
  autoload :Sendfile, 'rainbows/sendfile'
45
35
  autoload :AppPool, 'rainbows/app_pool'
@@ -49,42 +39,70 @@ module Rainbows
49
39
  autoload :EvCore, 'rainbows/ev_core'
50
40
  autoload :SocketProxy, 'rainbows/socket_proxy'
51
41
 
42
+ # Sleeps the current application dispatch. This will pick the
43
+ # optimal method to sleep depending on the concurrency model chosen
44
+ # (which may still suck and block the entire process). Using this
45
+ # with the basic :Coolio or :EventMachine models is not recommended.
46
+ # This should be used within your Rack application.
47
+ def self.sleep(nr)
48
+ case Rainbows.server.use
49
+ when :FiberPool, :FiberSpawn
50
+ Rainbows::Fiber.sleep(nr)
51
+ when :RevFiberSpawn, :CoolioFiberSpawn
52
+ Rainbows::Fiber::Coolio::Sleeper.new(nr)
53
+ when :Revactor
54
+ Actor.sleep(nr)
55
+ else
56
+ Kernel.sleep(nr)
57
+ end
58
+ end
59
+
60
+ # runs the Rainbows! HttpServer with +app+ and +options+ and does
61
+ # not return until the server has exited.
62
+ def self.run(app, options = {}) # :nodoc:
63
+ HttpServer.new(app, options).start.join
64
+ end
65
+
66
+ # :stopdoc:
52
67
  class << self
68
+ attr_accessor :max_bytes, :keepalive_timeout
69
+ attr_accessor :server
70
+ attr_accessor :cur # may not always be used
71
+ attr_reader :alive
72
+ attr_writer :tick_io
73
+ end
74
+ # :startdoc:
53
75
 
54
- # Sleeps the current application dispatch. This will pick the
55
- # optimal method to sleep depending on the concurrency model chosen
56
- # (which may still suck and block the entire process). Using this
57
- # with the basic :Coolio or :EventMachine models is not recommended.
58
- # This should be used within your Rack application.
59
- def sleep(nr)
60
- case G.server.use
61
- when :FiberPool, :FiberSpawn
62
- Rainbows::Fiber.sleep(nr)
63
- when :RevFiberSpawn, :CoolioFiberSpawn
64
- Rainbows::Fiber::Coolio::Sleeper.new(nr)
65
- when :Revactor
66
- Actor.sleep(nr)
67
- else
68
- Kernel.sleep(nr)
69
- end
70
- end
76
+ # the default max body size is 1 megabyte (1024 * 1024 bytes)
77
+ @max_bytes = 1024 * 1024
71
78
 
72
- # runs the Rainbows! HttpServer with +app+ and +options+ and does
73
- # not return until the server has exited.
74
- def run(app, options = {}) # :nodoc:
75
- HttpServer.new(app, options).start.join
76
- end
79
+ # the default keepalive_timeout is 5 seconds
80
+ @keepalive_timeout = 5
77
81
 
78
- # :stopdoc:
79
- # the default max body size is 1 megabyte (1024 * 1024 bytes)
80
- @@max_bytes = 1024 * 1024
82
+ # :stopdoc:
83
+ @alive = true
84
+ @cur = 0
85
+ @tick_mod = 0
86
+ @expire = nil
81
87
 
82
- def max_bytes; @@max_bytes; end
83
- def max_bytes=(nr); @@max_bytes = nr; end
84
- # :startdoc:
88
+ def self.tick
89
+ @tick_io.chmod(@tick_mod = 0 == @tick_mod ? 1 : 0)
90
+ exit!(2) if @expire && Time.now >= @expire
91
+ @alive && @server.master_pid == Process.ppid or quit!
92
+ end
93
+
94
+ def self.cur_alive
95
+ @alive || @cur > 0
96
+ end
97
+
98
+ def self.quit!
99
+ @alive = false
100
+ Rainbows::HttpParser.quit
101
+ @expire ||= Time.now + (@server.timeout * 2.0)
102
+ @server.class.const_get(:LISTENERS).map! { |s| s.close rescue nil }
103
+ false
85
104
  end
86
105
 
87
- # :stopdoc:
88
106
  # maps models to default worker counts, default worker count numbers are
89
107
  # pretty arbitrary and tuning them to your application and hardware is
90
108
  # highly recommended
data/lib/rainbows/base.rb CHANGED
@@ -6,12 +6,7 @@
6
6
  # not intended for production use, as keepalive with a pure prefork
7
7
  # concurrency model is extremely expensive.
8
8
  module Rainbows::Base
9
-
10
9
  # :stopdoc:
11
- include Rainbows::ProcessClient
12
-
13
- # shortcuts...
14
- G = Rainbows::G
15
10
 
16
11
  # this method is called by all current concurrency models
17
12
  def init_worker_process(worker) # :nodoc:
@@ -19,7 +14,7 @@ module Rainbows::Base
19
14
  Rainbows::Response.setup(self.class)
20
15
  Rainbows::MaxBody.setup
21
16
  Rainbows::RackInput.setup
22
- G.tmp = worker.tmp
17
+ Rainbows.tick_io = worker.tmp
23
18
 
24
19
  listeners = Rainbows::HttpServer::LISTENERS
25
20
  Rainbows::HttpServer::IO_PURGATORY.concat(listeners)
@@ -28,15 +23,18 @@ module Rainbows::Base
28
23
  # since we don't defer reopening logs
29
24
  Rainbows::HttpServer::SELF_PIPE.each { |x| x.close }.clear
30
25
  trap(:USR1) { reopen_worker_logs(worker.nr) }
31
- trap(:QUIT) { G.quit! }
26
+ trap(:QUIT) { Rainbows.quit! }
32
27
  [:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
33
- Rainbows::ProcessClient.const_set(:APP, G.server.app)
28
+ Rainbows::ProcessClient.const_set(:APP, Rainbows.server.app)
34
29
  logger.info "Rainbows! #@use worker_connections=#@worker_connections"
35
30
  end
36
31
 
32
+ def process_client(client)
33
+ client.process_loop
34
+ end
35
+
37
36
  def self.included(klass) # :nodoc:
38
37
  klass.const_set :LISTENERS, Rainbows::HttpServer::LISTENERS
39
- klass.const_set :G, Rainbows::G
40
38
  end
41
39
 
42
40
  # :startdoc:
@@ -1,9 +1,30 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
3
 
4
- require 'rainbows/timed_read'
5
-
4
+ # this class is used for most synchronous concurrency models
6
5
  class Rainbows::Client < Kgio::Socket
7
- include Rainbows::TimedRead
6
+ def read_expire
7
+ Time.now + Rainbows.keepalive_timeout
8
+ end
9
+
10
+ def kgio_wait_readable
11
+ IO.select([self], nil, nil, Rainbows.keepalive_timeout)
12
+ end
13
+
14
+ # used for reading headers (respecting keepalive_timeout)
15
+ def timed_read(buf)
16
+ expire = nil
17
+ begin
18
+ case rv = kgio_tryread(16384, buf)
19
+ when :wait_readable
20
+ return if expire && expire < Time.now
21
+ expire ||= read_expire
22
+ kgio_wait_readable
23
+ else
24
+ return rv
25
+ end
26
+ end while true
27
+ end
28
+
29
+ include Rainbows::ProcessClient
8
30
  end
9
- Kgio.accept_class = Rainbows::Client
@@ -2,7 +2,7 @@
2
2
  # :enddoc:
3
3
  module Rainbows::Const
4
4
 
5
- RAINBOWS_VERSION = '2.1.0'
5
+ RAINBOWS_VERSION = '3.0.0'
6
6
 
7
7
  include Unicorn::Const
8
8
 
@@ -18,6 +18,9 @@ require 'rainbows/coolio_support'
18
18
  # allows the Rack application to process data as it arrives. This
19
19
  # means "rack.input" will be fully buffered in memory or to a
20
20
  # temporary file before the application is entered.
21
+ #
22
+ # This model is mostly compatible with users of "async.callback" in
23
+ # the Rack environment as long as they do not depend on EventMachine.
21
24
  module Rainbows::Coolio
22
25
  # :stopdoc:
23
26
  # keep-alive timeout scoreboard
@@ -31,15 +34,15 @@ module Rainbows::Coolio
31
34
  KATO.compare_by_identity
32
35
  end
33
36
 
37
+ autoload :Client, 'rainbows/coolio/client'
34
38
  autoload :Master, 'rainbows/coolio/master'
35
39
  autoload :ThreadClient, 'rainbows/coolio/thread_client'
36
- autoload :DeferredChunkResponse, 'rainbows/coolio/deferred_chunk_response'
40
+ autoload :ResponsePipe, 'rainbows/coolio/response_pipe'
41
+ autoload :ResponseChunkPipe, 'rainbows/coolio/response_chunk_pipe'
37
42
  # :startdoc:
38
43
  end
39
44
  # :enddoc:
40
45
  require 'rainbows/coolio/heartbeat'
41
46
  require 'rainbows/coolio/server'
42
47
  require 'rainbows/coolio/core'
43
- require 'rainbows/coolio/deferred_response'
44
- require 'rainbows/coolio/client'
45
48
  Rainbows::Coolio.__send__ :include, Rainbows::Coolio::Core
@@ -2,12 +2,9 @@
2
2
  # :enddoc:
3
3
  class Rainbows::Coolio::Client < Coolio::IO
4
4
  include Rainbows::EvCore
5
- G = Rainbows::G
6
- SF = Rainbows::StreamFile
7
5
  CONN = Rainbows::Coolio::CONN
8
6
  KATO = Rainbows::Coolio::KATO
9
- DeferredResponse = Rainbows::Coolio::DeferredResponse
10
- DeferredChunkResponse = Rainbows::Coolio::DeferredChunkResponse
7
+ LOOP = Coolio::Loop.default
11
8
 
12
9
  def initialize(io)
13
10
  CONN[self] = false
@@ -22,7 +19,7 @@ class Rainbows::Coolio::Client < Coolio::IO
22
19
 
23
20
  def quit
24
21
  super
25
- close if @deferred.nil? && @_write_buffer.empty?
22
+ close if nil == @deferred && @_write_buffer.empty?
26
23
  end
27
24
 
28
25
  # override the Coolio::IO#write method try to write directly to the
@@ -59,87 +56,78 @@ class Rainbows::Coolio::Client < Coolio::IO
59
56
  close
60
57
  end
61
58
 
62
- # queued, optional response bodies, it should only be unpollable "fast"
63
- # devices where read(2) is uninterruptable. Unfortunately, NFS and ilk
64
- # are also part of this. We'll also stick DeferredResponse bodies in
65
- # here to prevent connections from being closed on us.
66
- def defer_body(io)
67
- @deferred = io
68
- enable_write_watcher
69
- end
70
-
71
59
  # allows enabling of write watcher even when read watcher is disabled
72
60
  def evloop
73
- LOOP # this constant is set in when a worker starts
61
+ LOOP
74
62
  end
75
63
 
76
64
  def next!
77
65
  attached? or return
78
66
  @deferred = nil
79
- enable_write_watcher
67
+ enable_write_watcher # trigger on_write_complete
80
68
  end
81
69
 
82
70
  def timeout?
83
- @deferred.nil? && @_write_buffer.empty? and close.nil?
71
+ nil == @deferred && @_write_buffer.empty? and close.nil?
84
72
  end
85
73
 
86
74
  # used for streaming sockets and pipes
87
- def stream_response(status, headers, io, body)
88
- c = stream_response_headers(status, headers) if headers
75
+ def stream_response_body(body, io, chunk)
89
76
  # we only want to attach to the Coolio::Loop belonging to the
90
77
  # main thread in Ruby 1.9
91
- io = (c ? DeferredChunkResponse : DeferredResponse).new(io, self, body)
92
- defer_body(io.attach(LOOP))
78
+ (chunk ? Rainbows::Coolio::ResponseChunkPipe :
79
+ Rainbows::Coolio::ResponsePipe).new(io, self, body).attach(LOOP)
80
+ @deferred = true
93
81
  end
94
82
 
95
- def coolio_write_response(response, alive)
96
- status, headers, body = response
97
- headers = @hp.headers? ? HH.new(headers) : nil
83
+ def write_response_path(status, headers, body, alive)
84
+ io = body_to_io(body)
85
+ st = io.stat
86
+
87
+ if st.file?
88
+ defer_file(status, headers, body, alive, io, st)
89
+ elsif st.socket? || st.pipe?
90
+ chunk = stream_response_headers(status, headers, alive)
91
+ stream_response_body(body, io, chunk)
92
+ else
93
+ # char or block device... WTF?
94
+ write_response(status, headers, body, alive)
95
+ end
96
+ end
98
97
 
99
- headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE if headers
98
+ def ev_write_response(status, headers, body, alive)
100
99
  if body.respond_to?(:to_path)
101
- io = body_to_io(body)
102
- st = io.stat
103
-
104
- if st.file?
105
- offset, count = 0, st.size
106
- if headers
107
- if range = make_range!(@env, status, headers)
108
- status, offset, count = range
109
- end
110
- write(response_header(status, headers))
111
- end
112
- return defer_body(SF.new(offset, count, io, body))
113
- elsif st.socket? || st.pipe?
114
- return stream_response(status, headers, io, body)
115
- end
116
- # char or block device... WTF? fall through to body.each
100
+ write_response_path(status, headers, body, alive)
101
+ else
102
+ write_response(status, headers, body, alive)
117
103
  end
118
- write(response_header(status, headers)) if headers
119
- write_body_each(self, body, nil)
104
+ return quit unless alive && :close != @state
105
+ @state = :headers
120
106
  end
121
107
 
122
108
  def app_call
123
109
  KATO.delete(self)
110
+ disable if enabled?
124
111
  @env[RACK_INPUT] = @input
125
112
  @env[REMOTE_ADDR] = @_io.kgio_addr
126
- response = APP.call(@env.update(RACK_DEFAULTS))
113
+ @env[ASYNC_CALLBACK] = method(:write_async_response)
114
+ status, headers, body = catch(:async) {
115
+ APP.call(@env.merge!(RACK_DEFAULTS))
116
+ }
127
117
 
128
- coolio_write_response(response, alive = @hp.next? && G.alive)
129
- return quit unless alive && :close != @state
130
- @state = :headers
131
- disable if enabled?
118
+ (nil == status || -1 == status) ? @deferred = true :
119
+ ev_write_response(status, headers, body, @hp.next?)
132
120
  end
133
121
 
134
122
  def on_write_complete
135
123
  case @deferred
136
- when DeferredResponse then return
137
- when NilClass # fall through
124
+ when true then return # #next! will clear this bit
125
+ when nil # fall through
138
126
  else
139
127
  begin
140
- return rev_sendfile(@deferred)
128
+ return stream_file_chunk(@deferred)
141
129
  rescue EOFError # expected at file EOF
142
- close_deferred
130
+ close_deferred # fall through
143
131
  end
144
132
  end
145
133
 
@@ -171,13 +159,11 @@ class Rainbows::Coolio::Client < Coolio::IO
171
159
  end
172
160
 
173
161
  def close_deferred
174
- case @deferred
175
- when DeferredResponse, NilClass
176
- else
162
+ if @deferred
177
163
  begin
178
- @deferred.close
164
+ @deferred.close if @deferred.respond_to?(:close)
179
165
  rescue => e
180
- G.server.logger.error("closing #@deferred: #{e}")
166
+ Rainbows.server.logger.error("closing #@deferred: #{e}")
181
167
  end
182
168
  @deferred = nil
183
169
  end
@@ -188,4 +174,39 @@ class Rainbows::Coolio::Client < Coolio::IO
188
174
  CONN.delete(self)
189
175
  KATO.delete(self)
190
176
  end
177
+
178
+ if IO.method_defined?(:sendfile_nonblock)
179
+ def defer_file(status, headers, body, alive, io, st)
180
+ if r = sendfile_range(status, headers)
181
+ status, headers, range = r
182
+ write_headers(status, headers, alive)
183
+ range and defer_file_stream(range[0], range[1], io, body)
184
+ else
185
+ write_headers(status, headers, alive)
186
+ defer_file_stream(0, st.size, io, body)
187
+ end
188
+ end
189
+
190
+ def stream_file_chunk(sf) # +sf+ is a Rainbows::StreamFile object
191
+ sf.offset += (n = @_io.sendfile_nonblock(sf, sf.offset, sf.count))
192
+ 0 == (sf.count -= n) and raise EOFError
193
+ enable_write_watcher
194
+ rescue Errno::EAGAIN
195
+ enable_write_watcher
196
+ end
197
+ else
198
+ def defer_file(status, headers, body, alive, io, st)
199
+ write_headers(status, headers, alive)
200
+ defer_file_stream(0, st.size, io, body)
201
+ end
202
+
203
+ def stream_file_chunk(body)
204
+ write(body.to_io.sysread(0x4000))
205
+ end
206
+ end
207
+
208
+ def defer_file_stream(offset, count, io, body)
209
+ @deferred = Rainbows::StreamFile.new(offset, count, io, body)
210
+ enable_write_watcher
211
+ end
191
212
  end