rainbows 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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