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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e25c90cb532eea0215afe63b0156fa102446c5f2
4
- data.tar.gz: b2acddebb3e293807a9b6bb6a6f986432859afb0
3
+ metadata.gz: 1245d9a408be7cfa9f796b5b02e6f2dcaff10f2a
4
+ data.tar.gz: 2dce33db07a7a16dcade71b081996f241d2075ab
5
5
  SHA512:
6
- metadata.gz: f599db91c12cf1a0e6a86c114e6ea687f9027d1a58e982f4f68bded6e38409cf4aab46fb742ec2d9fd80e85214a41a7f341399348645bb8765099c8f059b3efe
7
- data.tar.gz: 12fb14500e54c7eeb46a79a9b112eb5b85a457c50ae388f505db18b9a2ab826d3c206bd4d5bc778b487a94f7af48d8b5df79f1f01396087c2b856ea2e0b589d8
6
+ metadata.gz: ef3734d43bbd4fb5f50fbef3d9b3348d382c9389433a8af91e9d94410b818b3eaa8d306b827e91aafaa26fa28aa461f9c028293280c6df48bf5d28412554140a
7
+ data.tar.gz: cc7122626f5587a8a750746acfb59d7462fb426cd68234ee141805e77b5bbfb39736543f71fc26575ef5bd1bd6dce3b7eb2b8857408461cf4f9ac2e4f2d05c78
data/.gitignore CHANGED
@@ -13,3 +13,4 @@ Gemfile.lock
13
13
  /rdsrv
14
14
  /redis/*
15
15
  /test/db
16
+ /test/test.conf
@@ -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.0
15
+ # 3.2.1
16
16
 
17
- * Redis Sentinel support.
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
- # 3.1.1 (unreleased)
28
+ * Added `Redis#disconnect!` as a public-API way of disconnecting the client
29
+ (without needing to use `QUIT`). See #506.
20
30
 
21
- * Added support for variadic `PFCOUNT`.
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-pages.github.io/github/redis/redis-rb.png
7
- [inchpages-link]: http://inch-pages.github.io/github/redis/redis-rb
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 succesfully executed, all futures are assigned their
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"]
@@ -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.msetnx({ "key1" => "v1", "key2" => "v2" })
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.hmget("hash", "f1", "f2")
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.sscan("set").to_a
2488
+ # redis.sscan_each("set").to_a
2454
2489
  # # => ["key1", "key2", "key3"]
2455
2490
  #
2456
2491
  # @param [Hash] options
@@ -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
- @connection = @options[:driver].connect(server)
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 ConnectionError, InheritedError
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
- @sentinels = options.fetch(:sentinels).dup
469
- @role = options[:role].to_s
470
- @master = options[:host]
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(:host => sentinel[:host], :port => sentinel[:port], :timeout => 0.3)
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
- return nil
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], Integer(config[:timeout] * 1_000_000))
15
+ connection.connect_unix(config[:path], connect_timeout)
15
16
  else
16
- connection.connect(config[:host], config[:port], Integer(config[:timeout] * 1_000_000))
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[:timeout])
209
+ sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
210
210
  else
211
- sock = TCPSocket.connect(config[:host], config[:port], config[:timeout])
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[:timeout], 0.1].max
73
+ c.pending_connect_timeout = [config[:connect_timeout], 0.1].max
74
74
  end
75
75
  end
76
76
 
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "3.2.0"
2
+ VERSION = "3.2.1"
3
3
  end
@@ -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, /host not specified/) do
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, /port not specified/) do
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
@@ -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(OPTIONS.merge(:host => "10.255.255.254", :timeout => 0.1)).ping
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 result, "OK"
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"), 1
102
- assert_equal r.object(:encoding, "list"), "ziplist"
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 result, 0
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
@@ -24,20 +24,26 @@ module RedisMock
24
24
  end
25
25
 
26
26
  def run
27
- loop do
28
- session = @server.accept
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
- return if yield(session) == :exit
32
- ensure
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(MOCK_PORT, options)
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(MOCK_PORT)
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
@@ -0,0 +1,9 @@
1
+ dir <%= REDIS_DIR %>
2
+ pidfile <%= REDIS_PID %>
3
+ port 6381
4
+ unixsocket <%= REDIS_SOCKET %>
5
+ timeout 300
6
+ loglevel debug
7
+ logfile <%= REDIS_LOG %>
8
+ databases 16
9
+ daemonize yes
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.0
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: 2014-12-11 00:00:00.000000000 Z
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
@@ -1,9 +0,0 @@
1
- dir ./test/db
2
- pidfile ./redis.pid
3
- port 6381
4
- unixsocket ./redis.sock
5
- timeout 300
6
- loglevel debug
7
- logfile stdout
8
- databases 16
9
- daemonize yes