redis 3.2.0 → 3.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +22 -4
- data/README.md +25 -4
- data/Rakefile +20 -1
- data/lib/redis.rb +39 -4
- data/lib/redis/client.rb +47 -16
- data/lib/redis/connection/hiredis.rb +3 -2
- data/lib/redis/connection/ruby.rb +2 -2
- data/lib/redis/connection/synchrony.rb +1 -1
- data/lib/redis/version.rb +1 -1
- data/test/commands_on_sorted_sets_test.rb +14 -0
- data/test/commands_on_value_types_test.rb +4 -2
- data/test/connection_handling_test.rb +48 -0
- data/test/internals_test.rb +4 -1
- data/test/publish_subscribe_test.rb +44 -0
- data/test/remote_server_control_commands_test.rb +5 -4
- data/test/sentinel_test.rb +241 -0
- data/test/support/redis_mock.rb +22 -16
- data/test/test.conf.erb +9 -0
- metadata +6 -4
- data/test/test.conf +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1245d9a408be7cfa9f796b5b02e6f2dcaff10f2a
|
4
|
+
data.tar.gz: 2dce33db07a7a16dcade71b081996f241d2075ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef3734d43bbd4fb5f50fbef3d9b3348d382c9389433a8af91e9d94410b818b3eaa8d306b827e91aafaa26fa28aa461f9c028293280c6df48bf5d28412554140a
|
7
|
+
data.tar.gz: cc7122626f5587a8a750746acfb59d7462fb426cd68234ee141805e77b5bbfb39736543f71fc26575ef5bd1bd6dce3b7eb2b8857408461cf4f9ac2e4f2d05c78
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -12,13 +12,31 @@
|
|
12
12
|
security updates in June of 2013; continuing to support it would prevent
|
13
13
|
the use of newer features of Ruby.
|
14
14
|
|
15
|
-
# 3.2.
|
15
|
+
# 3.2.1
|
16
16
|
|
17
|
-
*
|
17
|
+
* Added support for `PUBSUB` command.
|
18
|
+
|
19
|
+
* More low-level socket errors are now raised as `CannotConnectError`.
|
20
|
+
|
21
|
+
* Added `:connect_timeout` option.
|
22
|
+
|
23
|
+
* Added support for `:limit` option for `ZREVRANGEBYLEX`.
|
24
|
+
|
25
|
+
* Fixed an issue where connections become inconsistent when using Ruby's
|
26
|
+
Timeout module outside of the client (see #501, #502).
|
18
27
|
|
19
|
-
#
|
28
|
+
* Added `Redis#disconnect!` as a public-API way of disconnecting the client
|
29
|
+
(without needing to use `QUIT`). See #506.
|
20
30
|
|
21
|
-
*
|
31
|
+
* Fixed Sentinel support with Hiredis.
|
32
|
+
|
33
|
+
* Fixed Sentinel support when using authentication and databases.
|
34
|
+
|
35
|
+
* Improved resilience when trying to contact sentinels.
|
36
|
+
|
37
|
+
# 3.2.0
|
38
|
+
|
39
|
+
* Redis Sentinel support.
|
22
40
|
|
23
41
|
# 3.1.0
|
24
42
|
|
data/README.md
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
[travis-image]: https://secure.travis-ci.org/redis/redis-rb.png?branch=master
|
4
4
|
[travis-link]: http://travis-ci.org/redis/redis-rb
|
5
5
|
[travis-home]: http://travis-ci.org/
|
6
|
-
[inchpages-image]: http://inch-
|
7
|
-
[inchpages-link]: http://inch-
|
6
|
+
[inchpages-image]: http://inch-ci.org/github/redis/redis-rb.png
|
7
|
+
[inchpages-link]: http://inch-ci.org/github/redis/redis-rb
|
8
8
|
|
9
9
|
A Ruby client library for [Redis][redis-home].
|
10
10
|
|
@@ -105,7 +105,8 @@ and one or more slaves (`mymaster` in the example).
|
|
105
105
|
|
106
106
|
* It is possible to optionally provide a role. The allowed roles are `master`
|
107
107
|
and `slave`. When the role is `slave`, the client will try to connect to a
|
108
|
-
random slave of the specified master.
|
108
|
+
random slave of the specified master. If a role is not specified, the client
|
109
|
+
will connect to the master.
|
109
110
|
|
110
111
|
* When using the Sentinel support you need to specify a list of sentinels to
|
111
112
|
connect to. The list does not need to enumerate all your Sentinel instances,
|
@@ -170,7 +171,7 @@ end
|
|
170
171
|
Replies to commands in a pipeline can be accessed via the *futures* they
|
171
172
|
emit (since redis-rb 3.0). All calls inside a pipeline block return a
|
172
173
|
`Future` object, which responds to the `#value` method. When the
|
173
|
-
pipeline has
|
174
|
+
pipeline has successfully executed, all futures are assigned their
|
174
175
|
respective replies and can be used.
|
175
176
|
|
176
177
|
```ruby
|
@@ -186,6 +187,26 @@ end
|
|
186
187
|
# => 1
|
187
188
|
```
|
188
189
|
|
190
|
+
## Error Handling
|
191
|
+
|
192
|
+
In general, if something goes wrong you'll get an exception. For example, if
|
193
|
+
it can't connect to the server a `Redis::CannotConnectError` error will be raised.
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
begin
|
197
|
+
redis.ping
|
198
|
+
rescue Exception => e
|
199
|
+
e.inspect
|
200
|
+
# => #<Redis::CannotConnectError: Timed out connecting to Redis on 10.0.1.1:6380>
|
201
|
+
|
202
|
+
e.message
|
203
|
+
# => Timed out connecting to Redis on 10.0.1.1:6380
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
207
|
+
See lib/redis/errors.rb for information about what exceptions are possible.
|
208
|
+
|
209
|
+
|
189
210
|
## Expert-Mode Options
|
190
211
|
|
191
212
|
- `inherit_socket: true`: disable safety check that prevents a forked child
|
data/Rakefile
CHANGED
@@ -4,7 +4,10 @@ ENV["REDIS_BRANCH"] ||= "unstable"
|
|
4
4
|
|
5
5
|
REDIS_DIR = File.expand_path(File.join("..", "test"), __FILE__)
|
6
6
|
REDIS_CNF = File.join(REDIS_DIR, "test.conf")
|
7
|
+
REDIS_CNF_TEMPLATE = File.join(REDIS_DIR, "test.conf.erb")
|
7
8
|
REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
|
9
|
+
REDIS_LOG = File.join(REDIS_DIR, "db", "redis.log")
|
10
|
+
REDIS_SOCKET = File.join(REDIS_DIR, "db", "redis.sock")
|
8
11
|
BINARY = "tmp/redis-#{ENV["REDIS_BRANCH"]}/src/redis-server"
|
9
12
|
|
10
13
|
task :default => :run
|
@@ -13,7 +16,7 @@ desc "Run tests and manage server start/stop"
|
|
13
16
|
task :run => [:start, :test, :stop]
|
14
17
|
|
15
18
|
desc "Start the Redis server"
|
16
|
-
task :start => BINARY do
|
19
|
+
task :start => [BINARY, REDIS_CNF] do
|
17
20
|
sh "#{BINARY} --version"
|
18
21
|
|
19
22
|
redis_running = \
|
@@ -46,6 +49,7 @@ end
|
|
46
49
|
desc "Clean up testing artifacts"
|
47
50
|
task :clean do
|
48
51
|
FileUtils.rm_f(BINARY)
|
52
|
+
FileUtils.rm_f(REDIS_CNF)
|
49
53
|
end
|
50
54
|
|
51
55
|
file BINARY do
|
@@ -62,6 +66,21 @@ file BINARY do
|
|
62
66
|
SH
|
63
67
|
end
|
64
68
|
|
69
|
+
file REDIS_CNF => [REDIS_CNF_TEMPLATE, __FILE__] do |t|
|
70
|
+
require 'erb'
|
71
|
+
|
72
|
+
erb = t.prerequisites[0]
|
73
|
+
template = File.read(erb)
|
74
|
+
|
75
|
+
File.open(REDIS_CNF, 'w') do |file|
|
76
|
+
file.puts "\# This file was auto-generated at #{Time.now}",
|
77
|
+
"\# from (#{erb})",
|
78
|
+
"\#"
|
79
|
+
conf = ERB.new(template).result
|
80
|
+
file << conf
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
65
84
|
Rake::TestTask.new do |t|
|
66
85
|
t.options = "-v" if $VERBOSE
|
67
86
|
t.test_files = FileList["test/*_test.rb"]
|
data/lib/redis.rb
CHANGED
@@ -54,6 +54,11 @@ class Redis
|
|
54
54
|
@original_client.connected?
|
55
55
|
end
|
56
56
|
|
57
|
+
# Disconnect the client as quickly and silently as possible.
|
58
|
+
def disconnect!
|
59
|
+
@original_client.disconnect
|
60
|
+
end
|
61
|
+
|
57
62
|
# Authenticate to the server.
|
58
63
|
#
|
59
64
|
# @param [String] password must match the password specified in the
|
@@ -764,7 +769,7 @@ class Redis
|
|
764
769
|
# Set one or more values, only if none of the keys exist.
|
765
770
|
#
|
766
771
|
# @example
|
767
|
-
# redis.
|
772
|
+
# redis.mapped_msetnx({ "key1" => "v1", "key2" => "v2" })
|
768
773
|
# # => true
|
769
774
|
#
|
770
775
|
# @param [Hash] hash keys mapping to values
|
@@ -1620,6 +1625,28 @@ class Redis
|
|
1620
1625
|
end
|
1621
1626
|
end
|
1622
1627
|
|
1628
|
+
# Return a range of members with the same score in a sorted set, by reversed lexicographical ordering.
|
1629
|
+
# Apart from the reversed ordering, #zrevrangebylex is similar to #zrangebylex.
|
1630
|
+
#
|
1631
|
+
# @example Retrieve members matching a
|
1632
|
+
# redis.zrevrangebylex("zset", "[a", "[a\xff")
|
1633
|
+
# # => ["abbygail", "abby", "abagael", "aaren"]
|
1634
|
+
# @example Retrieve the last 2 members matching a
|
1635
|
+
# redis.zrevrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
|
1636
|
+
# # => ["abbygail", "abby"]
|
1637
|
+
#
|
1638
|
+
# @see #zrangebylex
|
1639
|
+
def zrevrangebylex(key, max, min, options = {})
|
1640
|
+
args = []
|
1641
|
+
|
1642
|
+
limit = options[:limit]
|
1643
|
+
args.concat(["LIMIT"] + limit) if limit
|
1644
|
+
|
1645
|
+
synchronize do |client|
|
1646
|
+
client.call([:zrevrangebylex, key, max, min] + args)
|
1647
|
+
end
|
1648
|
+
end
|
1649
|
+
|
1623
1650
|
# Return a range of members in a sorted set, by score.
|
1624
1651
|
#
|
1625
1652
|
# @example Retrieve members with score `>= 5` and `< 100`
|
@@ -1856,7 +1883,7 @@ class Redis
|
|
1856
1883
|
# # => "OK"
|
1857
1884
|
#
|
1858
1885
|
# @param [String] key
|
1859
|
-
# @param [Hash] hash fields mapping to values
|
1886
|
+
# @param [Hash] a non-empty hash with fields mapping to values
|
1860
1887
|
# @return `"OK"`
|
1861
1888
|
#
|
1862
1889
|
# @see #hmset
|
@@ -1895,7 +1922,7 @@ class Redis
|
|
1895
1922
|
# Get the values of all the given hash fields.
|
1896
1923
|
#
|
1897
1924
|
# @example
|
1898
|
-
# redis.
|
1925
|
+
# redis.mapped_hmget("hash", "f1", "f2")
|
1899
1926
|
# # => { "f1" => "v1", "f2" => "v2" }
|
1900
1927
|
#
|
1901
1928
|
# @param [String] key
|
@@ -2032,6 +2059,14 @@ class Redis
|
|
2032
2059
|
end
|
2033
2060
|
end
|
2034
2061
|
|
2062
|
+
# Inspect the state of the Pub/Sub subsystem.
|
2063
|
+
# Possible subcommands: channels, numsub, numpat.
|
2064
|
+
def pubsub(subcommand, *args)
|
2065
|
+
synchronize do |client|
|
2066
|
+
client.call([:pubsub, subcommand] + args)
|
2067
|
+
end
|
2068
|
+
end
|
2069
|
+
|
2035
2070
|
# Watch the given keys to determine execution of the MULTI/EXEC block.
|
2036
2071
|
#
|
2037
2072
|
# Using a block is optional, but is necessary for thread-safety.
|
@@ -2450,7 +2485,7 @@ class Redis
|
|
2450
2485
|
# Scan a set
|
2451
2486
|
#
|
2452
2487
|
# @example Retrieve all of the keys in a set
|
2453
|
-
# redis.
|
2488
|
+
# redis.sscan_each("set").to_a
|
2454
2489
|
# # => ["key1", "key2", "key3"]
|
2455
2490
|
#
|
2456
2491
|
# @param [Hash] options
|
data/lib/redis/client.rb
CHANGED
@@ -12,6 +12,7 @@ class Redis
|
|
12
12
|
:port => 6379,
|
13
13
|
:path => nil,
|
14
14
|
:timeout => 5.0,
|
15
|
+
:connect_timeout => 5.0,
|
15
16
|
:password => nil,
|
16
17
|
:db => 0,
|
17
18
|
:driver => nil,
|
@@ -76,6 +77,8 @@ class Redis
|
|
76
77
|
@connection = nil
|
77
78
|
@command_map = {}
|
78
79
|
|
80
|
+
@pending_reads = 0
|
81
|
+
|
79
82
|
if options.include?(:sentinels)
|
80
83
|
@connector = Connector::Sentinel.new(@options)
|
81
84
|
else
|
@@ -242,12 +245,15 @@ class Redis
|
|
242
245
|
|
243
246
|
def read
|
244
247
|
io do
|
245
|
-
connection.read
|
248
|
+
value = connection.read
|
249
|
+
@pending_reads -= 1
|
250
|
+
value
|
246
251
|
end
|
247
252
|
end
|
248
253
|
|
249
254
|
def write(command)
|
250
255
|
io do
|
256
|
+
@pending_reads += 1
|
251
257
|
connection.write(command)
|
252
258
|
end
|
253
259
|
end
|
@@ -311,16 +317,23 @@ class Redis
|
|
311
317
|
server = @connector.resolve.dup
|
312
318
|
|
313
319
|
@options[:host] = server[:host]
|
314
|
-
@options[:port] = server[:port]
|
320
|
+
@options[:port] = Integer(server[:port]) if server.include?(:port)
|
321
|
+
|
322
|
+
@connection = @options[:driver].connect(@options)
|
323
|
+
@pending_reads = 0
|
324
|
+
rescue TimeoutError,
|
325
|
+
Errno::ECONNREFUSED,
|
326
|
+
Errno::EHOSTDOWN,
|
327
|
+
Errno::EHOSTUNREACH,
|
328
|
+
Errno::ENETUNREACH,
|
329
|
+
Errno::ETIMEDOUT
|
315
330
|
|
316
|
-
|
317
|
-
rescue TimeoutError
|
318
|
-
raise CannotConnectError, "Timed out connecting to Redis on #{location}"
|
319
|
-
rescue Errno::ECONNREFUSED
|
320
|
-
raise CannotConnectError, "Error connecting to Redis on #{location} (ECONNREFUSED)"
|
331
|
+
raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})"
|
321
332
|
end
|
322
333
|
|
323
334
|
def ensure_connected
|
335
|
+
disconnect if @pending_reads > 0
|
336
|
+
|
324
337
|
attempts = 0
|
325
338
|
|
326
339
|
begin
|
@@ -338,7 +351,7 @@ class Redis
|
|
338
351
|
end
|
339
352
|
|
340
353
|
yield
|
341
|
-
rescue
|
354
|
+
rescue BaseConnectionError
|
342
355
|
disconnect
|
343
356
|
|
344
357
|
if attempts <= @options[:reconnect_attempts] && @reconnect
|
@@ -353,6 +366,8 @@ class Redis
|
|
353
366
|
end
|
354
367
|
|
355
368
|
def _parse_options(options)
|
369
|
+
return options if options[:_parsed]
|
370
|
+
|
356
371
|
defaults = DEFAULTS.dup
|
357
372
|
options = options.dup
|
358
373
|
|
@@ -378,7 +393,7 @@ class Redis
|
|
378
393
|
defaults[:path] = uri.path
|
379
394
|
elsif uri.scheme == "redis"
|
380
395
|
# Require the URL to have at least a host
|
381
|
-
raise ArgumentError, "invalid url" unless uri.host
|
396
|
+
raise ArgumentError, "invalid url: #{uri}" unless uri.host
|
382
397
|
|
383
398
|
defaults[:scheme] = uri.scheme
|
384
399
|
defaults[:host] = uri.host
|
@@ -408,6 +423,12 @@ class Redis
|
|
408
423
|
end
|
409
424
|
|
410
425
|
options[:timeout] = options[:timeout].to_f
|
426
|
+
options[:connect_timeout] = if options[:connect_timeout]
|
427
|
+
options[:connect_timeout].to_f
|
428
|
+
else
|
429
|
+
options[:timeout]
|
430
|
+
end
|
431
|
+
|
411
432
|
options[:db] = options[:db].to_i
|
412
433
|
options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last
|
413
434
|
|
@@ -431,6 +452,8 @@ class Redis
|
|
431
452
|
end
|
432
453
|
end
|
433
454
|
|
455
|
+
options[:_parsed] = true
|
456
|
+
|
434
457
|
options
|
435
458
|
end
|
436
459
|
|
@@ -451,7 +474,7 @@ class Redis
|
|
451
474
|
|
452
475
|
class Connector
|
453
476
|
def initialize(options)
|
454
|
-
@options = options
|
477
|
+
@options = options.dup
|
455
478
|
end
|
456
479
|
|
457
480
|
def resolve
|
@@ -465,9 +488,12 @@ class Redis
|
|
465
488
|
def initialize(options)
|
466
489
|
super(options)
|
467
490
|
|
468
|
-
@
|
469
|
-
@
|
470
|
-
|
491
|
+
@options[:password] = DEFAULTS.fetch(:password)
|
492
|
+
@options[:db] = DEFAULTS.fetch(:db)
|
493
|
+
|
494
|
+
@sentinels = @options.delete(:sentinels).dup
|
495
|
+
@role = @options.fetch(:role, "master").to_s
|
496
|
+
@master = @options[:host]
|
471
497
|
end
|
472
498
|
|
473
499
|
def check(client)
|
@@ -482,7 +508,7 @@ class Redis
|
|
482
508
|
end
|
483
509
|
|
484
510
|
if role != @role
|
485
|
-
disconnect
|
511
|
+
client.disconnect
|
486
512
|
raise ConnectionError, "Instance role mismatch. Expected #{@role}, got #{role}."
|
487
513
|
end
|
488
514
|
end
|
@@ -502,7 +528,11 @@ class Redis
|
|
502
528
|
|
503
529
|
def sentinel_detect
|
504
530
|
@sentinels.each do |sentinel|
|
505
|
-
client = Client.new(
|
531
|
+
client = Client.new(@options.merge({
|
532
|
+
:host => sentinel[:host],
|
533
|
+
:port => sentinel[:port],
|
534
|
+
:reconnect_attempts => 0,
|
535
|
+
}))
|
506
536
|
|
507
537
|
begin
|
508
538
|
if result = yield(client)
|
@@ -512,12 +542,13 @@ class Redis
|
|
512
542
|
|
513
543
|
return result
|
514
544
|
end
|
545
|
+
rescue BaseConnectionError
|
515
546
|
ensure
|
516
547
|
client.disconnect
|
517
548
|
end
|
518
549
|
end
|
519
550
|
|
520
|
-
|
551
|
+
raise CannotConnectError, "No sentinels available."
|
521
552
|
end
|
522
553
|
|
523
554
|
def resolve_master
|
@@ -9,11 +9,12 @@ class Redis
|
|
9
9
|
|
10
10
|
def self.connect(config)
|
11
11
|
connection = ::Hiredis::Connection.new
|
12
|
+
connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
|
12
13
|
|
13
14
|
if config[:scheme] == "unix"
|
14
|
-
connection.connect_unix(config[:path],
|
15
|
+
connection.connect_unix(config[:path], connect_timeout)
|
15
16
|
else
|
16
|
-
connection.connect(config[:host], config[:port],
|
17
|
+
connection.connect(config[:host], config[:port], connect_timeout)
|
17
18
|
end
|
18
19
|
|
19
20
|
instance = new(connection)
|
@@ -206,9 +206,9 @@ class Redis
|
|
206
206
|
|
207
207
|
def self.connect(config)
|
208
208
|
if config[:scheme] == "unix"
|
209
|
-
sock = UNIXSocket.connect(config[:path], config[:
|
209
|
+
sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
|
210
210
|
else
|
211
|
-
sock = TCPSocket.connect(config[:host], config[:port], config[:
|
211
|
+
sock = TCPSocket.connect(config[:host], config[:port], config[:connect_timeout])
|
212
212
|
end
|
213
213
|
|
214
214
|
instance = new(sock)
|
@@ -70,7 +70,7 @@ class Redis
|
|
70
70
|
conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
|
71
71
|
else
|
72
72
|
conn = EventMachine.connect(config[:host], config[:port], RedisClient) do |c|
|
73
|
-
c.pending_connect_timeout = [config[:
|
73
|
+
c.pending_connect_timeout = [config[:connect_timeout], 0.1].max
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
data/lib/redis/version.rb
CHANGED
@@ -22,6 +22,20 @@ class TestCommandsOnSortedSets < Test::Unit::TestCase
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
def test_zrevrangebylex
|
26
|
+
target_version "2.9.9" do
|
27
|
+
r.zadd "foo", 0, "aaren"
|
28
|
+
r.zadd "foo", 0, "abagael"
|
29
|
+
r.zadd "foo", 0, "abby"
|
30
|
+
r.zadd "foo", 0, "abbygail"
|
31
|
+
|
32
|
+
assert_equal ["abbygail", "abby", "abagael", "aaren"], r.zrevrangebylex("foo", "[a\xff", "[a")
|
33
|
+
assert_equal ["abbygail", "abby"], r.zrevrangebylex("foo", "[a\xff", "[a", :limit => [0, 2])
|
34
|
+
assert_equal ["abbygail", "abby"], r.zrevrangebylex("foo", "(abb\xff", "(abb")
|
35
|
+
assert_equal ["abbygail"], r.zrevrangebylex("foo", "(abby\xff", "(abby")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
25
39
|
def test_zcount
|
26
40
|
r.zadd "foo", 1, "s1"
|
27
41
|
r.zadd "foo", 2, "s2"
|
@@ -101,13 +101,15 @@ class TestCommandsOnValueTypes < Test::Unit::TestCase
|
|
101
101
|
redis_mock(:migrate => lambda { |*args| args }) do |redis|
|
102
102
|
options = { :host => "127.0.0.1", :port => 1234 }
|
103
103
|
|
104
|
-
assert_raise(RuntimeError
|
104
|
+
ex = assert_raise(RuntimeError) do
|
105
105
|
redis.migrate("foo", options.reject { |key, _| key == :host })
|
106
106
|
end
|
107
|
+
assert ex.message =~ /host not specified/
|
107
108
|
|
108
|
-
assert_raise(RuntimeError
|
109
|
+
ex = assert_raise(RuntimeError) do
|
109
110
|
redis.migrate("foo", options.reject { |key, _| key == :port })
|
110
111
|
end
|
112
|
+
assert ex.message =~ /port not specified/
|
111
113
|
|
112
114
|
default_db = redis.client.db.to_i
|
113
115
|
default_timeout = redis.client.timeout.to_i
|
@@ -38,6 +38,33 @@ class TestConnectionHandling < Test::Unit::TestCase
|
|
38
38
|
assert !r.client.connected?
|
39
39
|
end
|
40
40
|
|
41
|
+
def test_disconnect
|
42
|
+
quit = 0
|
43
|
+
|
44
|
+
commands = {
|
45
|
+
:quit => lambda do
|
46
|
+
quit += 1
|
47
|
+
"+OK"
|
48
|
+
end
|
49
|
+
}
|
50
|
+
|
51
|
+
redis_mock(commands) do |redis|
|
52
|
+
assert_equal 0, quit
|
53
|
+
|
54
|
+
redis.quit
|
55
|
+
|
56
|
+
assert_equal 1, quit
|
57
|
+
|
58
|
+
redis.ping
|
59
|
+
|
60
|
+
redis.disconnect!
|
61
|
+
|
62
|
+
assert_equal 1, quit
|
63
|
+
|
64
|
+
assert !redis.connected?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
41
68
|
def test_shutdown
|
42
69
|
commands = {
|
43
70
|
:shutdown => lambda { :exit }
|
@@ -186,4 +213,25 @@ class TestConnectionHandling < Test::Unit::TestCase
|
|
186
213
|
r.config :set, "timeout", 300
|
187
214
|
end
|
188
215
|
end
|
216
|
+
|
217
|
+
driver(:ruby, :hiredis) do
|
218
|
+
def test_consistency_on_multithreaded_env
|
219
|
+
t = nil
|
220
|
+
|
221
|
+
commands = {
|
222
|
+
:set => lambda { |key, value| t.kill; "+OK\r\n" },
|
223
|
+
:incr => lambda { |key| ":1\r\n" },
|
224
|
+
}
|
225
|
+
|
226
|
+
redis_mock(commands) do |redis|
|
227
|
+
t = Thread.new do
|
228
|
+
redis.set("foo", "bar")
|
229
|
+
end
|
230
|
+
|
231
|
+
t.join
|
232
|
+
|
233
|
+
assert_equal 1, redis.incr("baz")
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
189
237
|
end
|
data/test/internals_test.rb
CHANGED
@@ -152,9 +152,12 @@ class TestInternals < Test::Unit::TestCase
|
|
152
152
|
end
|
153
153
|
|
154
154
|
def test_connection_timeout
|
155
|
+
opts = OPTIONS.merge(:host => "10.255.255.254", :connect_timeout => 0.1, :timeout => 5.0)
|
156
|
+
start_time = Time.now
|
155
157
|
assert_raise Redis::CannotConnectError do
|
156
|
-
Redis.new(
|
158
|
+
Redis.new(opts).ping
|
157
159
|
end
|
160
|
+
assert (Time.now - start_time) <= opts[:timeout]
|
158
161
|
end
|
159
162
|
|
160
163
|
def close_on_ping(seq, options = {})
|
@@ -87,6 +87,50 @@ class TestPublishSubscribe < Test::Unit::TestCase
|
|
87
87
|
assert_equal "s1", @message
|
88
88
|
end
|
89
89
|
|
90
|
+
def test_pubsub_with_numpat_subcommand
|
91
|
+
target_version("2.8.0") do
|
92
|
+
@subscribed = false
|
93
|
+
wire = Wire.new do
|
94
|
+
r.psubscribe("f*") do |on|
|
95
|
+
on.psubscribe { |channel, total| @subscribed = true }
|
96
|
+
on.pmessage { |pattern, channel, message| r.punsubscribe }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
Wire.pass while !@subscribed
|
100
|
+
redis = Redis.new(OPTIONS)
|
101
|
+
numpat_result = redis.pubsub(:numpat)
|
102
|
+
|
103
|
+
redis.publish("foo", "s1")
|
104
|
+
wire.join
|
105
|
+
|
106
|
+
assert_equal redis.pubsub(:numpat), 0
|
107
|
+
assert_equal numpat_result, 1
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
def test_pubsub_with_channels_and_numsub_subcommnads
|
113
|
+
target_version("2.8.0") do
|
114
|
+
@subscribed = false
|
115
|
+
wire = Wire.new do
|
116
|
+
r.subscribe("foo") do |on|
|
117
|
+
on.subscribe { |channel, total| @subscribed = true }
|
118
|
+
on.message { |channel, message| r.unsubscribe }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
Wire.pass while !@subscribed
|
122
|
+
redis = Redis.new(OPTIONS)
|
123
|
+
channels_result = redis.pubsub(:channels)
|
124
|
+
numsub_result = redis.pubsub(:numsub, 'foo', 'boo')
|
125
|
+
|
126
|
+
redis.publish("foo", "s1")
|
127
|
+
wire.join
|
128
|
+
|
129
|
+
assert_equal channels_result, ['foo']
|
130
|
+
assert_equal numsub_result, ['foo', 1, 'boo', 0]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
90
134
|
def test_subscribe_connection_usable_after_raise
|
91
135
|
@subscribed = false
|
92
136
|
|
@@ -82,7 +82,7 @@ class TestRemoteServerControlCommands < Test::Unit::TestCase
|
|
82
82
|
break line
|
83
83
|
end
|
84
84
|
|
85
|
-
assert_equal
|
85
|
+
assert_equal "OK", result
|
86
86
|
end
|
87
87
|
|
88
88
|
def test_echo
|
@@ -98,8 +98,9 @@ class TestRemoteServerControlCommands < Test::Unit::TestCase
|
|
98
98
|
def test_object
|
99
99
|
r.lpush "list", "value"
|
100
100
|
|
101
|
-
assert_equal r.object(:refcount, "list")
|
102
|
-
|
101
|
+
assert_equal 1, r.object(:refcount, "list")
|
102
|
+
encoding = r.object(:encoding, "list")
|
103
|
+
assert "ziplist" == encoding || "quicklist" == encoding, "Wrong encoding for list"
|
103
104
|
assert r.object(:idletime, "list").kind_of?(Fixnum)
|
104
105
|
end
|
105
106
|
|
@@ -112,6 +113,6 @@ class TestRemoteServerControlCommands < Test::Unit::TestCase
|
|
112
113
|
def test_slowlog
|
113
114
|
r.slowlog(:reset)
|
114
115
|
result = r.slowlog(:len)
|
115
|
-
assert_equal
|
116
|
+
assert_equal 0, result
|
116
117
|
end
|
117
118
|
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
4
|
+
|
5
|
+
class SentinalTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include Helper::Client
|
8
|
+
|
9
|
+
def test_sentinel_connection
|
10
|
+
sentinels = [{:host => "127.0.0.1", :port => 26381},
|
11
|
+
{:host => "127.0.0.1", :port => 26382}]
|
12
|
+
|
13
|
+
commands = {
|
14
|
+
:s1 => [],
|
15
|
+
:s2 => [],
|
16
|
+
}
|
17
|
+
|
18
|
+
handler = lambda do |id|
|
19
|
+
{
|
20
|
+
:sentinel => lambda do |command, *args|
|
21
|
+
commands[id] << [command, *args]
|
22
|
+
["127.0.0.1", "6381"]
|
23
|
+
end
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
RedisMock.start(handler.call(:s1), {}, 26381) do
|
28
|
+
RedisMock.start(handler.call(:s2), {}, 26382) do
|
29
|
+
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
|
30
|
+
|
31
|
+
assert redis.ping
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
assert_equal commands[:s1], [%w[get-master-addr-by-name master1]]
|
36
|
+
assert_equal commands[:s2], []
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_sentinel_failover
|
40
|
+
sentinels = [{:host => "127.0.0.1", :port => 26381},
|
41
|
+
{:host => "127.0.0.1", :port => 26382}]
|
42
|
+
|
43
|
+
commands = {
|
44
|
+
:s1 => [],
|
45
|
+
:s2 => [],
|
46
|
+
}
|
47
|
+
|
48
|
+
s1 = {
|
49
|
+
:sentinel => lambda do |command, *args|
|
50
|
+
commands[:s1] << [command, *args]
|
51
|
+
"$-1" # Nil
|
52
|
+
end
|
53
|
+
}
|
54
|
+
|
55
|
+
s2 = {
|
56
|
+
:sentinel => lambda do |command, *args|
|
57
|
+
commands[:s2] << [command, *args]
|
58
|
+
["127.0.0.1", "6381"]
|
59
|
+
end
|
60
|
+
}
|
61
|
+
|
62
|
+
RedisMock.start(s1, {}, 26381) do
|
63
|
+
RedisMock.start(s2, {}, 26382) do
|
64
|
+
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
|
65
|
+
|
66
|
+
assert redis.ping
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
assert_equal commands[:s1], [%w[get-master-addr-by-name master1]]
|
71
|
+
assert_equal commands[:s2], [%w[get-master-addr-by-name master1]]
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_sentinel_failover_prioritize_healthy_sentinel
|
75
|
+
sentinels = [{:host => "127.0.0.1", :port => 26381},
|
76
|
+
{:host => "127.0.0.1", :port => 26382}]
|
77
|
+
|
78
|
+
commands = {
|
79
|
+
:s1 => [],
|
80
|
+
:s2 => [],
|
81
|
+
}
|
82
|
+
|
83
|
+
s1 = {
|
84
|
+
:sentinel => lambda do |command, *args|
|
85
|
+
commands[:s1] << [command, *args]
|
86
|
+
"$-1" # Nil
|
87
|
+
end
|
88
|
+
}
|
89
|
+
|
90
|
+
s2 = {
|
91
|
+
:sentinel => lambda do |command, *args|
|
92
|
+
commands[:s2] << [command, *args]
|
93
|
+
["127.0.0.1", "6381"]
|
94
|
+
end
|
95
|
+
}
|
96
|
+
|
97
|
+
RedisMock.start(s1, {}, 26381) do
|
98
|
+
RedisMock.start(s2, {}, 26382) do
|
99
|
+
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
|
100
|
+
|
101
|
+
assert redis.ping
|
102
|
+
|
103
|
+
redis.quit
|
104
|
+
|
105
|
+
assert redis.ping
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
assert_equal commands[:s1], [%w[get-master-addr-by-name master1]]
|
110
|
+
assert_equal commands[:s2], [%w[get-master-addr-by-name master1], %w[get-master-addr-by-name master1]]
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_sentinel_with_non_sentinel_options
|
114
|
+
sentinels = [{:host => "127.0.0.1", :port => 26381}]
|
115
|
+
|
116
|
+
commands = {
|
117
|
+
:s1 => [],
|
118
|
+
:m1 => []
|
119
|
+
}
|
120
|
+
|
121
|
+
sentinel = {
|
122
|
+
:auth => lambda do |pass|
|
123
|
+
commands[:s1] << ["auth", pass]
|
124
|
+
"-ERR unknown command 'auth'"
|
125
|
+
end,
|
126
|
+
:select => lambda do |db|
|
127
|
+
commands[:s1] << ["select", db]
|
128
|
+
"-ERR unknown command 'select'"
|
129
|
+
end,
|
130
|
+
:sentinel => lambda do |command, *args|
|
131
|
+
commands[:s1] << [command, *args]
|
132
|
+
["127.0.0.1", "6382"]
|
133
|
+
end
|
134
|
+
}
|
135
|
+
|
136
|
+
master = {
|
137
|
+
:auth => lambda do |pass|
|
138
|
+
commands[:m1] << ["auth", pass]
|
139
|
+
"+OK"
|
140
|
+
end,
|
141
|
+
:role => lambda do
|
142
|
+
commands[:m1] << ["role"]
|
143
|
+
["master"]
|
144
|
+
end
|
145
|
+
}
|
146
|
+
|
147
|
+
RedisMock.start(master, {}, 6382) do
|
148
|
+
RedisMock.start(sentinel, {}, 26381) do
|
149
|
+
redis = Redis.new(:url => "redis://:foo@master1/15", :sentinels => sentinels, :role => :master)
|
150
|
+
|
151
|
+
assert redis.ping
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
assert_equal [%w[get-master-addr-by-name master1]], commands[:s1]
|
156
|
+
assert_equal [%w[auth foo], %w[role]], commands[:m1]
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_sentinel_role_mismatch
|
160
|
+
sentinels = [{:host => "127.0.0.1", :port => 26381}]
|
161
|
+
|
162
|
+
sentinel = {
|
163
|
+
:sentinel => lambda do |command, *args|
|
164
|
+
["127.0.0.1", "6382"]
|
165
|
+
end
|
166
|
+
}
|
167
|
+
|
168
|
+
master = {
|
169
|
+
:role => lambda do
|
170
|
+
["slave"]
|
171
|
+
end
|
172
|
+
}
|
173
|
+
|
174
|
+
ex = assert_raise(Redis::ConnectionError) do
|
175
|
+
RedisMock.start(master, {}, 6382) do
|
176
|
+
RedisMock.start(sentinel, {}, 26381) do
|
177
|
+
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
|
178
|
+
|
179
|
+
assert redis.ping
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
assert_match(/Instance role mismatch/, ex.message)
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_sentinel_retries
|
188
|
+
sentinels = [{:host => "127.0.0.1", :port => 26381},
|
189
|
+
{:host => "127.0.0.1", :port => 26382}]
|
190
|
+
|
191
|
+
connections = []
|
192
|
+
|
193
|
+
handler = lambda do |id|
|
194
|
+
{
|
195
|
+
:sentinel => lambda do |command, *args|
|
196
|
+
connections << id
|
197
|
+
|
198
|
+
if connections.count(id) < 2
|
199
|
+
:close
|
200
|
+
else
|
201
|
+
["127.0.0.1", "6382"]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
}
|
205
|
+
end
|
206
|
+
|
207
|
+
master = {
|
208
|
+
:role => lambda do
|
209
|
+
["master"]
|
210
|
+
end
|
211
|
+
}
|
212
|
+
|
213
|
+
RedisMock.start(master, {}, 6382) do
|
214
|
+
RedisMock.start(handler.call(:s1), {}, 26381) do
|
215
|
+
RedisMock.start(handler.call(:s2), {}, 26382) do
|
216
|
+
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master, :reconnect_attempts => 1)
|
217
|
+
|
218
|
+
assert redis.ping
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
assert_equal [:s1, :s2, :s1], connections
|
224
|
+
|
225
|
+
connections.clear
|
226
|
+
|
227
|
+
ex = assert_raise(Redis::CannotConnectError) do
|
228
|
+
RedisMock.start(master, {}, 6382) do
|
229
|
+
RedisMock.start(handler.call(:s1), {}, 26381) do
|
230
|
+
RedisMock.start(handler.call(:s2), {}, 26382) do
|
231
|
+
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master, :reconnect_attempts => 0)
|
232
|
+
|
233
|
+
assert redis.ping
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
assert_match(/No sentinels available/, ex.message)
|
240
|
+
end
|
241
|
+
end
|
data/test/support/redis_mock.rb
CHANGED
@@ -24,20 +24,26 @@ module RedisMock
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def run
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
begin
|
28
|
+
loop do
|
29
|
+
session = @server.accept
|
30
|
+
|
31
|
+
begin
|
32
|
+
return if yield(session) == :exit
|
33
|
+
ensure
|
34
|
+
session.close
|
35
|
+
end
|
36
|
+
end
|
37
|
+
rescue => ex
|
38
|
+
$stderr.puts "Error running mock server: #{ex.message}" if VERBOSE
|
39
|
+
$stderr.puts ex.backtrace if VERBOSE
|
40
|
+
retry
|
41
|
+
ensure
|
30
42
|
begin
|
31
|
-
|
32
|
-
|
33
|
-
session.close
|
43
|
+
@server.close
|
44
|
+
rescue IOError
|
34
45
|
end
|
35
46
|
end
|
36
|
-
rescue => ex
|
37
|
-
$stderr.puts "Error running mock server: #{ex.message}" if VERBOSE
|
38
|
-
$stderr.puts ex.backtrace if VERBOSE
|
39
|
-
ensure
|
40
|
-
@server.close
|
41
47
|
end
|
42
48
|
end
|
43
49
|
|
@@ -53,13 +59,13 @@ module RedisMock
|
|
53
59
|
# # Every connection will be closed immediately
|
54
60
|
# end
|
55
61
|
#
|
56
|
-
def self.start_with_handler(blk, options = {})
|
57
|
-
server = Server.new(
|
62
|
+
def self.start_with_handler(blk, options = {}, port = MOCK_PORT)
|
63
|
+
server = Server.new(port, options)
|
58
64
|
|
59
65
|
begin
|
60
66
|
server.start(&blk)
|
61
67
|
|
62
|
-
yield(
|
68
|
+
yield(port)
|
63
69
|
|
64
70
|
ensure
|
65
71
|
server.shutdown
|
@@ -75,7 +81,7 @@ module RedisMock
|
|
75
81
|
# assert_equal "PONG", Redis.new(:port => MOCK_PORT).ping
|
76
82
|
# end
|
77
83
|
#
|
78
|
-
def self.start(commands, options = {}, &blk)
|
84
|
+
def self.start(commands, options = {}, port = MOCK_PORT, &blk)
|
79
85
|
handler = lambda do |session|
|
80
86
|
while line = session.gets
|
81
87
|
argv = Array.new(line[1..-3].to_i) do
|
@@ -110,6 +116,6 @@ module RedisMock
|
|
110
116
|
end
|
111
117
|
end
|
112
118
|
|
113
|
-
start_with_handler(handler, options, &blk)
|
119
|
+
start_with_handler(handler, options, port, &blk)
|
114
120
|
end
|
115
121
|
end
|
data/test/test.conf.erb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.2.
|
4
|
+
version: 3.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ezra Zygmuntowicz
|
@@ -16,7 +16,7 @@ authors:
|
|
16
16
|
autorequire:
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
|
-
date:
|
19
|
+
date: 2015-02-11 00:00:00.000000000 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
name: rake
|
@@ -134,6 +134,7 @@ files:
|
|
134
134
|
- test/remote_server_control_commands_test.rb
|
135
135
|
- test/scanning_test.rb
|
136
136
|
- test/scripting_test.rb
|
137
|
+
- test/sentinel_test.rb
|
137
138
|
- test/sorting_test.rb
|
138
139
|
- test/support/connection/hiredis.rb
|
139
140
|
- test/support/connection/ruby.rb
|
@@ -142,7 +143,7 @@ files:
|
|
142
143
|
- test/support/wire/synchrony.rb
|
143
144
|
- test/support/wire/thread.rb
|
144
145
|
- test/synchrony_driver.rb
|
145
|
-
- test/test.conf
|
146
|
+
- test/test.conf.erb
|
146
147
|
- test/thread_safety_test.rb
|
147
148
|
- test/transactions_test.rb
|
148
149
|
- test/unknown_commands_test.rb
|
@@ -223,6 +224,7 @@ test_files:
|
|
223
224
|
- test/remote_server_control_commands_test.rb
|
224
225
|
- test/scanning_test.rb
|
225
226
|
- test/scripting_test.rb
|
227
|
+
- test/sentinel_test.rb
|
226
228
|
- test/sorting_test.rb
|
227
229
|
- test/support/connection/hiredis.rb
|
228
230
|
- test/support/connection/ruby.rb
|
@@ -231,7 +233,7 @@ test_files:
|
|
231
233
|
- test/support/wire/synchrony.rb
|
232
234
|
- test/support/wire/thread.rb
|
233
235
|
- test/synchrony_driver.rb
|
234
|
-
- test/test.conf
|
236
|
+
- test/test.conf.erb
|
235
237
|
- test/thread_safety_test.rb
|
236
238
|
- test/transactions_test.rb
|
237
239
|
- test/unknown_commands_test.rb
|