redis 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- Njk4NjUzM2ZiOTk2NTU0Y2FkYzg5NzU4NDc0Zjg1MmJiMDUwYjFlMg==
5
- data.tar.gz: !binary |-
6
- MTAxMTMzZmVjY2E2YjI1NzQ3Y2M1YTI5NDk3ZWQ5ZjVmZmExYTMwYg==
2
+ SHA1:
3
+ metadata.gz: e25c90cb532eea0215afe63b0156fa102446c5f2
4
+ data.tar.gz: b2acddebb3e293807a9b6bb6a6f986432859afb0
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- NjkwNTI4ZTg3OTVhNTkwYWViNGQzY2NmZGJiODQ1ZTc1MzNmNzdjNjE0NTkx
10
- NzljZTdhZTg1ZjEyYTYxYTJjYmJkYmIwZjYzZTdkNmFlOTNmNDZlY2YzMTY1
11
- MTgyZmRmZjNlNTEwNzZlYWJmM2Q3YTkxZTEyZDc1MjZhNWNlODc=
12
- data.tar.gz: !binary |-
13
- NDQ0NDIyZmVmMWVmMmE5ZGU4ZDQyYzAxODljYTEzODM2YjY5YzAyMWZjNzdh
14
- NTEwMjcwZDM2OWRhYWY4ZjUzMTRmOWU5MTNjMDM3ZDM2YjM0YTRhMDc3NGY0
15
- MjQ5ZTFhYjg5ODM5NDlmZjMyMDkxMTg1NmI3YjgyZWQwNmQzN2Y=
6
+ metadata.gz: f599db91c12cf1a0e6a86c114e6ea687f9027d1a58e982f4f68bded6e38409cf4aab46fb742ec2d9fd80e85214a41a7f341399348645bb8765099c8f059b3efe
7
+ data.tar.gz: 12fb14500e54c7eeb46a79a9b112eb5b85a457c50ae388f505db18b9a2ab826d3c206bd4d5bc778b487a94f7af48d8b5df79f1f01396087c2b856ea2e0b589d8
data/.gitignore CHANGED
@@ -1,11 +1,13 @@
1
1
  *.rdb
2
2
  *.swp
3
3
  Gemfile.lock
4
+ *.gem
4
5
  /tmp/
5
6
  /.idea
6
7
  /.yardoc
7
8
  /coverage/*
8
9
  /doc/
10
+ /examples/sentinel/sentinel.conf
9
11
  /nohup.out
10
12
  /pkg/*
11
13
  /rdsrv
@@ -1,52 +1,50 @@
1
1
  language: ruby
2
2
 
3
- branches:
4
- only:
5
- - master
6
-
7
3
  rvm:
8
4
  - 1.8.7
9
- - 1.9.2
10
5
  - 1.9.3
11
- - 2.0.0
12
- - 2.1.0
6
+ - 2.1
13
7
  - jruby-18mode
14
8
  - jruby-19mode
15
9
 
16
- gemfile:
17
- - .travis/Gemfile
10
+ gemfile: ".travis/Gemfile"
11
+
12
+ sudo: false
18
13
 
19
14
  env:
20
15
  global:
21
16
  - VERBOSE=true
22
17
  - TIMEOUT=1
23
18
  matrix:
24
- - conn=ruby REDIS_BRANCH=2.6
25
- - conn=hiredis REDIS_BRANCH=2.6
26
- - conn=synchrony REDIS_BRANCH=2.6
27
19
  - conn=ruby REDIS_BRANCH=2.8
20
+ - conn=hiredis REDIS_BRANCH=2.8
21
+ - conn=synchrony REDIS_BRANCH=2.8
28
22
  - conn=ruby REDIS_BRANCH=unstable
29
23
 
24
+ branches:
25
+ only:
26
+ - master
27
+
30
28
  matrix:
31
29
  exclude:
32
30
  # hiredis
33
31
  - rvm: jruby-18mode
34
32
  gemfile: .travis/Gemfile
35
- env: conn=hiredis REDIS_BRANCH=2.6
33
+ env: conn=hiredis REDIS_BRANCH=2.8
36
34
  - rvm: jruby-19mode
37
35
  gemfile: .travis/Gemfile
38
- env: conn=hiredis REDIS_BRANCH=2.6
36
+ env: conn=hiredis REDIS_BRANCH=2.8
39
37
 
40
38
  # synchrony
41
39
  - rvm: 1.8.7
42
40
  gemfile: .travis/Gemfile
43
- env: conn=synchrony REDIS_BRANCH=2.6
41
+ env: conn=synchrony REDIS_BRANCH=2.8
44
42
  - rvm: jruby-18mode
45
43
  gemfile: .travis/Gemfile
46
- env: conn=synchrony REDIS_BRANCH=2.6
44
+ env: conn=synchrony REDIS_BRANCH=2.8
47
45
  - rvm: jruby-19mode
48
46
  gemfile: .travis/Gemfile
49
- env: conn=synchrony REDIS_BRANCH=2.6
47
+ env: conn=synchrony REDIS_BRANCH=2.8
50
48
 
51
49
  notifications:
52
50
  irc:
@@ -12,7 +12,15 @@
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.1.x (unreleased)
15
+ # 3.2.0
16
+
17
+ * Redis Sentinel support.
18
+
19
+ # 3.1.1 (unreleased)
20
+
21
+ * Added support for variadic `PFCOUNT`.
22
+
23
+ # 3.1.0
16
24
 
17
25
  * Added debug log sanitization (#428).
18
26
 
data/README.md CHANGED
@@ -85,6 +85,34 @@ available on [rdoc.info][rdoc].
85
85
 
86
86
  [rdoc]: http://rdoc.info/github/redis/redis-rb/
87
87
 
88
+ ## Sentinel support
89
+
90
+ The client is able to perform automatic failovers by using [Redis
91
+ Sentinel](http://redis.io/topics/sentinel). Make sure to run Redis 2.8+
92
+ if you want to use this feature.
93
+
94
+ To connect using Sentinel, use:
95
+
96
+ ```ruby
97
+ SENTINELS = [{:host => "127.0.0.1", :port => 26380},
98
+ {:host => "127.0.0.1", :port => 26381}]
99
+
100
+ redis = Redis.new(:url => "redis://mymaster", :sentinels => SENTINELS, :role => :master)
101
+ ```
102
+
103
+ * The master name identifies a group of Redis instances composed of a master
104
+ and one or more slaves (`mymaster` in the example).
105
+
106
+ * It is possible to optionally provide a role. The allowed roles are `master`
107
+ and `slave`. When the role is `slave`, the client will try to connect to a
108
+ random slave of the specified master.
109
+
110
+ * When using the Sentinel support you need to specify a list of sentinels to
111
+ connect to. The list does not need to enumerate all your Sentinel instances,
112
+ but a few so that if one is down the client will try the next one. The client
113
+ is able to remember the last Sentinel that was able to reply correctly and will
114
+ use it for the next requests.
115
+
88
116
  ## Storing objects
89
117
 
90
118
  Redis only stores strings as values. If you want to store an object, you
@@ -166,7 +194,7 @@ end
166
194
  to redis, AND
167
195
  - your own code prevents the parent process from using the redis
168
196
  connection while a child is alive
169
-
197
+
170
198
  Improper use of `inherit_socket` will result in corrupted and/or incorrect
171
199
  responses.
172
200
 
@@ -0,0 +1,114 @@
1
+ # This file implements a simple consistency test for Redis-rb (or any other
2
+ # Redis environment if you pass a different client object) where a client
3
+ # writes to the database using INCR in order to increment keys, but actively
4
+ # remember the value the key should have. Before every write a read is performed
5
+ # to check if the value in the database matches the value expected.
6
+ #
7
+ # In this way this program can check for lost writes, or acknowledged writes
8
+ # that were executed.
9
+ #
10
+ # Copyright (C) 2013-2014 Salvatore Sanfilippo <antirez@gmail.com>
11
+ #
12
+ # Permission is hereby granted, free of charge, to any person obtaining
13
+ # a copy of this software and associated documentation files (the
14
+ # "Software"), to deal in the Software without restriction, including
15
+ # without limitation the rights to use, copy, modify, merge, publish,
16
+ # distribute, sublicense, and/or sell copies of the Software, and to
17
+ # permit persons to whom the Software is furnished to do so, subject to
18
+ # the following conditions:
19
+ #
20
+ # The above copyright notice and this permission notice shall be
21
+ # included in all copies or substantial portions of the Software.
22
+ #
23
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
+
31
+ require 'redis'
32
+
33
+ class ConsistencyTester
34
+ def initialize(redis)
35
+ @r = redis
36
+ @working_set = 10000
37
+ @keyspace = 100000
38
+ @writes = 0
39
+ @reads = 0
40
+ @failed_writes = 0
41
+ @failed_reads = 0
42
+ @lost_writes = 0
43
+ @not_ack_writes = 0
44
+ @delay = 0
45
+ @cached = {} # We take our view of data stored in the DB.
46
+ @prefix = [Process.pid.to_s,Time.now.usec,@r.object_id,""].join("|")
47
+ @errtime = {}
48
+ end
49
+
50
+ def genkey
51
+ # Write more often to a small subset of keys
52
+ ks = rand() > 0.5 ? @keyspace : @working_set
53
+ @prefix+"key_"+rand(ks).to_s
54
+ end
55
+
56
+ def check_consistency(key,value)
57
+ expected = @cached[key]
58
+ return if !expected # We lack info about previous state.
59
+ if expected > value
60
+ @lost_writes += expected-value
61
+ elsif expected < value
62
+ @not_ack_writes += value-expected
63
+ end
64
+ end
65
+
66
+ def puterr(msg)
67
+ if !@errtime[msg] || Time.now.to_i != @errtime[msg]
68
+ puts msg
69
+ end
70
+ @errtime[msg] = Time.now.to_i
71
+ end
72
+
73
+ def test
74
+ last_report = Time.now.to_i
75
+ while true
76
+ # Read
77
+ key = genkey
78
+ begin
79
+ val = @r.get(key)
80
+ check_consistency(key,val.to_i)
81
+ @reads += 1
82
+ rescue => e
83
+ puterr "Reading: #{e.class}: #{e.message} (#{e.backtrace.first})"
84
+ @failed_reads += 1
85
+ end
86
+
87
+ # Write
88
+ begin
89
+ @cached[key] = @r.incr(key).to_i
90
+ @writes += 1
91
+ rescue => e
92
+ puterr "Writing: #{e.class}: #{e.message} (#{e.backtrace.first})"
93
+ @failed_writes += 1
94
+ end
95
+
96
+ # Report
97
+ sleep @delay
98
+ if Time.now.to_i != last_report
99
+ report = "#{@reads} R (#{@failed_reads} err) | " +
100
+ "#{@writes} W (#{@failed_writes} err) | "
101
+ report += "#{@lost_writes} lost | " if @lost_writes > 0
102
+ report += "#{@not_ack_writes} noack | " if @not_ack_writes > 0
103
+ last_report = Time.now.to_i
104
+ puts report
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ Sentinels = [{:host => "127.0.0.1", :port => 26379},
111
+ {:host => "127.0.0.1", :port => 26380}]
112
+ r = Redis.new(:url => "redis://master1", :sentinels => Sentinels, :role => :master)
113
+ tester = ConsistencyTester.new(r)
114
+ tester.test
@@ -0,0 +1,41 @@
1
+ require 'redis'
2
+
3
+ # This example creates a master-slave setup with a sentinel, then connects to
4
+ # it and sends write commands in a loop.
5
+ #
6
+ # After 30 seconds, the master dies. You will be able to see how a new master
7
+ # is elected and things continue to work as if nothing happened.
8
+ #
9
+ # To run this example:
10
+ #
11
+ # $ ruby -I./lib examples/sentinel.rb
12
+ #
13
+
14
+ at_exit do
15
+ begin
16
+ Process.kill(:INT, $redises)
17
+ rescue Errno::ESRCH
18
+ end
19
+
20
+ Process.waitall
21
+ end
22
+
23
+ $redises = spawn("examples/sentinel/start")
24
+
25
+ Sentinels = [{:host => "127.0.0.1", :port => 26379},
26
+ {:host => "127.0.0.1", :port => 26380}]
27
+ r = Redis.new(:url => "redis://master1", :sentinels => Sentinels, :role => :master)
28
+
29
+ # Set keys into a loop.
30
+ #
31
+ # The example traps errors so that you can actually try to failover while
32
+ # running the script to see redis-rb reconfiguring.
33
+ (0..1000000).each{|i|
34
+ begin
35
+ r.set(i,i)
36
+ $stdout.write("SET (#{i} times)\n") if i % 100 == 0
37
+ rescue => e
38
+ $stdout.write("E")
39
+ end
40
+ sleep(0.01)
41
+ }
@@ -0,0 +1,49 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # This is a helper script used together with examples/sentinel.rb
4
+ # It runs two Redis masters, two slaves for each of them, and two sentinels.
5
+ # After 30 seconds, the first master dies.
6
+ #
7
+ # You don't need to run this script yourself. Rather, use examples/sentinel.rb.
8
+
9
+ require "fileutils"
10
+
11
+ $pids = []
12
+
13
+ at_exit do
14
+ $pids.each do |pid|
15
+ begin
16
+ Process.kill(:INT, pid)
17
+ rescue Errno::ESRCH
18
+ end
19
+ end
20
+
21
+ Process.waitall
22
+ end
23
+
24
+ base = File.expand_path(File.dirname(__FILE__))
25
+
26
+ # Masters
27
+ $pids << spawn("redis-server --port 6380 --loglevel warning")
28
+ $pids << spawn("redis-server --port 6381 --loglevel warning")
29
+
30
+ # Slaves of Master 1
31
+ $pids << spawn("redis-server --port 63800 --slaveof 127.0.0.1 6380 --loglevel warning")
32
+ $pids << spawn("redis-server --port 63801 --slaveof 127.0.0.1 6380 --loglevel warning")
33
+
34
+ # Slaves of Master 2
35
+ $pids << spawn("redis-server --port 63810 --slaveof 127.0.0.1 6381 --loglevel warning")
36
+ $pids << spawn("redis-server --port 63811 --slaveof 127.0.0.1 6381 --loglevel warning")
37
+
38
+ FileUtils.cp(File.join(base, "sentinel.conf"), "tmp/sentinel1.conf")
39
+ FileUtils.cp(File.join(base, "sentinel.conf"), "tmp/sentinel2.conf")
40
+
41
+ # Sentinels
42
+ $pids << spawn("redis-server tmp/sentinel1.conf --sentinel --port 26379")
43
+ $pids << spawn("redis-server tmp/sentinel2.conf --sentinel --port 26380")
44
+
45
+ sleep 30
46
+
47
+ Process.kill(:KILL, $pids[0])
48
+
49
+ Process.waitall
@@ -1588,6 +1588,38 @@ class Redis
1588
1588
  end
1589
1589
  end
1590
1590
 
1591
+ # Return a range of members with the same score in a sorted set, by lexicographical ordering
1592
+ #
1593
+ # @example Retrieve members matching a
1594
+ # redis.zrangebylex("zset", "[a", "[a\xff")
1595
+ # # => ["aaren", "aarika", "abagael", "abby"]
1596
+ # @example Retrieve the first 2 members matching a
1597
+ # redis.zrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
1598
+ # # => ["aaren", "aarika"]
1599
+ #
1600
+ # @param [String] key
1601
+ # @param [String] min
1602
+ # - inclusive minimum is specified by prefixing `(`
1603
+ # - exclusive minimum is specified by prefixing `[`
1604
+ # @param [String] max
1605
+ # - inclusive maximum is specified by prefixing `(`
1606
+ # - exclusive maximum is specified by prefixing `[`
1607
+ # @param [Hash] options
1608
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
1609
+ # `count` members
1610
+ #
1611
+ # @return [Array<String>, Array<[String, Float]>]
1612
+ def zrangebylex(key, min, max, options = {})
1613
+ args = []
1614
+
1615
+ limit = options[:limit]
1616
+ args.concat(["LIMIT"] + limit) if limit
1617
+
1618
+ synchronize do |client|
1619
+ client.call([:zrangebylex, key, min, max] + args)
1620
+ end
1621
+ end
1622
+
1591
1623
  # Return a range of members in a sorted set, by score.
1592
1624
  #
1593
1625
  # @example Retrieve members with score `>= 5` and `< 100`
@@ -2449,11 +2481,14 @@ class Redis
2449
2481
 
2450
2482
  # Get the approximate cardinality of members added to HyperLogLog structure.
2451
2483
  #
2452
- # @param [String] key
2484
+ # If called with multiple keys, returns the approximate cardinality of the
2485
+ # union of the HyperLogLogs contained in the keys.
2486
+ #
2487
+ # @param [String, Array<String>] keys
2453
2488
  # @return [Fixnum]
2454
- def pfcount(key)
2489
+ def pfcount(*keys)
2455
2490
  synchronize do |client|
2456
- client.call([:pfcount, key])
2491
+ client.call([:pfcount] + keys)
2457
2492
  end
2458
2493
  end
2459
2494
 
@@ -75,6 +75,12 @@ class Redis
75
75
  @logger = @options[:logger]
76
76
  @connection = nil
77
77
  @command_map = {}
78
+
79
+ if options.include?(:sentinels)
80
+ @connector = Connector::Sentinel.new(@options)
81
+ else
82
+ @connector = Connector.new(@options)
83
+ end
78
84
  end
79
85
 
80
86
  def connect
@@ -85,6 +91,7 @@ class Redis
85
91
  establish_connection
86
92
  call [:auth, password] if password
87
93
  call [:select, db] if db != 0
94
+ @connector.check(self)
88
95
  end
89
96
 
90
97
  self
@@ -301,8 +308,12 @@ class Redis
301
308
  end
302
309
 
303
310
  def establish_connection
304
- @connection = @options[:driver].connect(@options.dup)
311
+ server = @connector.resolve.dup
305
312
 
313
+ @options[:host] = server[:host]
314
+ @options[:port] = server[:port]
315
+
316
+ @connection = @options[:driver].connect(server)
306
317
  rescue TimeoutError
307
318
  raise CannotConnectError, "Timed out connecting to Redis on #{location}"
308
319
  rescue Errno::ECONNREFUSED
@@ -365,7 +376,7 @@ class Redis
365
376
 
366
377
  if uri.scheme == "unix"
367
378
  defaults[:path] = uri.path
368
- else
379
+ elsif uri.scheme == "redis"
369
380
  # Require the URL to have at least a host
370
381
  raise ArgumentError, "invalid url" unless uri.host
371
382
 
@@ -374,6 +385,9 @@ class Redis
374
385
  defaults[:port] = uri.port if uri.port
375
386
  defaults[:password] = CGI.unescape(uri.password) if uri.password
376
387
  defaults[:db] = uri.path[1..-1].to_i if uri.path
388
+ defaults[:role] = :master
389
+ else
390
+ raise ArgumentError, "invalid uri scheme '#{uri.scheme}'"
377
391
  end
378
392
  end
379
393
 
@@ -383,10 +397,12 @@ class Redis
383
397
  end
384
398
 
385
399
  if options[:path]
400
+ # Unix socket
386
401
  options[:scheme] = "unix"
387
402
  options.delete(:host)
388
403
  options.delete(:port)
389
404
  else
405
+ # TCP socket
390
406
  options[:host] = options[:host].to_s
391
407
  options[:port] = options[:port].to_i
392
408
  end
@@ -432,5 +448,96 @@ class Redis
432
448
 
433
449
  driver
434
450
  end
451
+
452
+ class Connector
453
+ def initialize(options)
454
+ @options = options
455
+ end
456
+
457
+ def resolve
458
+ @options
459
+ end
460
+
461
+ def check(client)
462
+ end
463
+
464
+ class Sentinel < Connector
465
+ def initialize(options)
466
+ super(options)
467
+
468
+ @sentinels = options.fetch(:sentinels).dup
469
+ @role = options[:role].to_s
470
+ @master = options[:host]
471
+ end
472
+
473
+ def check(client)
474
+ # Check the instance is really of the role we are looking for.
475
+ # We can't assume the command is supported since it was introduced
476
+ # recently and this client should work with old stuff.
477
+ begin
478
+ role = client.call([:role])[0]
479
+ rescue Redis::CommandError
480
+ # Assume the test is passed if we can't get a reply from ROLE...
481
+ role = @role
482
+ end
483
+
484
+ if role != @role
485
+ disconnect
486
+ raise ConnectionError, "Instance role mismatch. Expected #{@role}, got #{role}."
487
+ end
488
+ end
489
+
490
+ def resolve
491
+ result = case @role
492
+ when "master"
493
+ resolve_master
494
+ when "slave"
495
+ resolve_slave
496
+ else
497
+ raise ArgumentError, "Unknown instance role #{@role}"
498
+ end
499
+
500
+ result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
501
+ end
502
+
503
+ def sentinel_detect
504
+ @sentinels.each do |sentinel|
505
+ client = Client.new(:host => sentinel[:host], :port => sentinel[:port], :timeout => 0.3)
506
+
507
+ begin
508
+ if result = yield(client)
509
+ # This sentinel responded. Make sure we ask it first next time.
510
+ @sentinels.delete(sentinel)
511
+ @sentinels.unshift(sentinel)
512
+
513
+ return result
514
+ end
515
+ ensure
516
+ client.disconnect
517
+ end
518
+ end
519
+
520
+ return nil
521
+ end
522
+
523
+ def resolve_master
524
+ sentinel_detect do |client|
525
+ if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
526
+ {:host => reply[0], :port => reply[1]}
527
+ end
528
+ end
529
+ end
530
+
531
+ def resolve_slave
532
+ sentinel_detect do |client|
533
+ if reply = client.call(["sentinel", "slaves", @master])
534
+ slave = Hash[*reply.sample]
535
+
536
+ {:host => slave.fetch("ip"), :port => slave.fetch("port")}
537
+ end
538
+ end
539
+ end
540
+ end
541
+ end
435
542
  end
436
543
  end
@@ -792,8 +792,10 @@ class Redis
792
792
  end
793
793
 
794
794
  # Get the approximate cardinality of members added to HyperLogLog structure.
795
- def pfcount(key)
796
- node_for(key).pfcount(key)
795
+ def pfcount(*keys)
796
+ ensure_same_node(:pfcount, keys.flatten(1)) do |node|
797
+ node.pfcount(keys)
798
+ end
797
799
  end
798
800
 
799
801
  # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "3.1.0"
2
+ VERSION = "3.2.0"
3
3
  end
@@ -8,6 +8,20 @@ class TestCommandsOnSortedSets < Test::Unit::TestCase
8
8
  include Helper::Client
9
9
  include Lint::SortedSets
10
10
 
11
+ def test_zrangebylex
12
+ target_version "2.8.9" do
13
+ r.zadd "foo", 0, "aaren"
14
+ r.zadd "foo", 0, "abagael"
15
+ r.zadd "foo", 0, "abby"
16
+ r.zadd "foo", 0, "abbygail"
17
+
18
+ assert_equal ["aaren", "abagael", "abby", "abbygail"], r.zrangebylex("foo", "[a", "[a\xff")
19
+ assert_equal ["aaren", "abagael"], r.zrangebylex("foo", "[a", "[a\xff", :limit => [0, 2])
20
+ assert_equal ["abby", "abbygail"], r.zrangebylex("foo", "(abb", "(abb\xff")
21
+ assert_equal ["abbygail"], r.zrangebylex("foo", "(abby", "(abby\xff")
22
+ end
23
+ end
24
+
11
25
  def test_zcount
12
26
  r.zadd "foo", 1, "s1"
13
27
  r.zadd "foo", 2, "s2"
@@ -19,4 +19,15 @@ class TestDistributedCommandsOnHyperLogLog < Test::Unit::TestCase
19
19
  end
20
20
  end
21
21
 
22
- end
22
+ def test_pfcount_multiple_keys_diff_nodes
23
+ target_version "2.8.9" do
24
+ assert_raise Redis::Distributed::CannotDistribute do
25
+ r.pfadd "foo", "s1"
26
+ r.pfadd "bar", "s2"
27
+
28
+ assert r.pfcount("res", "foo", "bar")
29
+ end
30
+ end
31
+ end
32
+
33
+ end
@@ -5,6 +5,8 @@ require "test/unit"
5
5
  require "logger"
6
6
  require "stringio"
7
7
 
8
+ (class Random; def self.rand(*args) super end; end) unless defined?(Random)
9
+
8
10
  begin
9
11
  require "ruby-debug"
10
12
  rescue LoadError
@@ -375,8 +375,16 @@ class TestInternals < Test::Unit::TestCase
375
375
  begin
376
376
  s = Socket.new(af, Socket::SOCK_STREAM, 0)
377
377
  begin
378
- sa = Socket.pack_sockaddr_in(9999, hosts[af])
379
- s.bind(sa)
378
+ tries = 5
379
+ begin
380
+ sa = Socket.pack_sockaddr_in(1024 + Random.rand(63076), hosts[af])
381
+ s.bind(sa)
382
+ rescue Errno::EADDRINUSE
383
+ tries -= 1
384
+ retry if tries > 0
385
+
386
+ raise
387
+ end
380
388
  yield
381
389
  rescue Errno::EADDRNOTAVAIL
382
390
  ensure
@@ -33,16 +33,28 @@ module Lint
33
33
 
34
34
  def test_variadic_pfcount
35
35
  target_version "2.8.9" do
36
- assert_equal 0, r.pfcount(["foo", "bar"])
36
+ assert_equal 0, r.pfcount(["{1}foo", "{1}bar"])
37
37
 
38
- assert_equal true, r.pfadd("foo", "s1")
39
- assert_equal true, r.pfadd("bar", "s1")
40
- assert_equal true, r.pfadd("bar", "s2")
38
+ assert_equal true, r.pfadd("{1}foo", "s1")
39
+ assert_equal true, r.pfadd("{1}bar", "s1")
40
+ assert_equal true, r.pfadd("{1}bar", "s2")
41
+
42
+ assert_equal 2, r.pfcount("{1}foo", "{1}bar")
43
+ end
44
+ end
45
+
46
+ def test_variadic_pfcount_expanded
47
+ target_version "2.8.9" do
48
+ assert_equal 0, r.pfcount("{1}foo", "{1}bar")
49
+
50
+ assert_equal true, r.pfadd("{1}foo", "s1")
51
+ assert_equal true, r.pfadd("{1}bar", "s1")
52
+ assert_equal true, r.pfadd("{1}bar", "s2")
41
53
 
42
- assert_equal 2, r.pfcount(["foo", "bar"])
54
+ assert_equal 2, r.pfcount("{1}foo", "{1}bar")
43
55
  end
44
56
  end
45
57
 
46
58
  end
47
59
 
48
- end
60
+ end
@@ -191,7 +191,7 @@ class TestPublishSubscribe < Test::Unit::TestCase
191
191
  def test_subscribe_past_a_timeout
192
192
  # For some reason, a thread here doesn't reproduce the issue.
193
193
  sleep = %{sleep #{OPTIONS[:timeout] * 2}}
194
- publish = %{echo "publish foo bar\r\n" | nc 127.0.0.1 #{OPTIONS[:port]}}
194
+ publish = %{ruby -rsocket -e 't=TCPSocket.new("127.0.0.1",#{OPTIONS[:port]});t.write("publish foo bar\\r\\n");t.read(4);t.close'}
195
195
  cmd = [sleep, publish].join("; ")
196
196
 
197
197
  IO.popen(cmd, "r+") do |pipe|
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.1.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ezra Zygmuntowicz
@@ -16,36 +16,36 @@ authors:
16
16
  autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
- date: 2014-06-06 00:00:00.000000000 Z
19
+ date: 2014-12-11 00:00:00.000000000 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: rake
23
23
  requirement: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - ! '>='
25
+ - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
28
  type: :development
29
29
  prerelease: false
30
30
  version_requirements: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - ! '>='
32
+ - - ">="
33
33
  - !ruby/object:Gem::Version
34
34
  version: '0'
35
- description: ! " A Ruby client that tries to match Redis' API one-to-one, while
36
- still\n providing an idiomatic interface. It features thread-safety,\n client-side
37
- sharding, pipelining, and an obsession for performance.\n"
35
+ description: |2
36
+ A Ruby client that tries to match Redis' API one-to-one, while still
37
+ providing an idiomatic interface. It features thread-safety,
38
+ client-side sharding, pipelining, and an obsession for performance.
38
39
  email:
39
40
  - redis-db@googlegroups.com
40
41
  executables: []
41
42
  extensions: []
42
43
  extra_rdoc_files: []
43
44
  files:
44
- - .gitignore
45
- - .order
46
- - .travis.yml
47
- - .travis/Gemfile
48
- - .yardopts
45
+ - ".gitignore"
46
+ - ".travis.yml"
47
+ - ".travis/Gemfile"
48
+ - ".yardopts"
49
49
  - CHANGELOG.md
50
50
  - Gemfile
51
51
  - LICENSE
@@ -57,10 +57,14 @@ files:
57
57
  - benchmarking/suite.rb
58
58
  - benchmarking/worker.rb
59
59
  - examples/basic.rb
60
+ - examples/consistency.rb
60
61
  - examples/dist_redis.rb
61
62
  - examples/incr-decr.rb
62
63
  - examples/list.rb
63
64
  - examples/pubsub.rb
65
+ - examples/sentinel.rb
66
+ - examples/sentinel/sentinel.conf
67
+ - examples/sentinel/start
64
68
  - examples/sets.rb
65
69
  - examples/unicorn/config.ru
66
70
  - examples/unicorn/unicorn.rb
@@ -153,12 +157,12 @@ require_paths:
153
157
  - lib
154
158
  required_ruby_version: !ruby/object:Gem::Requirement
155
159
  requirements:
156
- - - ! '>='
160
+ - - ">="
157
161
  - !ruby/object:Gem::Version
158
162
  version: '0'
159
163
  required_rubygems_version: !ruby/object:Gem::Requirement
160
164
  requirements:
161
- - - ! '>='
165
+ - - ">="
162
166
  - !ruby/object:Gem::Version
163
167
  version: '0'
164
168
  requirements: []
@@ -232,4 +236,3 @@ test_files:
232
236
  - test/transactions_test.rb
233
237
  - test/unknown_commands_test.rb
234
238
  - test/url_param_test.rb
235
- has_rdoc:
data/.order DELETED
@@ -1,170 +0,0 @@
1
- {
2
- "connection": [
3
- "auth",
4
- "select",
5
- "ping",
6
- "echo",
7
- "quit"
8
- ],
9
- "server": [
10
- "bgrewriteaof",
11
- "bgsave",
12
- "config",
13
- "dbsize",
14
- "debug",
15
- "flushall",
16
- "flushdb",
17
- "info",
18
- "lastsave",
19
- "monitor",
20
- "save",
21
- "shutdown",
22
- "slaveof",
23
- "slowlog",
24
- "sync",
25
- "time",
26
- "client"
27
- ],
28
- "generic": [
29
- "persist",
30
- "expire",
31
- "expireat",
32
- "ttl",
33
- "pexpire",
34
- "pexpireat",
35
- "pttl",
36
- "dump",
37
- "restore",
38
- "del",
39
- "exists",
40
- "keys",
41
- "migrate",
42
- "move",
43
- "object",
44
- "randomkey",
45
- "rename",
46
- "renamenx",
47
- "sort",
48
- "type"
49
- ],
50
- "string": [
51
- "decr",
52
- "decrby",
53
- "incr",
54
- "incrby",
55
- "incrbyfloat",
56
- "set",
57
- "setex",
58
- "psetex",
59
- "setnx",
60
- "mset",
61
- "mapped_mset",
62
- "msetnx",
63
- "mapped_msetnx",
64
- "get",
65
- "mget",
66
- "mapped_mget",
67
- "setrange",
68
- "getrange",
69
- "setbit",
70
- "getbit",
71
- "append",
72
- "bitcount",
73
- "getset",
74
- "strlen",
75
- "bitop"
76
- ],
77
- "list": [
78
- "llen",
79
- "lpush",
80
- "lpushx",
81
- "rpush",
82
- "rpushx",
83
- "lpop",
84
- "rpop",
85
- "rpoplpush",
86
- "_bpop",
87
- "blpop",
88
- "brpop",
89
- "brpoplpush",
90
- "lindex",
91
- "linsert",
92
- "lrange",
93
- "lrem",
94
- "lset",
95
- "ltrim"
96
- ],
97
- "set": [
98
- "scard",
99
- "sadd",
100
- "srem",
101
- "spop",
102
- "srandmember",
103
- "smove",
104
- "sismember",
105
- "smembers",
106
- "sdiff",
107
- "sdiffstore",
108
- "sinter",
109
- "sinterstore",
110
- "sunion",
111
- "sunionstore"
112
- ],
113
- "sorted_set": [
114
- "zcard",
115
- "zadd",
116
- "zincrby",
117
- "zrem",
118
- "zscore",
119
- "zrange",
120
- "zrevrange",
121
- "zrank",
122
- "zrevrank",
123
- "zremrangebyrank",
124
- "zrangebyscore",
125
- "zrevrangebyscore",
126
- "zremrangebyscore",
127
- "zcount",
128
- "zinterstore",
129
- "zunionstore"
130
- ],
131
- "hash": [
132
- "hlen",
133
- "hset",
134
- "hsetnx",
135
- "hmset",
136
- "mapped_hmset",
137
- "hget",
138
- "hmget",
139
- "mapped_hmget",
140
- "hdel",
141
- "hexists",
142
- "hincrby",
143
- "hincrbyfloat",
144
- "hkeys",
145
- "hvals",
146
- "hgetall"
147
- ],
148
- "pubsub": [
149
- "publish",
150
- "subscribed?",
151
- "subscribe",
152
- "unsubscribe",
153
- "psubscribe",
154
- "punsubscribe"
155
- ],
156
- "transactions": [
157
- "watch",
158
- "unwatch",
159
- "pipelined",
160
- "multi",
161
- "exec",
162
- "discard"
163
- ],
164
- "scripting": [
165
- "script",
166
- "_eval",
167
- "eval",
168
- "evalsha"
169
- ]
170
- }