redis 3.2.2 → 3.3.1

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/lib/redis/client.rb CHANGED
@@ -12,7 +12,6 @@ class Redis
12
12
  :port => 6379,
13
13
  :path => nil,
14
14
  :timeout => 5.0,
15
- :connect_timeout => 5.0,
16
15
  :password => nil,
17
16
  :db => 0,
18
17
  :driver => nil,
@@ -42,8 +41,16 @@ class Redis
42
41
  @options[:path]
43
42
  end
44
43
 
44
+ def read_timeout
45
+ @options[:read_timeout]
46
+ end
47
+
48
+ def connect_timeout
49
+ @options[:connect_timeout]
50
+ end
51
+
45
52
  def timeout
46
- @options[:timeout]
53
+ @options[:read_timeout]
47
54
  end
48
55
 
49
56
  def password
@@ -109,21 +116,21 @@ class Redis
109
116
  path || "#{host}:#{port}"
110
117
  end
111
118
 
112
- def call(command, &block)
119
+ def call(command)
113
120
  reply = process([command]) { read }
114
121
  raise reply if reply.is_a?(CommandError)
115
122
 
116
- if block
117
- block.call(reply)
123
+ if block_given?
124
+ yield reply
118
125
  else
119
126
  reply
120
127
  end
121
128
  end
122
129
 
123
- def call_loop(command)
130
+ def call_loop(command, timeout = 0)
124
131
  error = nil
125
132
 
126
- result = without_socket_timeout do
133
+ result = with_socket_timeout(timeout) do
127
134
  process([command]) do
128
135
  loop do
129
136
  reply = read
@@ -175,15 +182,21 @@ class Redis
175
182
  reconnect = @reconnect
176
183
 
177
184
  begin
185
+ exception = nil
186
+
178
187
  process(commands) do
179
188
  result[0] = read
180
189
 
181
190
  @reconnect = false
182
191
 
183
192
  (commands.size - 1).times do |i|
184
- result[i + 1] = read
193
+ reply = read
194
+ result[i + 1] = reply
195
+ exception = reply if exception.nil? && reply.is_a?(CommandError)
185
196
  end
186
197
  end
198
+
199
+ raise exception if exception
187
200
  ensure
188
201
  @reconnect = reconnect
189
202
  end
@@ -392,7 +405,7 @@ class Redis
392
405
 
393
406
  if uri.scheme == "unix"
394
407
  defaults[:path] = uri.path
395
- elsif uri.scheme == "redis"
408
+ elsif uri.scheme == "redis" || uri.scheme == "rediss"
396
409
  defaults[:scheme] = uri.scheme
397
410
  defaults[:host] = uri.host if uri.host
398
411
  defaults[:port] = uri.port if uri.port
@@ -402,6 +415,8 @@ class Redis
402
415
  else
403
416
  raise ArgumentError, "invalid uri scheme '#{uri.scheme}'"
404
417
  end
418
+
419
+ defaults[:ssl] = true if uri.scheme == "rediss"
405
420
  end
406
421
 
407
422
  # Use default when option is not specified or nil
@@ -420,13 +435,16 @@ class Redis
420
435
  options[:port] = options[:port].to_i
421
436
  end
422
437
 
423
- options[:timeout] = options[:timeout].to_f
424
- options[:connect_timeout] = if options[:connect_timeout]
425
- options[:connect_timeout].to_f
426
- else
427
- options[:timeout]
438
+ if options.has_key?(:timeout)
439
+ options[:connect_timeout] ||= options[:timeout]
440
+ options[:read_timeout] ||= options[:timeout]
441
+ options[:write_timeout] ||= options[:timeout]
428
442
  end
429
443
 
444
+ options[:connect_timeout] = Float(options[:connect_timeout])
445
+ options[:read_timeout] = Float(options[:read_timeout])
446
+ options[:write_timeout] = Float(options[:write_timeout])
447
+
430
448
  options[:db] = options[:db].to_i
431
449
  options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last
432
450
 
@@ -13,12 +13,14 @@ class Redis
13
13
 
14
14
  if config[:scheme] == "unix"
15
15
  connection.connect_unix(config[:path], connect_timeout)
16
+ elsif config[:scheme] == "rediss" || config[:ssl]
17
+ raise NotImplementedError, "SSL not supported by hiredis driver"
16
18
  else
17
19
  connection.connect(config[:host], config[:port], connect_timeout)
18
20
  end
19
21
 
20
22
  instance = new(connection)
21
- instance.timeout = config[:timeout]
23
+ instance.timeout = config[:read_timeout]
22
24
  instance
23
25
  rescue Errno::ETIMEDOUT
24
26
  raise TimeoutError
@@ -2,6 +2,23 @@ require "redis/connection/registry"
2
2
  require "redis/connection/command_helper"
3
3
  require "redis/errors"
4
4
  require "socket"
5
+ require "timeout"
6
+
7
+ begin
8
+ require "openssl"
9
+ rescue LoadError
10
+ # Not all systems have OpenSSL support
11
+ end
12
+
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
5
22
 
6
23
  class Redis
7
24
  module Connection
@@ -9,10 +26,14 @@ class Redis
9
26
 
10
27
  CRLF = "\r\n".freeze
11
28
 
29
+ # Exceptions raised during non-blocking I/O ops that require retrying the op
30
+ NBIO_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN]
31
+ NBIO_EXCEPTIONS << IO::WaitReadable if RUBY_VERSION >= "1.9.3"
32
+
12
33
  def initialize(*args)
13
34
  super(*args)
14
35
 
15
- @timeout = nil
36
+ @timeout = @write_timeout = nil
16
37
  @buffer = ""
17
38
  end
18
39
 
@@ -24,6 +45,14 @@ class Redis
24
45
  end
25
46
  end
26
47
 
48
+ def write_timeout=(timeout)
49
+ if timeout && timeout > 0
50
+ @write_timeout = timeout
51
+ else
52
+ @write_timeout = nil
53
+ end
54
+ end
55
+
27
56
  def read(nbytes)
28
57
  result = @buffer.slice!(0, nbytes)
29
58
 
@@ -45,10 +74,11 @@ class Redis
45
74
  end
46
75
 
47
76
  def _read_from_socket(nbytes)
77
+
48
78
  begin
49
79
  read_nonblock(nbytes)
50
80
 
51
- rescue Errno::EWOULDBLOCK, Errno::EAGAIN
81
+ rescue *NBIO_EXCEPTIONS
52
82
  if IO.select([self], nil, nil, @timeout)
53
83
  retry
54
84
  else
@@ -59,6 +89,36 @@ class Redis
59
89
  rescue EOFError
60
90
  raise Errno::ECONNRESET
61
91
  end
92
+
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
121
+ end
62
122
  end
63
123
 
64
124
  if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
@@ -195,6 +255,27 @@ class Redis
195
255
 
196
256
  end
197
257
 
258
+ if defined?(OpenSSL)
259
+ class SSLSocket < ::OpenSSL::SSL::SSLSocket
260
+ include SocketMixin
261
+
262
+ def self.connect(host, port, timeout, ssl_params)
263
+ # Note: this is using Redis::Connection::TCPSocket
264
+ tcp_sock = TCPSocket.connect(host, port, timeout)
265
+
266
+ ctx = OpenSSL::SSL::SSLContext.new
267
+ ctx.set_params(ssl_params) if ssl_params && !ssl_params.empty?
268
+
269
+ ssl_sock = new(tcp_sock, ctx)
270
+ ssl_sock.hostname = host
271
+ ssl_sock.connect
272
+ ssl_sock.post_connection_check(host)
273
+
274
+ ssl_sock
275
+ end
276
+ end
277
+ end
278
+
198
279
  class Ruby
199
280
  include Redis::Connection::CommandHelper
200
281
 
@@ -206,13 +287,17 @@ class Redis
206
287
 
207
288
  def self.connect(config)
208
289
  if config[:scheme] == "unix"
290
+ raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
209
291
  sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
292
+ elsif config[:scheme] == "rediss" || config[:ssl]
293
+ sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
210
294
  else
211
295
  sock = TCPSocket.connect(config[:host], config[:port], config[:connect_timeout])
212
296
  end
213
297
 
214
298
  instance = new(sock)
215
299
  instance.timeout = config[:timeout]
300
+ instance.write_timeout = config[:write_timeout]
216
301
  instance.set_tcp_keepalive config[:tcp_keepalive]
217
302
  instance
218
303
  end
@@ -265,6 +350,10 @@ class Redis
265
350
  end
266
351
  end
267
352
 
353
+ def write_timeout=(timeout)
354
+ @sock.write_timeout = timeout
355
+ end
356
+
268
357
  def write(command)
269
358
  @sock.write(build_command(command))
270
359
  end
@@ -9,6 +9,8 @@ class Redis
9
9
  class RedisClient < EventMachine::Connection
10
10
  include EventMachine::Deferrable
11
11
 
12
+ attr_accessor :timeout
13
+
12
14
  def post_init
13
15
  @req = nil
14
16
  @connected = false
@@ -44,6 +46,9 @@ class Redis
44
46
 
45
47
  def read
46
48
  @req = EventMachine::DefaultDeferrable.new
49
+ if @timeout > 0
50
+ @req.timeout(@timeout, :timeout)
51
+ end
47
52
  EventMachine::Synchrony.sync @req
48
53
  end
49
54
 
@@ -68,6 +73,8 @@ class Redis
68
73
  def self.connect(config)
69
74
  if config[:scheme] == "unix"
70
75
  conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
76
+ elsif config[:scheme] == "rediss" || config[:ssl]
77
+ raise NotImplementedError, "SSL not supported by synchrony driver"
71
78
  else
72
79
  conn = EventMachine.connect(config[:host], config[:port], RedisClient) do |c|
73
80
  c.pending_connect_timeout = [config[:connect_timeout], 0.1].max
@@ -81,7 +88,7 @@ class Redis
81
88
  raise Errno::ECONNREFUSED if Fiber.yield == :refused
82
89
 
83
90
  instance = new(conn)
84
- instance.timeout = config[:timeout]
91
+ instance.timeout = config[:read_timeout]
85
92
  instance
86
93
  end
87
94
 
@@ -94,7 +101,7 @@ class Redis
94
101
  end
95
102
 
96
103
  def timeout=(timeout)
97
- @timeout = timeout
104
+ @connection.timeout = timeout
98
105
  end
99
106
 
100
107
  def disconnect
@@ -113,6 +120,8 @@ class Redis
113
120
  payload
114
121
  elsif type == :error
115
122
  raise payload
123
+ elsif type == :timeout
124
+ raise TimeoutError
116
125
  else
117
126
  raise "Unknown type #{type.inspect}"
118
127
  end
@@ -12,10 +12,18 @@ class Redis
12
12
  subscription("subscribe", "unsubscribe", channels, block)
13
13
  end
14
14
 
15
+ def subscribe_with_timeout(timeout, *channels, &block)
16
+ subscription("subscribe", "unsubscribe", channels, block, timeout)
17
+ end
18
+
15
19
  def psubscribe(*channels, &block)
16
20
  subscription("psubscribe", "punsubscribe", channels, block)
17
21
  end
18
22
 
23
+ def psubscribe_with_timeout(timeout, *channels, &block)
24
+ subscription("psubscribe", "punsubscribe", channels, block, timeout)
25
+ end
26
+
19
27
  def unsubscribe(*channels)
20
28
  call([:unsubscribe, *channels])
21
29
  end
@@ -26,13 +34,13 @@ class Redis
26
34
 
27
35
  protected
28
36
 
29
- def subscription(start, stop, channels, block)
37
+ def subscription(start, stop, channels, block, timeout = 0)
30
38
  sub = Subscription.new(&block)
31
39
 
32
40
  unsubscribed = false
33
41
 
34
42
  begin
35
- @client.call_loop([start, *channels]) do |line|
43
+ @client.call_loop([start, *channels], timeout) do |line|
36
44
  type, *rest = line
37
45
  sub.callbacks[type].call(*rest)
38
46
  unsubscribed = type == stop && rest.last == 0
data/lib/redis/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "3.2.2"
2
+ VERSION = "3.3.1"
3
3
  end
data/redis.gemspec CHANGED
@@ -39,6 +39,6 @@ Gem::Specification.new do |s|
39
39
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
40
40
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
41
41
 
42
- s.add_development_dependency("rake")
43
- s.add_development_dependency("test-unit")
42
+ s.add_development_dependency("rake", "<11.0.0")
43
+ s.add_development_dependency("test-unit", "3.1.5")
44
44
  end
@@ -0,0 +1,59 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ class TestClient < Test::Unit::TestCase
4
+
5
+ include Helper::Client
6
+
7
+ def test_call
8
+ result = r.call("PING")
9
+ assert_equal result, "PONG"
10
+ end
11
+
12
+ def test_call_with_arguments
13
+ result = r.call("SET", "foo", "bar")
14
+ assert_equal result, "OK"
15
+ end
16
+
17
+ def test_call_integers
18
+ result = r.call("INCR", "foo")
19
+ assert_equal result, 1
20
+ end
21
+
22
+ def test_call_raise
23
+ assert_raises(Redis::CommandError) do
24
+ r.call("INCR")
25
+ end
26
+ end
27
+
28
+ def test_queue_commit
29
+ r.queue("SET", "foo", "bar")
30
+ r.queue("GET", "foo")
31
+ result = r.commit
32
+
33
+ assert_equal result, ["OK", "bar"]
34
+ end
35
+
36
+ def test_commit_raise
37
+ r.queue("SET", "foo", "bar")
38
+ r.queue("INCR")
39
+
40
+ assert_raise(Redis::CommandError) do
41
+ r.commit
42
+ end
43
+ end
44
+
45
+ def test_queue_after_error
46
+ r.queue("SET", "foo", "bar")
47
+ r.queue("INCR")
48
+
49
+ assert_raise(Redis::CommandError) do
50
+ r.commit
51
+ end
52
+
53
+ r.queue("SET", "foo", "bar")
54
+ r.queue("INCR", "baz")
55
+ result = r.commit
56
+
57
+ assert_equal result, ["OK", 1]
58
+ end
59
+ end