Skip to content

Commit c1f6a58

Browse files
committed
Merge remote-tracking branch 'origin/master' into 3.3
2 parents 517d83c + 0e1ba19 commit c1f6a58

14 files changed

+255
-71
lines changed

.travis.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ language: ruby
33
rvm:
44
- 1.8.7
55
- 1.9.3
6+
- 2.0
67
- 2.1
8+
- 2.2
79
- jruby-18mode
810
- jruby-19mode
11+
- rbx-2
912

1013
gemfile: ".travis/Gemfile"
1114

@@ -45,6 +48,8 @@ matrix:
4548
- rvm: jruby-19mode
4649
gemfile: .travis/Gemfile
4750
env: conn=synchrony REDIS_BRANCH=2.8
51+
allow_failures:
52+
- rvm: rbx-2
4853

4954
notifications:
5055
irc:

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@
1616

1717
* Added support for `ZADD` options `NX`, `XX`, `CH`, `INCR`. See #547.
1818

19+
* Added support for sentinel commands. See #556.
20+
21+
* New `:id` option allows you to identify the client against Redis. See #510.
22+
23+
* `Redis::Distributed` will raise when adding two nodes with the same ID.
24+
See #354.
25+
1926
# 3.2.1
2027

2128
* Added support for `PUBSUB` command.

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ most important changes, as well as a full list of changes.
2323

2424
## Getting started
2525

26+
To install **redis-rb**, run the following command:
27+
28+
```
29+
gem install redis
30+
```
31+
32+
Or if you are using **bundler**, add
33+
34+
```
35+
gem 'redis', '~>3.2'
36+
```
37+
38+
to your `Gemfile`, and run `bundle install`
39+
2640
As of version 2.0 this client only targets Redis version 2.0 and higher.
2741
You can use an older version of this client if you need to interface
2842
with a Redis instance older than 2.0, but this is no longer supported.

lib/redis.rb

Lines changed: 78 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,26 @@ def self.current=(redis)
2626

2727
include MonitorMixin
2828

29+
# Create a new client instance
30+
#
31+
# @param [Hash] options
32+
# @option options [String] :url (value of the environment variable REDIS_URL) a Redis URL, for a TCP connection: `redis://:[password]@[hostname]:[port]/[db]` (password, port and database are optional), for a unix socket connection: `unix://[path to Redis socket]`. This overrides all other options.
33+
# @option options [String] :host ("127.0.0.1") server hostname
34+
# @option options [Fixnum] :port (6379) server port
35+
# @option options [String] :path path to server socket (overrides host and port)
36+
# @option options [Float] :timeout (5.0) timeout in seconds
37+
# @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds
38+
# @option options [String] :password Password to authenticate against server
39+
# @option options [Fixnum] :db (0) Database to select after initial connect
40+
# @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis`, `:synchrony`
41+
# @option options [String] :id ID for the client connection, assigns name to current connection by sending `CLIENT SETNAME`
42+
# @option options [Hash, Fixnum] :tcp_keepalive Keepalive values, if Fixnum `intvl` and `probe` are calculated based on the value, if Hash `time`, `intvl` and `probes` can be specified as a Fixnum
43+
# @option options [Fixnum] :reconnect_attempts Number of attempts trying to connect
44+
# @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
45+
# @option options [Array] :sentinels List of sentinels to contact
46+
# @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
47+
#
48+
# @return [Redis] a new client instance
2949
def initialize(options = {})
3050
@options = options.dup
3151
@original_client = @client = Client.new(options)
@@ -368,8 +388,15 @@ def expireat(key, unix_time)
368388
# Get the time to live (in seconds) for a key.
369389
#
370390
# @param [String] key
371-
# @return [Fixnum] remaining time to live in seconds, or -1 if the
372-
# key does not exist or does not have a timeout
391+
# @return [Fixnum] remaining time to live in seconds.
392+
#
393+
# In Redis 2.6 or older the command returns -1 if the key does not exist or if
394+
# the key exist but has no associated expire.
395+
#
396+
# Starting with Redis 2.8 the return value in case of error changed:
397+
#
398+
# - The command returns -2 if the key does not exist.
399+
# - The command returns -1 if the key exists but has no associated expire.
373400
def ttl(key)
374401
synchronize do |client|
375402
client.call([:ttl, key])
@@ -401,8 +428,14 @@ def pexpireat(key, ms_unix_time)
401428
# Get the time to live (in milliseconds) for a key.
402429
#
403430
# @param [String] key
404-
# @return [Fixnum] remaining time to live in milliseconds, or -1 if the
405-
# key does not exist or does not have a timeout
431+
# @return [Fixnum] remaining time to live in milliseconds
432+
# In Redis 2.6 or older the command returns -1 if the key does not exist or if
433+
# the key exist but has no associated expire.
434+
#
435+
# Starting with Redis 2.8 the return value in case of error changed:
436+
#
437+
# - The command returns -2 if the key does not exist.
438+
# - The command returns -1 if the key exists but has no associated expire.
406439
def pttl(key)
407440
synchronize do |client|
408441
client.call([:pttl, key])
@@ -424,7 +457,7 @@ def dump(key)
424457
# @param [String] key
425458
# @param [String] ttl
426459
# @param [String] serialized_value
427-
# @return `"OK"`
460+
# @return [String] `"OK"`
428461
def restore(key, ttl, serialized_value)
429462
synchronize do |client|
430463
client.call([:restore, key, ttl, serialized_value])
@@ -729,7 +762,7 @@ def set(key, value, options = {})
729762
# @param [String] key
730763
# @param [Fixnum] ttl
731764
# @param [String] value
732-
# @return `"OK"`
765+
# @return [String] `"OK"`
733766
def setex(key, ttl, value)
734767
synchronize do |client|
735768
client.call([:setex, key, ttl, value.to_s])
@@ -741,7 +774,7 @@ def setex(key, ttl, value)
741774
# @param [String] key
742775
# @param [Fixnum] ttl
743776
# @param [String] value
744-
# @return `"OK"`
777+
# @return [String] `"OK"`
745778
def psetex(key, ttl, value)
746779
synchronize do |client|
747780
client.call([:psetex, key, ttl, value.to_s])
@@ -766,7 +799,7 @@ def setnx(key, value)
766799
# # => "OK"
767800
#
768801
# @param [Array<String>] args array of keys and values
769-
# @return `"OK"`
802+
# @return [String] `"OK"`
770803
#
771804
# @see #mapped_mset
772805
def mset(*args)
@@ -782,7 +815,7 @@ def mset(*args)
782815
# # => "OK"
783816
#
784817
# @param [Hash] hash keys mapping to values
785-
# @return `"OK"`
818+
# @return [String] `"OK"`
786819
#
787820
# @see #mset
788821
def mapped_mset(hash)
@@ -1007,7 +1040,7 @@ def llen(key)
10071040
# Prepend one or more values to a list, creating the list if it doesn't exist
10081041
#
10091042
# @param [String] key
1010-
# @param [String, Array] string value, or array of string values to push
1043+
# @param [String, Array] value string value, or array of string values to push
10111044
# @return [Fixnum] the length of the list after the push operation
10121045
def lpush(key, value)
10131046
synchronize do |client|
@@ -1936,7 +1969,7 @@ def hsetnx(key, field, value)
19361969
#
19371970
# @param [String] key
19381971
# @param [Array<String>] attrs array of fields and values
1939-
# @return `"OK"`
1972+
# @return [String] `"OK"`
19401973
#
19411974
# @see #mapped_hmset
19421975
def hmset(key, *attrs)
@@ -1952,8 +1985,8 @@ def hmset(key, *attrs)
19521985
# # => "OK"
19531986
#
19541987
# @param [String] key
1955-
# @param [Hash] a non-empty hash with fields mapping to values
1956-
# @return `"OK"`
1988+
# @param [Hash] hash a non-empty hash with fields mapping to values
1989+
# @return [String] `"OK"`
19571990
#
19581991
# @see #hmset
19591992
def mapped_hmset(key, hash)
@@ -2276,7 +2309,7 @@ def exec
22762309
#
22772310
# Only call this method when `#multi` was called **without** a block.
22782311
#
2279-
# @return `"OK"`
2312+
# @return [String] `"OK"`
22802313
#
22812314
# @see #multi
22822315
# @see #exec
@@ -2424,7 +2457,7 @@ def _scan(command, cursor, args, options = {}, &block)
24242457
# redis.scan(4, :match => "key:1?")
24252458
# # => ["92", ["key:13", "key:18"]]
24262459
#
2427-
# @param [String, Integer] cursor: the cursor of the iteration
2460+
# @param [String, Integer] cursor the cursor of the iteration
24282461
# @param [Hash] options
24292462
# - `:match => String`: only return keys matching the pattern
24302463
# - `:count => Integer`: return count keys at most per iteration
@@ -2464,7 +2497,7 @@ def scan_each(options={}, &block)
24642497
# @example Retrieve the first batch of key/value pairs in a hash
24652498
# redis.hscan("hash", 0)
24662499
#
2467-
# @param [String, Integer] cursor: the cursor of the iteration
2500+
# @param [String, Integer] cursor the cursor of the iteration
24682501
# @param [Hash] options
24692502
# - `:match => String`: only return keys matching the pattern
24702503
# - `:count => Integer`: return count keys at most per iteration
@@ -2502,7 +2535,7 @@ def hscan_each(key, options={}, &block)
25022535
# @example Retrieve the first batch of key/value pairs in a hash
25032536
# redis.zscan("zset", 0)
25042537
#
2505-
# @param [String, Integer] cursor: the cursor of the iteration
2538+
# @param [String, Integer] cursor the cursor of the iteration
25062539
# @param [Hash] options
25072540
# - `:match => String`: only return keys matching the pattern
25082541
# - `:count => Integer`: return count keys at most per iteration
@@ -2541,7 +2574,7 @@ def zscan_each(key, options={}, &block)
25412574
# @example Retrieve the first batch of keys in a set
25422575
# redis.sscan("set", 0)
25432576
#
2544-
# @param [String, Integer] cursor: the cursor of the iteration
2577+
# @param [String, Integer] cursor the cursor of the iteration
25452578
# @param [Hash] options
25462579
# - `:match => String`: only return keys matching the pattern
25472580
# - `:count => Integer`: return count keys at most per iteration
@@ -2608,6 +2641,33 @@ def pfmerge(dest_key, *source_key)
26082641
end
26092642
end
26102643

2644+
# Interact with the sentinel command (masters, master, slaves, failover)
2645+
#
2646+
# @param [String] subcommand e.g. `masters`, `master`, `slaves`
2647+
# @param [Array<String>] args depends on subcommand
2648+
# @return [Array<String>, Hash<String, String>, String] depends on subcommand
2649+
def sentinel(subcommand, *args)
2650+
subcommand = subcommand.to_s.downcase
2651+
synchronize do |client|
2652+
client.call([:sentinel, subcommand] + args) do |reply|
2653+
case subcommand
2654+
when "get-master-addr-by-name"
2655+
reply
2656+
else
2657+
if reply.kind_of?(Array)
2658+
if reply[0].kind_of?(Array)
2659+
reply.map(&_hashify)
2660+
else
2661+
_hashify.call(reply)
2662+
end
2663+
else
2664+
reply
2665+
end
2666+
end
2667+
end
2668+
end
2669+
end
2670+
26112671
def id
26122672
@original_client.id
26132673
end

lib/redis/client.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def connect
9494
establish_connection
9595
call [:auth, password] if password
9696
call [:select, db] if db != 0
97+
call [:client, :setname, @options[:id]] if @options[:id]
9798
@connector.check(self)
9899
end
99100

lib/redis/hash_ring.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def add_node(node)
2525
@nodes << node
2626
@replicas.times do |i|
2727
key = Zlib.crc32("#{node.id}:#{i}")
28+
raise "Node ID collision" if @ring.has_key?(key)
2829
@ring[key] = node
2930
@sorted_keys << key
3031
end

redis.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ Gem::Specification.new do |s|
4040
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
4141

4242
s.add_development_dependency("rake")
43+
s.add_development_dependency("test-unit")
4344
end

test/connection_handling_test.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ def test_auth
1717
end
1818
end
1919

20+
def test_id
21+
commands = {
22+
:client => lambda { |cmd, name| $name = [cmd, name]; "+OK" },
23+
:ping => lambda { "+PONG" },
24+
}
25+
26+
redis_mock(commands, :id => "client-name") do |redis|
27+
assert_equal "PONG", redis.ping
28+
end
29+
30+
assert_equal ["setname","client-name"], $name
31+
end
32+
2033
def test_ping
2134
assert_equal "PONG", r.ping
2235
end

test/distributed_internals_test.rb

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ class TestDistributedInternals < Test::Unit::TestCase
77
include Helper::Distributed
88

99
def test_provides_a_meaningful_inspect
10-
nodes = ["redis://127.0.0.1:#{PORT}/15", *NODES]
10+
nodes = ["redis://localhost:#{PORT}/15", *NODES]
1111
redis = Redis::Distributed.new nodes
1212

1313
assert_equal "#<Redis client v#{Redis::VERSION} for #{redis.nodes.map(&:id).join(', ')}>", redis.inspect
1414
end
1515

1616
def test_default_as_urls
17-
nodes = ["redis://127.0.0.1:#{PORT}/15", *NODES]
17+
nodes = ["redis://localhost:#{PORT}/15", *NODES]
1818
redis = Redis::Distributed.new nodes
19-
assert_equal ["redis://127.0.0.1:#{PORT}/15", *NODES], redis.nodes.map { |node| node.client.id}
19+
assert_equal ["redis://localhost:#{PORT}/15", *NODES], redis.nodes.map { |node| node.client.id}
2020
end
2121

2222
def test_default_as_config_hashes
@@ -67,4 +67,13 @@ def test_keeps_options_after_dup
6767

6868
assert_equal [], r2.sinter("baz:foo", "baz:bar")
6969
end
70+
71+
def test_colliding_node_ids
72+
nodes = ["redis://localhost:#{PORT}/15", "redis://localhost:#{PORT}/15", *NODES]
73+
74+
assert_raise(RuntimeError) do
75+
redis = Redis::Distributed.new nodes
76+
end
77+
end
78+
7079
end

test/distributed_key_tags_test.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ class TestDistributedKeyTags < Test::Unit::TestCase
88
include Helper::Distributed
99

1010
def test_hashes_consistently
11-
r1 = Redis::Distributed.new ["redis://127.0.0.1:#{PORT}/15", *NODES]
12-
r2 = Redis::Distributed.new ["redis://127.0.0.1:#{PORT}/15", *NODES]
13-
r3 = Redis::Distributed.new ["redis://127.0.0.1:#{PORT}/15", *NODES]
11+
r1 = Redis::Distributed.new ["redis://localhost:#{PORT}/15", *NODES]
12+
r2 = Redis::Distributed.new ["redis://localhost:#{PORT}/15", *NODES]
13+
r3 = Redis::Distributed.new ["redis://localhost:#{PORT}/15", *NODES]
1414

1515
assert_equal r1.node_for("foo").id, r2.node_for("foo").id
1616
assert_equal r1.node_for("foo").id, r3.node_for("foo").id

0 commit comments

Comments
 (0)