rainbows 3.0.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.wrongdoc.yml +2 -2
- data/Documentation/comparison.haml +6 -6
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +16 -128
- data/README +2 -3
- data/Rakefile +3 -3
- data/examples/reverse_proxy.ru +9 -0
- data/lib/rainbows.rb +14 -6
- data/lib/rainbows/base.rb +0 -1
- data/lib/rainbows/const.rb +1 -10
- data/lib/rainbows/coolio/client.rb +8 -4
- data/lib/rainbows/coolio/core.rb +0 -3
- data/lib/rainbows/coolio/thread_client.rb +2 -2
- data/lib/rainbows/coolio_fiber_spawn.rb +6 -6
- data/lib/rainbows/dev_fd_response.rb +16 -9
- data/lib/rainbows/epoll.rb +43 -0
- data/lib/rainbows/epoll/client.rb +232 -0
- data/lib/rainbows/epoll/response_chunk_pipe.rb +18 -0
- data/lib/rainbows/epoll/response_pipe.rb +32 -0
- data/lib/rainbows/epoll/server.rb +31 -0
- data/lib/rainbows/error.rb +1 -9
- data/lib/rainbows/ev_core.rb +12 -12
- data/lib/rainbows/ev_core/cap_input.rb +1 -1
- data/lib/rainbows/event_machine.rb +0 -6
- data/lib/rainbows/event_machine/client.rb +3 -3
- data/lib/rainbows/event_machine/response_chunk_pipe.rb +5 -7
- data/lib/rainbows/event_machine/response_pipe.rb +7 -8
- data/lib/rainbows/fiber/base.rb +2 -2
- data/lib/rainbows/fiber/io.rb +21 -63
- data/lib/rainbows/fiber/io/methods.rb +1 -1
- data/lib/rainbows/http_server.rb +4 -4
- data/lib/rainbows/join_threads.rb +18 -0
- data/lib/rainbows/max_body.rb +2 -1
- data/lib/rainbows/max_body/wrapper.rb +1 -1
- data/lib/rainbows/never_block/event_machine.rb +2 -2
- data/lib/rainbows/process_client.rb +9 -1
- data/lib/rainbows/queue_pool.rb +2 -2
- data/lib/rainbows/response.rb +1 -1
- data/lib/rainbows/rev_fiber_spawn.rb +4 -4
- data/lib/rainbows/revactor/client.rb +4 -5
- data/lib/rainbows/revactor/proxy.rb +1 -1
- data/lib/rainbows/reverse_proxy.rb +189 -0
- data/lib/rainbows/reverse_proxy/coolio.rb +61 -0
- data/lib/rainbows/reverse_proxy/ev_client.rb +39 -0
- data/lib/rainbows/reverse_proxy/event_machine.rb +46 -0
- data/lib/rainbows/reverse_proxy/multi_thread.rb +7 -0
- data/lib/rainbows/reverse_proxy/synchronous.rb +21 -0
- data/lib/rainbows/sendfile.rb +1 -1
- data/lib/rainbows/sync_close.rb +2 -2
- data/lib/rainbows/thread_pool.rb +1 -1
- data/lib/rainbows/writer_thread_pool.rb +1 -1
- data/lib/rainbows/xepoll.rb +24 -0
- data/lib/rainbows/xepoll/client.rb +45 -0
- data/pkg.mk +171 -0
- data/rainbows.gemspec +2 -4
- data/t/GNUmakefile +4 -0
- data/t/bin/content-md5-put +1 -0
- data/t/kgio-pipe-response.ru +9 -1
- data/t/rack-fiber_pool/app.ru +6 -1
- data/t/simple-http_Epoll.ru +9 -0
- data/t/simple-http_XEpoll.ru +9 -0
- data/t/t0014-config-conflict.sh +5 -3
- data/t/t0023-sendfile-byte-range.sh +1 -7
- data/t/t0034-pipelined-pipe-response.sh +2 -1
- data/t/t0035-kgio-pipe-response.sh +0 -7
- data/t/t0041-optional-pool-size.sh +51 -0
- data/t/t0050-response-body-close-has-env.sh +2 -1
- data/t/t0104-rack-input-limit-tiny.sh +6 -4
- data/t/t0105-rack-input-limit-bigger.sh +6 -4
- data/t/t0106-rack-input-keepalive.sh +2 -0
- data/t/t0107-rack-input-limit-zero.sh +60 -0
- data/t/t0113-rewindable-input-false.sh +1 -0
- data/t/t0114-rewindable-input-true.sh +1 -0
- data/t/t0202-async-response-one-oh.sh +56 -0
- data/t/test_isolate.rb +5 -2
- metadata +42 -37
- data/lib/rainbows/rack_input.rb +0 -17
@@ -2,12 +2,12 @@
|
|
2
2
|
require 'rainbows/fiber/coolio'
|
3
3
|
|
4
4
|
# A combination of the Coolio and FiberSpawn models. This allows Ruby
|
5
|
-
# 1.9 Fiber-based concurrency for application processing while
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
5
|
+
# 1.9 Fiber-based concurrency for application processing while exposing
|
6
|
+
# a synchronous execution model and using scalable network concurrency
|
7
|
+
# provided by Cool.io. A streaming "rack.input" is exposed.
|
8
|
+
# Applications are strongly advised to wrap all slow IO objects
|
9
|
+
# (sockets, pipes) using the Rainbows::Fiber::IO or a Cool.io-compatible
|
10
|
+
# class whenever possible.
|
11
11
|
module Rainbows::CoolioFiberSpawn
|
12
12
|
|
13
13
|
include Rainbows::Base
|
@@ -15,6 +15,13 @@ class Rainbows::DevFdResponse < Struct.new(:app)
|
|
15
15
|
|
16
16
|
# :stopdoc:
|
17
17
|
FD_MAP = Rainbows::FD_MAP
|
18
|
+
Content_Length = "Content-Length".freeze
|
19
|
+
Transfer_Encoding = "Transfer-Encoding".freeze
|
20
|
+
Rainbows_autochunk = "rainbows.autochunk".freeze
|
21
|
+
Rainbows_model = "rainbows.model"
|
22
|
+
HTTP_1_0 = "HTTP/1.0"
|
23
|
+
HTTP_VERSION = "HTTP_VERSION"
|
24
|
+
Chunked = "chunked"
|
18
25
|
|
19
26
|
# make this a no-op under Rubinius, it's pointless anyways
|
20
27
|
# since Rubinius doesn't have IO.copy_stream
|
@@ -44,19 +51,19 @@ class Rainbows::DevFdResponse < Struct.new(:app)
|
|
44
51
|
fileno = io.fileno
|
45
52
|
FD_MAP[fileno] = io
|
46
53
|
if st.file?
|
47
|
-
headers[
|
48
|
-
headers.delete(
|
54
|
+
headers[Content_Length] ||= st.size.to_s
|
55
|
+
headers.delete(Transfer_Encoding)
|
49
56
|
elsif st.pipe? || st.socket? # epoll-able things
|
50
|
-
unless headers.include?(
|
51
|
-
if env[
|
52
|
-
headers[
|
57
|
+
unless headers.include?(Content_Length)
|
58
|
+
if env[Rainbows_autochunk] && HTTP_1_0 != env[HTTP_VERSION]
|
59
|
+
headers[Transfer_Encoding] = Chunked
|
53
60
|
else
|
54
|
-
|
61
|
+
env[Rainbows_autochunk] = false
|
55
62
|
end
|
56
63
|
end
|
57
64
|
|
58
65
|
# we need to make sure our pipe output is Fiber-compatible
|
59
|
-
case env[
|
66
|
+
case env[Rainbows_model]
|
60
67
|
when :FiberSpawn, :FiberPool, :RevFiberSpawn, :CoolioFiberSpawn
|
61
68
|
io.respond_to?(:kgio_wait_readable) or
|
62
69
|
io = Rainbows::Fiber::IO.new(io)
|
@@ -72,8 +79,8 @@ class Rainbows::DevFdResponse < Struct.new(:app)
|
|
72
79
|
class Body < Struct.new(:to_io, :to_path, :orig_body)
|
73
80
|
# called by the webserver or other middlewares if they can't
|
74
81
|
# handle #to_path
|
75
|
-
def each
|
76
|
-
to_io.each
|
82
|
+
def each
|
83
|
+
to_io.each { |x| yield x }
|
77
84
|
end
|
78
85
|
|
79
86
|
# remain Rack::Lint-compatible for people with wonky systems :P
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
require 'sleepy_penguin'
|
4
|
+
require 'sendfile'
|
5
|
+
|
6
|
+
# Edge-triggered epoll concurrency model. This is extremely unfair
|
7
|
+
# and optimized for throughput at the expense of fairness
|
8
|
+
module Rainbows::Epoll
|
9
|
+
include Rainbows::Base
|
10
|
+
ReRun = []
|
11
|
+
autoload :Server, 'rainbows/epoll/server'
|
12
|
+
autoload :Client, 'rainbows/epoll/client'
|
13
|
+
autoload :ResponsePipe, 'rainbows/epoll/response_pipe'
|
14
|
+
autoload :ResponseChunkPipe, 'rainbows/epoll/response_chunk_pipe'
|
15
|
+
class << self
|
16
|
+
attr_writer :nr_clients
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.loop
|
20
|
+
timeout = Rainbows.server.timeout
|
21
|
+
begin
|
22
|
+
EP.wait(nil, timeout) { |flags, obj| obj.epoll_run }
|
23
|
+
while obj = ReRun.shift
|
24
|
+
obj.epoll_run
|
25
|
+
end
|
26
|
+
Rainbows::Epoll::Client.expire
|
27
|
+
rescue Errno::EINTR
|
28
|
+
rescue => e
|
29
|
+
Rainbows::Error.listen_loop(e)
|
30
|
+
end while Rainbows.tick || @nr_clients.call > 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def init_worker_process(worker)
|
34
|
+
super
|
35
|
+
Rainbows::Epoll.const_set :EP, SleepyPenguin::Epoll.new
|
36
|
+
Rainbows::Client.__send__ :include, Client
|
37
|
+
end
|
38
|
+
|
39
|
+
def worker_loop(worker) # :nodoc:
|
40
|
+
init_worker_process(worker)
|
41
|
+
Server.run
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
|
4
|
+
module Rainbows::Epoll::Client
|
5
|
+
|
6
|
+
include Rainbows::EvCore
|
7
|
+
APP = Rainbows.server.app
|
8
|
+
Server = Rainbows::Epoll::Server
|
9
|
+
IN = SleepyPenguin::Epoll::IN | SleepyPenguin::Epoll::ET
|
10
|
+
OUT = SleepyPenguin::Epoll::OUT | SleepyPenguin::Epoll::ET
|
11
|
+
KATO = {}
|
12
|
+
KATO.compare_by_identity if KATO.respond_to?(:compare_by_identity)
|
13
|
+
KEEPALIVE_TIMEOUT = Rainbows.keepalive_timeout
|
14
|
+
EP = Rainbows::Epoll::EP
|
15
|
+
@@last_expire = Time.now
|
16
|
+
|
17
|
+
def self.expire
|
18
|
+
return if ((now = Time.now) - @@last_expire) < 1.0
|
19
|
+
if (ot = KEEPALIVE_TIMEOUT) >= 0
|
20
|
+
ot = now - ot
|
21
|
+
KATO.delete_if { |client, time| time < ot and client.timeout! }
|
22
|
+
end
|
23
|
+
@@last_expire = now
|
24
|
+
end
|
25
|
+
|
26
|
+
# only call this once
|
27
|
+
def epoll_once
|
28
|
+
@wr_queue = [] # may contain String, ResponsePipe, and StreamFile objects
|
29
|
+
post_init
|
30
|
+
on_readable
|
31
|
+
rescue => e
|
32
|
+
handle_error(e)
|
33
|
+
end
|
34
|
+
|
35
|
+
def on_readable
|
36
|
+
case rv = kgio_tryread(16384, RBUF)
|
37
|
+
when String
|
38
|
+
on_read(rv)
|
39
|
+
return if @wr_queue[0] || closed?
|
40
|
+
when :wait_readable
|
41
|
+
KATO[self] = @@last_expire if :headers == @state
|
42
|
+
return EP.set(self, IN)
|
43
|
+
else
|
44
|
+
break
|
45
|
+
end until :close == @state
|
46
|
+
close unless closed?
|
47
|
+
rescue Errno::ECONNRESET
|
48
|
+
close
|
49
|
+
rescue IOError
|
50
|
+
end
|
51
|
+
|
52
|
+
def app_call input # called by on_read()
|
53
|
+
@env[RACK_INPUT] = input
|
54
|
+
@env[REMOTE_ADDR] = kgio_addr
|
55
|
+
status, headers, body = APP.call(@env.merge!(RACK_DEFAULTS))
|
56
|
+
ev_write_response(status, headers, body, @hp.next?)
|
57
|
+
end
|
58
|
+
|
59
|
+
def write_response_path(status, headers, body, alive)
|
60
|
+
io = body_to_io(body)
|
61
|
+
st = io.stat
|
62
|
+
|
63
|
+
if st.file?
|
64
|
+
defer_file(status, headers, body, alive, io, st)
|
65
|
+
elsif st.socket? || st.pipe?
|
66
|
+
chunk = stream_response_headers(status, headers, alive)
|
67
|
+
stream_response_body(body, io, chunk)
|
68
|
+
else
|
69
|
+
# char or block device... WTF?
|
70
|
+
write_response(status, headers, body, alive)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# used for streaming sockets and pipes
|
75
|
+
def stream_response_body(body, io, chunk)
|
76
|
+
pipe = (chunk ? Rainbows::Epoll::ResponseChunkPipe :
|
77
|
+
Rainbows::Epoll::ResponsePipe).new(io, self, body)
|
78
|
+
return @wr_queue << pipe if @wr_queue[0]
|
79
|
+
stream_pipe(pipe) or return
|
80
|
+
@wr_queue[0] or @wr_queue << Z
|
81
|
+
end
|
82
|
+
|
83
|
+
def ev_write_response(status, headers, body, alive)
|
84
|
+
if body.respond_to?(:to_path)
|
85
|
+
write_response_path(status, headers, body, alive)
|
86
|
+
else
|
87
|
+
write_response(status, headers, body, alive)
|
88
|
+
end
|
89
|
+
@state = alive ? :headers : :close
|
90
|
+
on_read(Z) if alive && 0 == @wr_queue.size && 0 != @buf.size
|
91
|
+
end
|
92
|
+
|
93
|
+
def epoll_run
|
94
|
+
if @wr_queue[0]
|
95
|
+
on_writable
|
96
|
+
else
|
97
|
+
KATO.delete self
|
98
|
+
on_readable
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def want_more
|
103
|
+
Rainbows::Epoll::ReRun << self
|
104
|
+
end
|
105
|
+
|
106
|
+
def on_deferred_write_complete
|
107
|
+
:close == @state and return close
|
108
|
+
0 == @buf.size ? on_readable : on_read(Z)
|
109
|
+
end
|
110
|
+
|
111
|
+
def handle_error(e)
|
112
|
+
msg = Rainbows::Error.response(e) and kgio_trywrite(msg) rescue nil
|
113
|
+
ensure
|
114
|
+
close
|
115
|
+
end
|
116
|
+
|
117
|
+
def write_deferred(obj)
|
118
|
+
Rainbows::StreamFile === obj ? stream_file(obj) : stream_pipe(obj)
|
119
|
+
end
|
120
|
+
|
121
|
+
# writes until our write buffer is empty or we block
|
122
|
+
# returns true if we're done writing everything
|
123
|
+
def on_writable
|
124
|
+
obj = @wr_queue.shift
|
125
|
+
|
126
|
+
case rv = String === obj ? kgio_trywrite(obj) : write_deferred(obj)
|
127
|
+
when nil
|
128
|
+
obj = @wr_queue.shift or return on_deferred_write_complete
|
129
|
+
when String
|
130
|
+
obj = rv # retry
|
131
|
+
when :wait_writable # Strings and StreamFiles only
|
132
|
+
@wr_queue.unshift(obj)
|
133
|
+
EP.set(self, OUT)
|
134
|
+
return
|
135
|
+
when :deferred
|
136
|
+
return
|
137
|
+
end while true
|
138
|
+
rescue => e
|
139
|
+
handle_error(e)
|
140
|
+
end
|
141
|
+
|
142
|
+
def write(buf)
|
143
|
+
unless @wr_queue[0]
|
144
|
+
case rv = kgio_trywrite(buf)
|
145
|
+
when nil
|
146
|
+
return # all written
|
147
|
+
when String
|
148
|
+
buf = rv # retry
|
149
|
+
when :wait_writable
|
150
|
+
@wr_queue << buf.dup # >3-word 1.9 strings are copy-on-write
|
151
|
+
return EP.set(self, OUT)
|
152
|
+
end while true
|
153
|
+
end
|
154
|
+
@wr_queue << buf.dup # >3-word 1.9 strings are copy-on-write
|
155
|
+
end
|
156
|
+
|
157
|
+
def close
|
158
|
+
@wr_queue.each { |x| x.respond_to?(:close) and x.close rescue nil }
|
159
|
+
super
|
160
|
+
on_close
|
161
|
+
end
|
162
|
+
|
163
|
+
def on_close
|
164
|
+
KATO.delete(self)
|
165
|
+
Server.decr
|
166
|
+
end
|
167
|
+
|
168
|
+
def timeout!
|
169
|
+
close
|
170
|
+
true
|
171
|
+
end
|
172
|
+
|
173
|
+
def defer_file(status, headers, body, alive, io, st)
|
174
|
+
if r = sendfile_range(status, headers)
|
175
|
+
status, headers, range = r
|
176
|
+
write_headers(status, headers, alive)
|
177
|
+
range and defer_file_stream(range[0], range[1], io, body)
|
178
|
+
else
|
179
|
+
write_headers(status, headers, alive)
|
180
|
+
defer_file_stream(0, st.size, io, body)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# returns +nil+ on EOF, :wait_writable if the client blocks
|
185
|
+
def stream_file(sf) # +sf+ is a Rainbows::StreamFile object
|
186
|
+
begin
|
187
|
+
sf.offset += (n = sendfile_nonblock(sf, sf.offset, sf.count))
|
188
|
+
0 == (sf.count -= n) and return sf.close
|
189
|
+
rescue Errno::EAGAIN
|
190
|
+
return :wait_writable
|
191
|
+
rescue
|
192
|
+
sf.close
|
193
|
+
raise
|
194
|
+
end while true
|
195
|
+
end
|
196
|
+
|
197
|
+
def defer_file_stream(offset, count, io, body)
|
198
|
+
sf = Rainbows::StreamFile.new(offset, count, io, body)
|
199
|
+
unless @wr_queue[0]
|
200
|
+
stream_file(sf) or return
|
201
|
+
end
|
202
|
+
@wr_queue << sf
|
203
|
+
EP.set(self, OUT)
|
204
|
+
end
|
205
|
+
|
206
|
+
# this alternates between a push and pull model from the pipe -> client
|
207
|
+
# to avoid having too much data in userspace on either end.
|
208
|
+
def stream_pipe(pipe)
|
209
|
+
case buf = pipe.tryread
|
210
|
+
when String
|
211
|
+
write(buf)
|
212
|
+
if @wr_queue[0]
|
213
|
+
# client is blocked on write, client will pull from pipe later
|
214
|
+
EP.delete pipe
|
215
|
+
@wr_queue << pipe
|
216
|
+
EP.set(self, OUT)
|
217
|
+
return :deferred
|
218
|
+
end
|
219
|
+
# continue looping...
|
220
|
+
when :wait_readable
|
221
|
+
# pipe blocked on read, let the pipe push to the client in the future
|
222
|
+
EP.delete self
|
223
|
+
EP.set(pipe, IN)
|
224
|
+
return :deferred
|
225
|
+
else # nil => EOF
|
226
|
+
return pipe.close # nil
|
227
|
+
end while true
|
228
|
+
rescue => e
|
229
|
+
pipe.close
|
230
|
+
raise
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
#
|
4
|
+
class Rainbows::Epoll::ResponseChunkPipe < Rainbows::Epoll::ResponsePipe
|
5
|
+
def tryread
|
6
|
+
@io or return
|
7
|
+
|
8
|
+
case rv = super
|
9
|
+
when String
|
10
|
+
"#{rv.size.to_s(16)}\r\n#{rv}\r\n"
|
11
|
+
when nil
|
12
|
+
close
|
13
|
+
"0\r\n\r\n"
|
14
|
+
else
|
15
|
+
rv
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
#
|
4
|
+
class Rainbows::Epoll::ResponsePipe
|
5
|
+
attr_reader :io
|
6
|
+
alias to_io io
|
7
|
+
RBUF = Rainbows::EvCore::RBUF
|
8
|
+
EP = Rainbows::Epoll::EP
|
9
|
+
|
10
|
+
def initialize(io, client, body)
|
11
|
+
@io, @client, @body = io, client, body
|
12
|
+
end
|
13
|
+
|
14
|
+
def epoll_run
|
15
|
+
return close if @client.closed?
|
16
|
+
@client.stream_pipe(self) or @client.on_deferred_write_complete
|
17
|
+
rescue => e
|
18
|
+
close
|
19
|
+
@client.handle_error(e)
|
20
|
+
end
|
21
|
+
|
22
|
+
def close
|
23
|
+
@io or return
|
24
|
+
EP.delete self
|
25
|
+
@body.respond_to?(:close) and @body.close
|
26
|
+
@io = @body = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def tryread
|
30
|
+
Kgio.tryread(@io, 16384, RBUF)
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
module Rainbows::Epoll::Server
|
4
|
+
@@nr = 0
|
5
|
+
Rainbows::Epoll.nr_clients = lambda { @@nr }
|
6
|
+
IN = SleepyPenguin::Epoll::IN | SleepyPenguin::Epoll::ET
|
7
|
+
MAX = Rainbows.server.worker_connections
|
8
|
+
THRESH = MAX - 1
|
9
|
+
LISTENERS = Rainbows::HttpServer::LISTENERS
|
10
|
+
EP = Rainbows::Epoll::EP
|
11
|
+
|
12
|
+
def self.run
|
13
|
+
LISTENERS.each { |sock| EP.add(sock.extend(self), IN) }
|
14
|
+
Rainbows::Epoll.loop
|
15
|
+
end
|
16
|
+
|
17
|
+
# rearms all listeners when there's a free slot
|
18
|
+
def self.decr
|
19
|
+
THRESH == (@@nr -= 1) and LISTENERS.each { |sock| EP.set(sock, IN) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def epoll_run
|
23
|
+
return EP.delete(self) if @@nr >= MAX
|
24
|
+
while io = kgio_tryaccept
|
25
|
+
@@nr += 1
|
26
|
+
# there's a chance the client never even sees epoll for simple apps
|
27
|
+
io.epoll_once
|
28
|
+
return EP.delete(self) if @@nr >= MAX
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/rainbows/error.rb
CHANGED
@@ -7,13 +7,7 @@ module Rainbows::Error
|
|
7
7
|
# if the socket is already closed or broken. We'll always ensure
|
8
8
|
# the socket is closed at the end of this function
|
9
9
|
def self.write(io, e)
|
10
|
-
|
11
|
-
if io.respond_to?(:kgio_trywrite)
|
12
|
-
io.kgio_trywrite(msg)
|
13
|
-
else
|
14
|
-
io.write_nonblock(msg)
|
15
|
-
end
|
16
|
-
end
|
10
|
+
msg = response(e) and Kgio.trywrite(io, msg)
|
17
11
|
rescue
|
18
12
|
end
|
19
13
|
|
@@ -35,8 +29,6 @@ module Rainbows::Error
|
|
35
29
|
when EOFError, Errno::ECONNRESET, Errno::EPIPE, Errno::EINVAL,
|
36
30
|
Errno::EBADF, Errno::ENOTCONN
|
37
31
|
# swallow error if client shuts down one end or disconnects
|
38
|
-
when Rainbows::Response416
|
39
|
-
Rainbows::Const::ERROR_416_RESPONSE
|
40
32
|
when Unicorn::HttpParserError
|
41
33
|
Rainbows::Const::ERROR_400_RESPONSE # try to tell the client they're bad
|
42
34
|
when IOError # HttpParserError is an IOError
|