Skip to content

Commit 3fd1b03

Browse files
authored
Merge pull request #615 from redis/write-nonblock
Write nonblock
2 parents fd77358 + 3aa4443 commit 3fd1b03

File tree

3 files changed

+86
-10
lines changed

3 files changed

+86
-10
lines changed

.travis.yml

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,48 @@ matrix:
3838
# hiredis
3939
- rvm: jruby-18mode
4040
gemfile: .travis/Gemfile
41-
env: conn=hiredis REDIS_BRANCH=2.8
41+
env: conn=hiredis REDIS_BRANCH=3.0
42+
- rvm: jruby-18mode
43+
gemfile: .travis/Gemfile
44+
env: conn=hiredis REDIS_BRANCH=3.2
45+
- rvm: jruby-19mode
46+
gemfile: .travis/Gemfile
47+
env: conn=hiredis REDIS_BRANCH=3.0
4248
- rvm: jruby-19mode
4349
gemfile: .travis/Gemfile
44-
env: conn=hiredis REDIS_BRANCH=2.8
50+
env: conn=hiredis REDIS_BRANCH=3.2
4551
- rvm: jruby-9.0.5.0
4652
gemfile: .travis/Gemfile
47-
env: conn=hiredis REDIS_BRANCH=2.8
53+
env: conn=hiredis REDIS_BRANCH=3.0
54+
- rvm: jruby-9.0.5.0
55+
gemfile: .travis/Gemfile
56+
env: conn=hiredis REDIS_BRANCH=3.2
4857

4958
# synchrony
5059
- rvm: 1.8.7
5160
gemfile: .travis/Gemfile
52-
env: conn=synchrony REDIS_BRANCH=2.8
61+
env: conn=synchrony REDIS_BRANCH=3.0
62+
- rvm: 1.8.7
63+
gemfile: .travis/Gemfile
64+
env: conn=synchrony REDIS_BRANCH=3.2
65+
- rvm: jruby-18mode
66+
gemfile: .travis/Gemfile
67+
env: conn=synchrony REDIS_BRANCH=3.0
5368
- rvm: jruby-18mode
5469
gemfile: .travis/Gemfile
55-
env: conn=synchrony REDIS_BRANCH=2.8
70+
env: conn=synchrony REDIS_BRANCH=3.2
5671
- rvm: jruby-19mode
5772
gemfile: .travis/Gemfile
58-
env: conn=synchrony REDIS_BRANCH=2.8
73+
env: conn=synchrony REDIS_BRANCH=3.0
74+
- rvm: jruby-19mode
75+
gemfile: .travis/Gemfile
76+
env: conn=synchrony REDIS_BRANCH=3.2
77+
- rvm: jruby-9.0.5.0
78+
gemfile: .travis/Gemfile
79+
env: conn=synchrony REDIS_BRANCH=3.0
5980
- rvm: jruby-9.0.5.0
6081
gemfile: .travis/Gemfile
61-
env: conn=synchrony REDIS_BRANCH=2.8
82+
env: conn=synchrony REDIS_BRANCH=3.2
6283
allow_failures:
6384
- rvm: rbx-2
6485

lib/redis/connection/ruby.rb

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010
# Not all systems have OpenSSL support
1111
end
1212

13+
if RUBY_VERSION < "1.9.3"
14+
class String
15+
# Ruby 1.8.7 does not have byteslice, but it handles encodings differently anyway.
16+
# We can simply slice the string, which is a byte array there.
17+
def byteslice(*args)
18+
slice(*args)
19+
end
20+
end
21+
end
22+
1323
class Redis
1424
module Connection
1525
module SocketMixin
@@ -80,9 +90,34 @@ def _read_from_socket(nbytes)
8090
raise Errno::ECONNRESET
8191
end
8292

83-
# UNIXSocket and TCPSocket don't support write timeouts
84-
def write(*args)
85-
Timeout.timeout(@write_timeout, TimeoutError) { super }
93+
def _write_to_socket(data)
94+
begin
95+
write_nonblock(data)
96+
97+
rescue *NBIO_EXCEPTIONS
98+
if IO.select(nil, [self], nil, @write_timeout)
99+
retry
100+
else
101+
raise Redis::TimeoutError
102+
end
103+
end
104+
105+
rescue EOFError
106+
raise Errno::ECONNRESET
107+
end
108+
109+
def write(data)
110+
return super(data) unless @write_timeout
111+
112+
length = data.bytesize
113+
total_count = 0
114+
loop do
115+
count = _write_to_socket(data)
116+
117+
total_count += count
118+
return total_count if total_count >= length
119+
data = data.byteslice(count..-1)
120+
end
86121
end
87122
end
88123

test/internals_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
class TestInternals < Test::Unit::TestCase
66

77
include Helper::Client
8+
include Helper::Skipable
89

910
def test_logger
1011
r.ping
@@ -160,6 +161,25 @@ def test_connection_timeout
160161
assert (Time.now - start_time) <= opts[:timeout]
161162
end
162163

164+
driver(:ruby) do
165+
def test_write_timeout
166+
return skip("Relies on buffer sizes, might be unreliable")
167+
168+
server = TCPServer.new("127.0.0.1", 0)
169+
port = server.addr[1]
170+
171+
# Hacky, but we need the buffer size
172+
val = TCPSocket.new("127.0.0.1", port).getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).unpack("i")[0]
173+
174+
assert_raise(Redis::TimeoutError) do
175+
Timeout.timeout(1) do
176+
redis = Redis.new(:port => port, :timeout => 5, :write_timeout => 0.1)
177+
redis.set("foo", "1" * val*2)
178+
end
179+
end
180+
end
181+
end
182+
163183
def close_on_ping(seq, options = {})
164184
$request = 0
165185

0 commit comments

Comments
 (0)