redis 3.2.0 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml 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