redis 3.0.1 → 3.0.2

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.
@@ -1,9 +1,12 @@
1
1
  require "redis/errors"
2
+ require "socket"
3
+ require "cgi"
2
4
 
3
5
  class Redis
4
6
  class Client
5
7
 
6
8
  DEFAULTS = {
9
+ :url => lambda { ENV["REDIS_URL"] },
7
10
  :scheme => "redis",
8
11
  :host => "127.0.0.1",
9
12
  :port => 6379,
@@ -11,6 +14,9 @@ class Redis
11
14
  :timeout => 5.0,
12
15
  :password => nil,
13
16
  :db => 0,
17
+ :driver => nil,
18
+ :id => nil,
19
+ :tcp_keepalive => 0
14
20
  }
15
21
 
16
22
  def scheme
@@ -45,9 +51,9 @@ class Redis
45
51
  @options[:db] = db.to_i
46
52
  end
47
53
 
48
- attr :logger
49
- attr :connection
50
- attr :command_map
54
+ attr_accessor :logger
55
+ attr_reader :connection
56
+ attr_reader :command_map
51
57
 
52
58
  def initialize(options = {})
53
59
  @options = _parse_options(options)
@@ -295,8 +301,19 @@ class Redis
295
301
 
296
302
  def _parse_options(options)
297
303
  defaults = DEFAULTS.dup
304
+ options = options.dup
298
305
 
299
- url = options[:url] || ENV["REDIS_URL"]
306
+ defaults.keys.each do |key|
307
+ # Fill in defaults if needed
308
+ if defaults[key].respond_to?(:call)
309
+ defaults[key] = defaults[key].call
310
+ end
311
+
312
+ # Symbolize only keys that are needed
313
+ options[key] = options[key.to_s] if options.has_key?(key.to_s)
314
+ end
315
+
316
+ url = options[:url] || defaults[:url]
300
317
 
301
318
  # Override defaults from URL if given
302
319
  if url
@@ -313,12 +330,15 @@ class Redis
313
330
  defaults[:scheme] = uri.scheme
314
331
  defaults[:host] = uri.host
315
332
  defaults[:port] = uri.port if uri.port
316
- defaults[:password] = uri.password if uri.password
333
+ defaults[:password] = CGI.unescape(uri.password) if uri.password
317
334
  defaults[:db] = uri.path[1..-1].to_i if uri.path
318
335
  end
319
336
  end
320
337
 
321
- options = defaults.merge(options)
338
+ # Use default when option is not specified or nil
339
+ defaults.keys.each do |key|
340
+ options[key] ||= defaults[key]
341
+ end
322
342
 
323
343
  if options[:path]
324
344
  options[:scheme] = "unix"
@@ -333,6 +353,26 @@ class Redis
333
353
  options[:db] = options[:db].to_i
334
354
  options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last
335
355
 
356
+ case options[:tcp_keepalive]
357
+ when Hash
358
+ [:time, :intvl, :probes].each do |key|
359
+ unless options[:tcp_keepalive][key].is_a?(Fixnum)
360
+ raise "Expected the #{key.inspect} key in :tcp_keepalive to be a Fixnum"
361
+ end
362
+ end
363
+
364
+ when Fixnum
365
+ if options[:tcp_keepalive] >= 60
366
+ options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 20, :intvl => 10, :probes => 2}
367
+
368
+ elsif options[:tcp_keepalive] >= 30
369
+ options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 10, :intvl => 5, :probes => 2}
370
+
371
+ elsif options[:tcp_keepalive] >= 5
372
+ options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 2, :intvl => 2, :probes => 1}
373
+ end
374
+ end
375
+
336
376
  options
337
377
  end
338
378
 
@@ -79,22 +79,26 @@ class Redis
79
79
  end
80
80
  end
81
81
 
82
- class UNIXSocket < ::UNIXSocket
82
+ if defined?(::UNIXSocket)
83
83
 
84
- # This class doesn't include the mixin, because JRuby raises
85
- # Errno::EAGAIN on #read_nonblock even when IO.select says it is
86
- # readable. This behavior shows in 1.6.6 in both 1.8 and 1.9 mode.
87
- # Therefore, fall back on the default Unix socket implementation,
88
- # without timeouts.
84
+ class UNIXSocket < ::UNIXSocket
89
85
 
90
- def self.connect(path, timeout)
91
- Timeout.timeout(timeout) do
92
- sock = new(path)
93
- sock
86
+ # This class doesn't include the mixin, because JRuby raises
87
+ # Errno::EAGAIN on #read_nonblock even when IO.select says it is
88
+ # readable. This behavior shows in 1.6.6 in both 1.8 and 1.9 mode.
89
+ # Therefore, fall back on the default Unix socket implementation,
90
+ # without timeouts.
91
+
92
+ def self.connect(path, timeout)
93
+ Timeout.timeout(timeout) do
94
+ sock = new(path)
95
+ sock
96
+ end
97
+ rescue Timeout::Error
98
+ raise TimeoutError
94
99
  end
95
- rescue Timeout::Error
96
- raise TimeoutError
97
100
  end
101
+
98
102
  end
99
103
 
100
104
  else
@@ -172,9 +176,37 @@ class Redis
172
176
 
173
177
  instance = new(sock)
174
178
  instance.timeout = config[:timeout]
179
+ instance.set_tcp_keepalive config[:tcp_keepalive]
175
180
  instance
176
181
  end
177
182
 
183
+ if [:SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all?{|c| Socket.const_defined? c}
184
+ def set_tcp_keepalive(keepalive)
185
+ return unless keepalive.is_a?(Hash)
186
+
187
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
188
+ @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, keepalive[:time])
189
+ @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, keepalive[:intvl])
190
+ @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, keepalive[:probes])
191
+ end
192
+
193
+ def get_tcp_keepalive
194
+ {
195
+ :time => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
196
+ :intvl => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
197
+ :probes => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int,
198
+ }
199
+ end
200
+ else
201
+ def set_tcp_keepalive(keepalive)
202
+ end
203
+
204
+ def get_tcp_keepalive
205
+ {
206
+ }
207
+ end
208
+ end
209
+
178
210
  def initialize(sock)
179
211
  @sock = sock
180
212
  end
@@ -27,13 +27,18 @@ class Redis
27
27
  def receive_data(data)
28
28
  @reader.feed(data)
29
29
 
30
- begin
31
- until (reply = @reader.gets) == false
32
- reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
33
- @req.succeed [:reply, reply]
30
+ loop do
31
+ begin
32
+ reply = @reader.gets
33
+ rescue RuntimeError => err
34
+ @req.fail [:error, ProtocolError.new(err.message)]
35
+ break
34
36
  end
35
- rescue RuntimeError => err
36
- @req.fail [:error, ProtocolError.new(err.message)]
37
+
38
+ break if reply == false
39
+
40
+ reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
41
+ @req.succeed [:reply, reply]
37
42
  end
38
43
  end
39
44
 
@@ -15,10 +15,11 @@ class Redis
15
15
 
16
16
  attr_reader :ring
17
17
 
18
- def initialize(urls, options = {})
18
+ def initialize(node_configs, options = {})
19
19
  @tag = options.delete(:tag) || /^\{(.+?)\}/
20
20
  @default_options = options
21
- @ring = HashRing.new urls.map { |url| Redis.new(options.merge(:url => url)) }
21
+ @ring = HashRing.new
22
+ node_configs.each { |node_config| add_node(node_config) }
22
23
  @subscribed_node = nil
23
24
  end
24
25
 
@@ -30,8 +31,10 @@ class Redis
30
31
  @ring.nodes
31
32
  end
32
33
 
33
- def add_node(url)
34
- @ring.add_node Redis.new(@default_options.merge(:url => url))
34
+ def add_node(options)
35
+ options = { :url => options } if options.is_a?(String)
36
+ options = @default_options.merge(options)
37
+ @ring.add_node Redis.new( options )
35
38
  end
36
39
 
37
40
  # Change the selected database for the current connection.
@@ -68,14 +68,19 @@ class Redis
68
68
 
69
69
  class Multi < self
70
70
  def finish(replies)
71
- return if replies.last.nil? # The transaction failed because of WATCH.
71
+ exec = replies.last
72
72
 
73
- if replies.last.size < futures.size - 2
73
+ return if exec.nil? # The transaction failed because of WATCH.
74
+
75
+ # EXEC command failed.
76
+ raise exec if exec.is_a?(CommandError)
77
+
78
+ if exec.size < futures.size - 2
74
79
  # Some command wasn't recognized by Redis.
75
- raise replies.detect { |r| r.kind_of?(::RuntimeError) }
80
+ raise replies.detect { |r| r.is_a?(CommandError) }
76
81
  end
77
82
 
78
- super(replies.last) do |reply|
83
+ super(exec) do |reply|
79
84
  # Because an EXEC returns nested replies, hiredis won't be able to
80
85
  # convert an error reply to a CommandError instance itself. This is
81
86
  # specific to MULTI/EXEC, so we solve this here.
@@ -17,11 +17,11 @@ class Redis
17
17
  end
18
18
 
19
19
  def unsubscribe(*channels)
20
- call [:unsubscribe, *channels]
20
+ call([:unsubscribe, *channels])
21
21
  end
22
22
 
23
23
  def punsubscribe(*channels)
24
- call [:punsubscribe, *channels]
24
+ call([:punsubscribe, *channels])
25
25
  end
26
26
 
27
27
  protected
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "3.0.1"
2
+ VERSION = "3.0.2"
3
3
  end
@@ -12,4 +12,30 @@ class TestDistributedInternals < Test::Unit::TestCase
12
12
 
13
13
  assert_equal "#<Redis client v#{Redis::VERSION} for #{redis.nodes.map(&:id).join(', ')}>", redis.inspect
14
14
  end
15
+
16
+ def test_default_as_urls
17
+ nodes = ["redis://localhost:#{PORT}/15", *NODES]
18
+ redis = Redis::Distributed.new nodes
19
+ assert_equal ["redis://localhost:#{PORT}/15", *NODES], redis.nodes.map { |node| node.client.id}
20
+ end
21
+
22
+ def test_default_as_config_hashes
23
+ nodes = [OPTIONS.merge(:host => 'localhost'), OPTIONS.merge(:host => 'localhost', :port => PORT.next)]
24
+ redis = Redis::Distributed.new nodes
25
+ assert_equal ["redis://localhost:#{PORT}/15","redis://localhost:#{PORT.next}/15"], redis.nodes.map { |node| node.client.id }
26
+ end
27
+
28
+ def test_as_mix_and_match
29
+ nodes = ["redis://localhost:7389/15", OPTIONS.merge(:host => 'localhost'), OPTIONS.merge(:host => 'localhost', :port => PORT.next)]
30
+ redis = Redis::Distributed.new nodes
31
+ assert_equal ["redis://localhost:7389/15", "redis://localhost:#{PORT}/15", "redis://localhost:#{PORT.next}/15"], redis.nodes.map { |node| node.client.id }
32
+ end
33
+
34
+ def test_override_id
35
+ nodes = [OPTIONS.merge(:host => 'localhost', :id => "test"), OPTIONS.merge( :host => 'localhost', :port => PORT.next, :id => "test1")]
36
+ redis = Redis::Distributed.new nodes
37
+ assert_equal redis.nodes.first.client.id, "test"
38
+ assert_equal redis.nodes.last.client.id, "test1"
39
+ assert_equal "#<Redis client v#{Redis::VERSION} for #{redis.nodes.map(&:id).join(', ')}>", redis.inspect
40
+ end
15
41
  end
@@ -28,7 +28,7 @@ class TestDistributedRemoteServerControlCommands < Test::Unit::TestCase
28
28
  end
29
29
 
30
30
  def test_info_commandstats
31
- return if version < "2.9.0"
31
+ return if version < "2.5.7"
32
32
 
33
33
  r.nodes.each { |n| n.config(:resetstat) }
34
34
  r.ping # Executed on every node
@@ -98,6 +98,24 @@ class TestInternals < Test::Unit::TestCase
98
98
  end
99
99
  end
100
100
 
101
+ driver(:ruby) do
102
+ def test_tcp_keepalive
103
+ keepalive = {:time => 20, :intvl => 10, :probes => 5}
104
+
105
+ redis = Redis.new(OPTIONS.merge(:tcp_keepalive => keepalive))
106
+ redis.ping
107
+
108
+ connection = redis.client.connection
109
+ actual_keepalive = connection.get_tcp_keepalive
110
+
111
+ [:time, :intvl, :probes].each do |key|
112
+ if actual_keepalive.has_key?(key)
113
+ assert_equal actual_keepalive[key], keepalive[key]
114
+ end
115
+ end
116
+ end
117
+ end
118
+
101
119
  def test_time
102
120
  return if version < "2.5.4"
103
121
 
@@ -2,6 +2,8 @@ module Lint
2
2
 
3
3
  module SortedSets
4
4
 
5
+ Infinity = 1.0/0.0
6
+
5
7
  def test_zadd
6
8
  assert_equal 0, r.zcard("foo")
7
9
  assert_equal true, r.zadd("foo", 1, "s1")
@@ -61,6 +63,12 @@ module Lint
61
63
 
62
64
  rv = r.zincrby "foo", 10, "s1"
63
65
  assert_equal 11.0, rv
66
+
67
+ rv = r.zincrby "bar", "-inf", "s1"
68
+ assert_equal(-Infinity, rv)
69
+
70
+ rv = r.zincrby "bar", "+inf", "s2"
71
+ assert_equal(+Infinity, rv)
64
72
  end
65
73
 
66
74
  def test_zrank
@@ -87,6 +95,11 @@ module Lint
87
95
  assert_equal ["s1", "s2"], r.zrange("foo", 0, 1)
88
96
  assert_equal [["s1", 1.0], ["s2", 2.0]], r.zrange("foo", 0, 1, :with_scores => true)
89
97
  assert_equal [["s1", 1.0], ["s2", 2.0]], r.zrange("foo", 0, 1, :withscores => true)
98
+
99
+ r.zadd "bar", "-inf", "s1"
100
+ r.zadd "bar", "+inf", "s2"
101
+ assert_equal [["s1", -Infinity], ["s2", +Infinity]], r.zrange("bar", 0, 1, :with_scores => true)
102
+ assert_equal [["s1", -Infinity], ["s2", +Infinity]], r.zrange("bar", 0, 1, :withscores => true)
90
103
  end
91
104
 
92
105
  def test_zrevrange
@@ -97,6 +110,11 @@ module Lint
97
110
  assert_equal ["s3", "s2"], r.zrevrange("foo", 0, 1)
98
111
  assert_equal [["s3", 3.0], ["s2", 2.0]], r.zrevrange("foo", 0, 1, :with_scores => true)
99
112
  assert_equal [["s3", 3.0], ["s2", 2.0]], r.zrevrange("foo", 0, 1, :withscores => true)
113
+
114
+ r.zadd "bar", "-inf", "s1"
115
+ r.zadd "bar", "+inf", "s2"
116
+ assert_equal [["s2", +Infinity], ["s1", -Infinity]], r.zrevrange("bar", 0, 1, :with_scores => true)
117
+ assert_equal [["s2", +Infinity], ["s1", -Infinity]], r.zrevrange("bar", 0, 1, :withscores => true)
100
118
  end
101
119
 
102
120
  def test_zrangebyscore
@@ -147,6 +165,13 @@ module Lint
147
165
  assert_equal [["s3", 3.0]], r.zrangebyscore("foo", 2, 4, :limit => [1, 1], :with_scores => true)
148
166
  assert_equal [["s2", 2.0]], r.zrangebyscore("foo", 2, 4, :limit => [0, 1], :withscores => true)
149
167
  assert_equal [["s3", 3.0]], r.zrangebyscore("foo", 2, 4, :limit => [1, 1], :withscores => true)
168
+
169
+ r.zadd "bar", "-inf", "s1"
170
+ r.zadd "bar", "+inf", "s2"
171
+ assert_equal [["s1", -Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [0, 1], :with_scores => true)
172
+ assert_equal [["s2", +Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [1, 1], :with_scores => true)
173
+ assert_equal [["s1", -Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [0, 1], :withscores => true)
174
+ assert_equal [["s2", +Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [1, 1], :withscores => true)
150
175
  end
151
176
 
152
177
  def test_zrevrangebyscore_with_withscores
@@ -159,6 +184,13 @@ module Lint
159
184
  assert_equal [["s3", 3.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [1, 1], :with_scores => true)
160
185
  assert_equal [["s4", 4.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [0, 1], :withscores => true)
161
186
  assert_equal [["s3", 3.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [1, 1], :withscores => true)
187
+
188
+ r.zadd "bar", "-inf", "s1"
189
+ r.zadd "bar", "+inf", "s2"
190
+ assert_equal [["s2", +Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [0, 1], :with_scores => true)
191
+ assert_equal [["s1", -Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [1, 1], :with_scores => true)
192
+ assert_equal [["s2", +Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [0, 1], :withscores => true)
193
+ assert_equal [["s1", -Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [1, 1], :withscores => true)
162
194
  end
163
195
 
164
196
  def test_zcard
@@ -176,6 +208,11 @@ module Lint
176
208
 
177
209
  assert_equal nil, r.zscore("foo", "s2")
178
210
  assert_equal nil, r.zscore("bar", "s1")
211
+
212
+ r.zadd "bar", "-inf", "s1"
213
+ r.zadd "bar", "+inf", "s2"
214
+ assert_equal(-Infinity, r.zscore("bar", "s1"))
215
+ assert_equal(+Infinity, r.zscore("bar", "s2"))
179
216
  end
180
217
 
181
218
  def test_zremrangebyrank
@@ -32,30 +32,30 @@ module Lint
32
32
 
33
33
  def test_expire
34
34
  r.set("foo", "s1")
35
- assert r.expire("foo", 1)
36
- assert_in_range 0..1, r.ttl("foo")
35
+ assert r.expire("foo", 2)
36
+ assert_in_range 0..2, r.ttl("foo")
37
37
  end
38
38
 
39
39
  def test_pexpire
40
40
  return if version < "2.5.4"
41
41
 
42
42
  r.set("foo", "s1")
43
- assert r.pexpire("foo", 1000)
44
- assert_in_range 0..1, r.ttl("foo")
43
+ assert r.pexpire("foo", 2000)
44
+ assert_in_range 0..2, r.ttl("foo")
45
45
  end
46
46
 
47
47
  def test_expireat
48
48
  r.set("foo", "s1")
49
- assert r.expireat("foo", (Time.now + 1).to_i)
50
- assert_in_range 0..1, r.ttl("foo")
49
+ assert r.expireat("foo", (Time.now + 2).to_i)
50
+ assert_in_range 0..2, r.ttl("foo")
51
51
  end
52
52
 
53
53
  def test_pexpireat
54
54
  return if version < "2.5.4"
55
55
 
56
56
  r.set("foo", "s1")
57
- assert r.pexpireat("foo", (Time.now + 1).to_i * 1_000)
58
- assert_in_range 0..1, r.ttl("foo")
57
+ assert r.pexpireat("foo", (Time.now + 2).to_i * 1_000)
58
+ assert_in_range 0..2, r.ttl("foo")
59
59
  end
60
60
 
61
61
  def test_persist
@@ -68,16 +68,16 @@ module Lint
68
68
 
69
69
  def test_ttl
70
70
  r.set("foo", "s1")
71
- r.expire("foo", 1)
72
- assert_in_range 0..1, r.ttl("foo")
71
+ r.expire("foo", 2)
72
+ assert_in_range 0..2, r.ttl("foo")
73
73
  end
74
74
 
75
75
  def test_pttl
76
76
  return if version < "2.5.4"
77
77
 
78
78
  r.set("foo", "s1")
79
- r.expire("foo", 1)
80
- assert_in_range 1..1000, r.pttl("foo")
79
+ r.expire("foo", 2)
80
+ assert_in_range 1..2000, r.pttl("foo")
81
81
  end
82
82
 
83
83
  def test_move