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.
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +2 -3
- data/Rakefile +5 -2
- data/lib/rainbows.rb +72 -54
- data/lib/rainbows/base.rb +7 -9
- data/lib/rainbows/client.rb +25 -4
- data/lib/rainbows/const.rb +1 -1
- data/lib/rainbows/coolio.rb +6 -3
- data/lib/rainbows/coolio/client.rb +78 -57
- data/lib/rainbows/coolio/core.rb +1 -4
- data/lib/rainbows/coolio/heartbeat.rb +2 -3
- data/lib/rainbows/coolio/master.rb +3 -2
- data/lib/rainbows/coolio/{deferred_chunk_response.rb → response_chunk_pipe.rb} +1 -2
- data/lib/rainbows/coolio/{deferred_response.rb → response_pipe.rb} +1 -1
- data/lib/rainbows/coolio/thread_client.rb +4 -6
- data/lib/rainbows/coolio_fiber_spawn.rb +1 -1
- data/lib/rainbows/coolio_thread_pool.rb +1 -1
- data/lib/rainbows/coolio_thread_pool/watcher.rb +2 -4
- data/lib/rainbows/coolio_thread_spawn.rb +1 -1
- data/lib/rainbows/dev_fd_response.rb +2 -2
- data/lib/rainbows/error.rb +5 -7
- data/lib/rainbows/ev_core.rb +20 -7
- data/lib/rainbows/event_machine.rb +6 -5
- data/lib/rainbows/event_machine/client.rb +46 -53
- data/lib/rainbows/event_machine/response_pipe.rb +2 -3
- data/lib/rainbows/fiber/base.rb +5 -5
- data/lib/rainbows/fiber/body.rb +4 -13
- data/lib/rainbows/fiber/coolio/heartbeat.rb +1 -3
- data/lib/rainbows/fiber/coolio/server.rb +4 -7
- data/lib/rainbows/fiber_pool.rb +1 -1
- data/lib/rainbows/fiber_spawn.rb +2 -2
- data/lib/rainbows/http_parser.rb +12 -0
- data/lib/rainbows/http_server.rb +5 -7
- data/lib/rainbows/max_body.rb +2 -2
- data/lib/rainbows/never_block/core.rb +1 -1
- data/lib/rainbows/process_client.rb +15 -29
- data/lib/rainbows/queue_pool.rb +1 -3
- data/lib/rainbows/rack_input.rb +3 -3
- data/lib/rainbows/response.rb +164 -38
- data/lib/rainbows/revactor.rb +5 -65
- data/lib/rainbows/revactor/client.rb +60 -0
- data/lib/rainbows/revactor/{body.rb → client/methods.rb} +14 -14
- data/lib/rainbows/revactor/{tee_socket.rb → client/tee_socket.rb} +1 -1
- data/lib/rainbows/sendfile.rb +1 -2
- data/lib/rainbows/server_token.rb +1 -1
- data/lib/rainbows/thread_pool.rb +9 -9
- data/lib/rainbows/thread_spawn.rb +7 -6
- data/lib/rainbows/thread_timeout.rb +1 -1
- data/lib/rainbows/writer_thread_pool.rb +9 -25
- data/lib/rainbows/writer_thread_pool/client.rb +44 -1
- data/lib/rainbows/writer_thread_spawn.rb +2 -11
- data/lib/rainbows/writer_thread_spawn/client.rb +53 -13
- data/rainbows.gemspec +3 -12
- data/t/async_chunk_app.ru +62 -0
- data/t/byte-range-common.sh +142 -0
- data/t/t0022-copy_stream-byte-range.sh +2 -111
- data/t/t0023-sendfile-byte-range.sh +2 -32
- data/t/t0025-write-on-close.sh +23 -0
- data/t/t0040-keepalive_requests-setting.sh +0 -5
- data/t/t0402-async-keepalive.sh +146 -0
- data/t/t0500-cramp-streaming.sh +2 -0
- data/t/t0501-cramp-rainsocket.sh +2 -0
- data/t/t9000-rack-app-pool.sh +1 -1
- data/t/test_isolate.rb +5 -10
- data/t/test_isolate_cramp.rb +26 -0
- data/t/write-on-close.ru +11 -0
- metadata +33 -30
- data/lib/rainbows/coolio/sendfile.rb +0 -17
- data/lib/rainbows/response/body.rb +0 -127
- data/lib/rainbows/response/range.rb +0 -34
- data/lib/rainbows/timed_read.rb +0 -28
data/GIT-VERSION-GEN
CHANGED
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
|
-
|
84
|
+
_, subject, body = `git cat-file tag v#{version}`.split(/\n\n/, 3)
|
85
85
|
tmp = Tempfile.new('fm-changelog')
|
86
|
-
tmp.
|
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
|
-
|
5
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
55
|
-
|
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
|
-
|
73
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
82
|
+
# :stopdoc:
|
83
|
+
@alive = true
|
84
|
+
@cur = 0
|
85
|
+
@tick_mod = 0
|
86
|
+
@expire = nil
|
81
87
|
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
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) {
|
26
|
+
trap(:QUIT) { Rainbows.quit! }
|
32
27
|
[:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
|
33
|
-
Rainbows::ProcessClient.const_set(: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:
|
data/lib/rainbows/client.rb
CHANGED
@@ -1,9 +1,30 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
# :enddoc:
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
# this class is used for most synchronous concurrency models
|
6
5
|
class Rainbows::Client < Kgio::Socket
|
7
|
-
|
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
|
data/lib/rainbows/const.rb
CHANGED
data/lib/rainbows/coolio.rb
CHANGED
@@ -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 :
|
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
|
-
|
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
|
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
|
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
|
71
|
+
nil == @deferred && @_write_buffer.empty? and close.nil?
|
84
72
|
end
|
85
73
|
|
86
74
|
# used for streaming sockets and pipes
|
87
|
-
def
|
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
|
-
|
92
|
-
|
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
|
96
|
-
|
97
|
-
|
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
|
-
|
98
|
+
def ev_write_response(status, headers, body, alive)
|
100
99
|
if body.respond_to?(:to_path)
|
101
|
-
|
102
|
-
|
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
|
-
|
119
|
-
|
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
|
-
|
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
|
-
|
129
|
-
|
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
|
137
|
-
when
|
124
|
+
when true then return # #next! will clear this bit
|
125
|
+
when nil # fall through
|
138
126
|
else
|
139
127
|
begin
|
140
|
-
return
|
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
|
-
|
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
|
-
|
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
|