redis 4.1.4 → 4.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/README.md +9 -4
- data/lib/redis/client.rb +71 -70
- data/lib/redis/cluster/node.rb +3 -0
- data/lib/redis/cluster/option.rb +4 -1
- data/lib/redis/cluster/slot.rb +28 -14
- data/lib/redis/cluster/slot_loader.rb +2 -3
- data/lib/redis/cluster.rb +4 -0
- data/lib/redis/connection/command_helper.rb +2 -2
- data/lib/redis/connection/hiredis.rb +3 -3
- data/lib/redis/connection/registry.rb +1 -1
- data/lib/redis/connection/ruby.rb +89 -107
- data/lib/redis/connection/synchrony.rb +8 -4
- data/lib/redis/connection.rb +1 -0
- data/lib/redis/distributed.rb +108 -57
- data/lib/redis/errors.rb +1 -0
- data/lib/redis/hash_ring.rb +14 -14
- data/lib/redis/pipeline.rb +6 -8
- data/lib/redis/subscribe.rb +10 -12
- data/lib/redis/version.rb +2 -1
- data/lib/redis.rb +287 -248
- metadata +14 -9
data/lib/redis/distributed.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative "hash_ring"
|
3
4
|
|
4
5
|
class Redis
|
5
6
|
class Distributed
|
6
|
-
|
7
7
|
class CannotDistribute < RuntimeError
|
8
8
|
def initialize(command)
|
9
9
|
@command = command
|
10
10
|
end
|
11
11
|
|
12
12
|
def message
|
13
|
-
"#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need
|
13
|
+
"#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need " \
|
14
|
+
"to be on the same server or because we cannot guarantee that the operation will be atomic."
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
@@ -23,10 +24,14 @@ class Redis
|
|
23
24
|
@default_options = options.dup
|
24
25
|
node_configs.each { |node_config| add_node(node_config) }
|
25
26
|
@subscribed_node = nil
|
27
|
+
@watch_key = nil
|
26
28
|
end
|
27
29
|
|
28
30
|
def node_for(key)
|
29
|
-
|
31
|
+
key = key_tag(key.to_s) || key.to_s
|
32
|
+
raise CannotDistribute, :watch if @watch_key && @watch_key != key
|
33
|
+
|
34
|
+
@ring.get_node(key)
|
30
35
|
end
|
31
36
|
|
32
37
|
def nodes
|
@@ -34,9 +39,9 @@ class Redis
|
|
34
39
|
end
|
35
40
|
|
36
41
|
def add_node(options)
|
37
|
-
options = { :
|
42
|
+
options = { url: options } if options.is_a?(String)
|
38
43
|
options = @default_options.merge(options)
|
39
|
-
@ring.add_node Redis.new(
|
44
|
+
@ring.add_node Redis.new(options)
|
40
45
|
end
|
41
46
|
|
42
47
|
# Change the selected database for the current connection.
|
@@ -145,12 +150,12 @@ class Redis
|
|
145
150
|
end
|
146
151
|
|
147
152
|
# Create a key using the serialized value, previously obtained using DUMP.
|
148
|
-
def restore(key, ttl, serialized_value, options
|
149
|
-
node_for(key).restore(key, ttl, serialized_value, options)
|
153
|
+
def restore(key, ttl, serialized_value, **options)
|
154
|
+
node_for(key).restore(key, ttl, serialized_value, **options)
|
150
155
|
end
|
151
156
|
|
152
157
|
# Transfer a key from the connected instance to another instance.
|
153
|
-
def migrate(
|
158
|
+
def migrate(_key, _options)
|
154
159
|
raise CannotDistribute, :migrate
|
155
160
|
end
|
156
161
|
|
@@ -171,8 +176,33 @@ class Redis
|
|
171
176
|
end
|
172
177
|
|
173
178
|
# Determine if a key exists.
|
174
|
-
def exists(
|
175
|
-
|
179
|
+
def exists(*args)
|
180
|
+
if !Redis.exists_returns_integer && args.size == 1
|
181
|
+
message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
|
182
|
+
"use `exists?` instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = true. " \
|
183
|
+
"(#{::Kernel.caller(1, 1).first})\n"
|
184
|
+
|
185
|
+
if defined?(::Warning)
|
186
|
+
::Warning.warn(message)
|
187
|
+
else
|
188
|
+
warn(message)
|
189
|
+
end
|
190
|
+
exists?(*args)
|
191
|
+
else
|
192
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
193
|
+
keys_per_node.inject(0) do |sum, (node, keys)|
|
194
|
+
sum + node._exists(*keys)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Determine if any of the keys exists.
|
200
|
+
def exists?(*args)
|
201
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
202
|
+
keys_per_node.each do |node, keys|
|
203
|
+
return true if node.exists?(*keys)
|
204
|
+
end
|
205
|
+
false
|
176
206
|
end
|
177
207
|
|
178
208
|
# Find all keys matching the given pattern.
|
@@ -205,11 +235,11 @@ class Redis
|
|
205
235
|
end
|
206
236
|
|
207
237
|
# Sort the elements in a list, set or sorted set.
|
208
|
-
def sort(key, options
|
238
|
+
def sort(key, **options)
|
209
239
|
keys = [key, options[:by], options[:store], *Array(options[:get])].compact
|
210
240
|
|
211
241
|
ensure_same_node(:sort, keys) do |node|
|
212
|
-
node.sort(key, options)
|
242
|
+
node.sort(key, **options)
|
213
243
|
end
|
214
244
|
end
|
215
245
|
|
@@ -244,8 +274,8 @@ class Redis
|
|
244
274
|
end
|
245
275
|
|
246
276
|
# Set the string value of a key.
|
247
|
-
def set(key, value, options
|
248
|
-
node_for(key).set(key, value, options)
|
277
|
+
def set(key, value, **options)
|
278
|
+
node_for(key).set(key, value, **options)
|
249
279
|
end
|
250
280
|
|
251
281
|
# Set the time to live in seconds of a key.
|
@@ -264,20 +294,20 @@ class Redis
|
|
264
294
|
end
|
265
295
|
|
266
296
|
# Set multiple keys to multiple values.
|
267
|
-
def mset(*
|
297
|
+
def mset(*_args)
|
268
298
|
raise CannotDistribute, :mset
|
269
299
|
end
|
270
300
|
|
271
|
-
def mapped_mset(
|
301
|
+
def mapped_mset(_hash)
|
272
302
|
raise CannotDistribute, :mapped_mset
|
273
303
|
end
|
274
304
|
|
275
305
|
# Set multiple keys to multiple values, only if none of the keys exist.
|
276
|
-
def msetnx(*
|
306
|
+
def msetnx(*_args)
|
277
307
|
raise CannotDistribute, :msetnx
|
278
308
|
end
|
279
309
|
|
280
|
-
def mapped_msetnx(
|
310
|
+
def mapped_msetnx(_hash)
|
281
311
|
raise CannotDistribute, :mapped_msetnx
|
282
312
|
end
|
283
313
|
|
@@ -336,7 +366,7 @@ class Redis
|
|
336
366
|
end
|
337
367
|
|
338
368
|
# Return the position of the first bit set to 1 or 0 in a string.
|
339
|
-
def bitpos(key, bit, start=nil, stop=nil)
|
369
|
+
def bitpos(key, bit, start = nil, stop = nil)
|
340
370
|
node_for(key).bitpos(key, bit, start, stop)
|
341
371
|
end
|
342
372
|
|
@@ -354,7 +384,7 @@ class Redis
|
|
354
384
|
get(key)
|
355
385
|
end
|
356
386
|
|
357
|
-
def []=(key,value)
|
387
|
+
def []=(key, value)
|
358
388
|
set(key, value)
|
359
389
|
end
|
360
390
|
|
@@ -439,15 +469,9 @@ class Redis
|
|
439
469
|
|
440
470
|
# Pop a value from a list, push it to another list and return it; or block
|
441
471
|
# until one is available.
|
442
|
-
def brpoplpush(source, destination,
|
443
|
-
case options
|
444
|
-
when Integer
|
445
|
-
# Issue deprecation notice in obnoxious mode...
|
446
|
-
options = { :timeout => options }
|
447
|
-
end
|
448
|
-
|
472
|
+
def brpoplpush(source, destination, deprecated_timeout = 0, **options)
|
449
473
|
ensure_same_node(:brpoplpush, [source, destination]) do |node|
|
450
|
-
node.brpoplpush(source, destination, options)
|
474
|
+
node.brpoplpush(source, destination, deprecated_timeout, **options)
|
451
475
|
end
|
452
476
|
end
|
453
477
|
|
@@ -524,13 +548,13 @@ class Redis
|
|
524
548
|
end
|
525
549
|
|
526
550
|
# Scan a set
|
527
|
-
def sscan(key, cursor, options
|
528
|
-
node_for(key).sscan(key, cursor, options)
|
551
|
+
def sscan(key, cursor, **options)
|
552
|
+
node_for(key).sscan(key, cursor, **options)
|
529
553
|
end
|
530
554
|
|
531
555
|
# Scan a set and return an enumerator
|
532
|
-
def sscan_each(key, options
|
533
|
-
node_for(key).sscan_each(key, options, &block)
|
556
|
+
def sscan_each(key, **options, &block)
|
557
|
+
node_for(key).sscan_each(key, **options, &block)
|
534
558
|
end
|
535
559
|
|
536
560
|
# Subtract multiple sets.
|
@@ -585,6 +609,7 @@ class Redis
|
|
585
609
|
def zadd(key, *args)
|
586
610
|
node_for(key).zadd(key, *args)
|
587
611
|
end
|
612
|
+
ruby2_keywords(:zadd) if respond_to?(:ruby2_keywords, true)
|
588
613
|
|
589
614
|
# Increment the score of a member in a sorted set.
|
590
615
|
def zincrby(key, increment, member)
|
@@ -602,14 +627,14 @@ class Redis
|
|
602
627
|
end
|
603
628
|
|
604
629
|
# Return a range of members in a sorted set, by index.
|
605
|
-
def zrange(key, start, stop, options
|
606
|
-
node_for(key).zrange(key, start, stop, options)
|
630
|
+
def zrange(key, start, stop, **options)
|
631
|
+
node_for(key).zrange(key, start, stop, **options)
|
607
632
|
end
|
608
633
|
|
609
634
|
# Return a range of members in a sorted set, by index, with scores ordered
|
610
635
|
# from high to low.
|
611
|
-
def zrevrange(key, start, stop, options
|
612
|
-
node_for(key).zrevrange(key, start, stop, options)
|
636
|
+
def zrevrange(key, start, stop, **options)
|
637
|
+
node_for(key).zrevrange(key, start, stop, **options)
|
613
638
|
end
|
614
639
|
|
615
640
|
# Determine the index of a member in a sorted set.
|
@@ -629,14 +654,14 @@ class Redis
|
|
629
654
|
end
|
630
655
|
|
631
656
|
# Return a range of members in a sorted set, by score.
|
632
|
-
def zrangebyscore(key, min, max, options
|
633
|
-
node_for(key).zrangebyscore(key, min, max, options)
|
657
|
+
def zrangebyscore(key, min, max, **options)
|
658
|
+
node_for(key).zrangebyscore(key, min, max, **options)
|
634
659
|
end
|
635
660
|
|
636
661
|
# Return a range of members in a sorted set, by score, with scores ordered
|
637
662
|
# from high to low.
|
638
|
-
def zrevrangebyscore(key, max, min, options
|
639
|
-
node_for(key).zrevrangebyscore(key, max, min, options)
|
663
|
+
def zrevrangebyscore(key, max, min, **options)
|
664
|
+
node_for(key).zrevrangebyscore(key, max, min, **options)
|
640
665
|
end
|
641
666
|
|
642
667
|
# Remove all members in a sorted set within the given scores.
|
@@ -651,16 +676,16 @@ class Redis
|
|
651
676
|
|
652
677
|
# Intersect multiple sorted sets and store the resulting sorted set in a new
|
653
678
|
# key.
|
654
|
-
def zinterstore(destination, keys, options
|
679
|
+
def zinterstore(destination, keys, **options)
|
655
680
|
ensure_same_node(:zinterstore, [destination] + keys) do |node|
|
656
|
-
node.zinterstore(destination, keys, options)
|
681
|
+
node.zinterstore(destination, keys, **options)
|
657
682
|
end
|
658
683
|
end
|
659
684
|
|
660
685
|
# Add multiple sorted sets and store the resulting sorted set in a new key.
|
661
|
-
def zunionstore(destination, keys, options
|
686
|
+
def zunionstore(destination, keys, **options)
|
662
687
|
ensure_same_node(:zunionstore, [destination] + keys) do |node|
|
663
|
-
node.zunionstore(destination, keys, options)
|
688
|
+
node.zunionstore(destination, keys, **options)
|
664
689
|
end
|
665
690
|
end
|
666
691
|
|
@@ -669,9 +694,9 @@ class Redis
|
|
669
694
|
node_for(key).hlen(key)
|
670
695
|
end
|
671
696
|
|
672
|
-
# Set
|
673
|
-
def hset(key,
|
674
|
-
node_for(key).hset(key,
|
697
|
+
# Set multiple hash fields to multiple values.
|
698
|
+
def hset(key, *attrs)
|
699
|
+
node_for(key).hset(key, *attrs)
|
675
700
|
end
|
676
701
|
|
677
702
|
# Set the value of a hash field, only if the field does not exist.
|
@@ -743,7 +768,7 @@ class Redis
|
|
743
768
|
end
|
744
769
|
|
745
770
|
def subscribed?
|
746
|
-
|
771
|
+
!!@subscribed_node
|
747
772
|
end
|
748
773
|
|
749
774
|
# Listen for messages published to the given channels.
|
@@ -761,7 +786,8 @@ class Redis
|
|
761
786
|
|
762
787
|
# Stop listening for messages posted to the given channels.
|
763
788
|
def unsubscribe(*channels)
|
764
|
-
raise
|
789
|
+
raise "Can't unsubscribe if not subscribed." unless subscribed?
|
790
|
+
|
765
791
|
@subscribed_node.unsubscribe(*channels)
|
766
792
|
end
|
767
793
|
|
@@ -777,13 +803,26 @@ class Redis
|
|
777
803
|
end
|
778
804
|
|
779
805
|
# Watch the given keys to determine execution of the MULTI/EXEC block.
|
780
|
-
def watch(*keys)
|
781
|
-
|
806
|
+
def watch(*keys, &block)
|
807
|
+
ensure_same_node(:watch, keys) do |node|
|
808
|
+
@watch_key = key_tag(keys.first) || keys.first.to_s
|
809
|
+
|
810
|
+
begin
|
811
|
+
node.watch(*keys, &block)
|
812
|
+
rescue StandardError
|
813
|
+
@watch_key = nil
|
814
|
+
raise
|
815
|
+
end
|
816
|
+
end
|
782
817
|
end
|
783
818
|
|
784
819
|
# Forget about all watched keys.
|
785
820
|
def unwatch
|
786
|
-
raise CannotDistribute, :unwatch
|
821
|
+
raise CannotDistribute, :unwatch unless @watch_key
|
822
|
+
|
823
|
+
result = node_for(@watch_key).unwatch
|
824
|
+
@watch_key = nil
|
825
|
+
result
|
787
826
|
end
|
788
827
|
|
789
828
|
def pipelined
|
@@ -791,18 +830,30 @@ class Redis
|
|
791
830
|
end
|
792
831
|
|
793
832
|
# Mark the start of a transaction block.
|
794
|
-
def multi
|
795
|
-
raise CannotDistribute, :multi
|
833
|
+
def multi(&block)
|
834
|
+
raise CannotDistribute, :multi unless @watch_key
|
835
|
+
|
836
|
+
result = node_for(@watch_key).multi(&block)
|
837
|
+
@watch_key = nil if block_given?
|
838
|
+
result
|
796
839
|
end
|
797
840
|
|
798
841
|
# Execute all commands issued after MULTI.
|
799
842
|
def exec
|
800
|
-
raise CannotDistribute, :exec
|
843
|
+
raise CannotDistribute, :exec unless @watch_key
|
844
|
+
|
845
|
+
result = node_for(@watch_key).exec
|
846
|
+
@watch_key = nil
|
847
|
+
result
|
801
848
|
end
|
802
849
|
|
803
850
|
# Discard all commands issued after MULTI.
|
804
851
|
def discard
|
805
|
-
raise CannotDistribute, :discard
|
852
|
+
raise CannotDistribute, :discard unless @watch_key
|
853
|
+
|
854
|
+
result = node_for(@watch_key).discard
|
855
|
+
@watch_key = nil
|
856
|
+
result
|
806
857
|
end
|
807
858
|
|
808
859
|
# Control remote script registry.
|
@@ -861,7 +912,7 @@ class Redis
|
|
861
912
|
self.class.new(@node_configs, @default_options)
|
862
913
|
end
|
863
914
|
|
864
|
-
|
915
|
+
protected
|
865
916
|
|
866
917
|
def on_each_node(command, *args)
|
867
918
|
nodes.map do |node|
|
data/lib/redis/errors.rb
CHANGED
data/lib/redis/hash_ring.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'zlib'
|
3
4
|
|
4
5
|
class Redis
|
5
6
|
class HashRing
|
6
|
-
|
7
7
|
POINTS_PER_SERVER = 160 # this is the default in libmemcached
|
8
8
|
|
9
9
|
attr_reader :ring, :sorted_keys, :replicas, :nodes
|
@@ -11,7 +11,7 @@ class Redis
|
|
11
11
|
# nodes is a list of objects that have a proper to_s representation.
|
12
12
|
# replicas indicates how many virtual points should be used pr. node,
|
13
13
|
# replicas are required to improve the distribution.
|
14
|
-
def initialize(nodes=[], replicas=POINTS_PER_SERVER)
|
14
|
+
def initialize(nodes = [], replicas = POINTS_PER_SERVER)
|
15
15
|
@replicas = replicas
|
16
16
|
@ring = {}
|
17
17
|
@nodes = []
|
@@ -33,11 +33,11 @@ class Redis
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def remove_node(node)
|
36
|
-
@nodes.reject!{|n| n.id == node.id}
|
36
|
+
@nodes.reject! { |n| n.id == node.id }
|
37
37
|
@replicas.times do |i|
|
38
38
|
key = Zlib.crc32("#{node.id}:#{i}")
|
39
39
|
@ring.delete(key)
|
40
|
-
@sorted_keys.reject! {|k| k == key}
|
40
|
+
@sorted_keys.reject! { |k| k == key }
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -47,27 +47,29 @@ class Redis
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def get_node_pos(key)
|
50
|
-
return [nil,nil] if @ring.
|
50
|
+
return [nil, nil] if @ring.empty?
|
51
|
+
|
51
52
|
crc = Zlib.crc32(key)
|
52
53
|
idx = HashRing.binary_search(@sorted_keys, crc)
|
53
|
-
|
54
|
+
[@ring[@sorted_keys[idx]], idx]
|
54
55
|
end
|
55
56
|
|
56
57
|
def iter_nodes(key)
|
57
|
-
return [nil,nil] if @ring.
|
58
|
+
return [nil, nil] if @ring.empty?
|
59
|
+
|
58
60
|
_, pos = get_node_pos(key)
|
59
61
|
@ring.size.times do |n|
|
60
|
-
yield @ring[@sorted_keys[(pos+n) % @ring.size]]
|
62
|
+
yield @ring[@sorted_keys[(pos + n) % @ring.size]]
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
64
66
|
# Find the closest index in HashRing with value <= the given value
|
65
|
-
def self.binary_search(ary, value
|
67
|
+
def self.binary_search(ary, value)
|
66
68
|
upper = ary.size - 1
|
67
69
|
lower = 0
|
68
70
|
idx = 0
|
69
71
|
|
70
|
-
while
|
72
|
+
while lower <= upper
|
71
73
|
idx = (lower + upper) / 2
|
72
74
|
comp = ary[idx] <=> value
|
73
75
|
|
@@ -80,10 +82,8 @@ class Redis
|
|
80
82
|
end
|
81
83
|
end
|
82
84
|
|
83
|
-
if upper < 0
|
84
|
-
|
85
|
-
end
|
86
|
-
return upper
|
85
|
+
upper = ary.size - 1 if upper < 0
|
86
|
+
upper
|
87
87
|
end
|
88
88
|
end
|
89
89
|
end
|
data/lib/redis/pipeline.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
class Redis
|
3
4
|
class Pipeline
|
4
5
|
attr_accessor :db
|
@@ -61,7 +62,7 @@ class Redis
|
|
61
62
|
@futures.map(&:timeout)
|
62
63
|
end
|
63
64
|
|
64
|
-
def with_reconnect(val=true)
|
65
|
+
def with_reconnect(val = true)
|
65
66
|
@with_reconnect = false unless val
|
66
67
|
yield
|
67
68
|
end
|
@@ -93,7 +94,8 @@ class Redis
|
|
93
94
|
|
94
95
|
if exec.size < futures.size
|
95
96
|
# Some command wasn't recognized by Redis.
|
96
|
-
|
97
|
+
command_error = replies.detect { |r| r.is_a?(CommandError) }
|
98
|
+
raise command_error
|
97
99
|
end
|
98
100
|
|
99
101
|
super(exec) do |reply|
|
@@ -145,11 +147,7 @@ class Redis
|
|
145
147
|
message << " - You probably meant to call .value == or .value !="
|
146
148
|
message << " (#{::Kernel.caller(1, 1).first})\n"
|
147
149
|
|
148
|
-
|
149
|
-
::Warning.warn(message)
|
150
|
-
else
|
151
|
-
$stderr.puts(message)
|
152
|
-
end
|
150
|
+
::Kernel.warn(message)
|
153
151
|
|
154
152
|
super
|
155
153
|
end
|
@@ -168,7 +166,7 @@ class Redis
|
|
168
166
|
end
|
169
167
|
|
170
168
|
def value
|
171
|
-
::Kernel.raise(@object) if @object.
|
169
|
+
::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
|
172
170
|
@object
|
173
171
|
end
|
174
172
|
|
data/lib/redis/subscribe.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
class Redis
|
3
4
|
class SubscribedClient
|
4
5
|
def initialize(client)
|
@@ -33,24 +34,21 @@ class Redis
|
|
33
34
|
call([:punsubscribe, *channels])
|
34
35
|
end
|
35
36
|
|
36
|
-
|
37
|
+
protected
|
37
38
|
|
38
39
|
def subscription(start, stop, channels, block, timeout = 0)
|
39
40
|
sub = Subscription.new(&block)
|
40
41
|
|
41
42
|
unsubscribed = false
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
break if unsubscribed
|
49
|
-
end
|
50
|
-
ensure
|
51
|
-
# No need to unsubscribe here. The real client closes the connection
|
52
|
-
# whenever an exception is raised (see #ensure_connected).
|
44
|
+
@client.call_loop([start, *channels], timeout) do |line|
|
45
|
+
type, *rest = line
|
46
|
+
sub.callbacks[type].call(*rest)
|
47
|
+
unsubscribed = type == stop && rest.last == 0
|
48
|
+
break if unsubscribed
|
53
49
|
end
|
50
|
+
# No need to unsubscribe here. The real client closes the connection
|
51
|
+
# whenever an exception is raised (see #ensure_connected).
|
54
52
|
end
|
55
53
|
end
|
56
54
|
|
@@ -59,7 +57,7 @@ class Redis
|
|
59
57
|
|
60
58
|
def initialize
|
61
59
|
@callbacks = Hash.new do |hash, key|
|
62
|
-
hash[key] =
|
60
|
+
hash[key] = ->(*_) {}
|
63
61
|
end
|
64
62
|
|
65
63
|
yield(self)
|
data/lib/redis/version.rb
CHANGED