redis 3.2.0 → 3.2.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.
- 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
|