redis 3.0.0.rc1 → 3.0.0.rc2
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.
- data/.travis.yml +50 -0
- data/.travis/Gemfile +11 -0
- data/CHANGELOG.md +47 -19
- data/README.md +160 -149
- data/Rakefile +15 -50
- data/examples/pubsub.rb +1 -1
- data/examples/unicorn/config.ru +1 -1
- data/examples/unicorn/unicorn.rb +1 -1
- data/lib/redis.rb +790 -390
- data/lib/redis/client.rb +137 -49
- data/lib/redis/connection/hiredis.rb +26 -15
- data/lib/redis/connection/ruby.rb +170 -53
- data/lib/redis/connection/synchrony.rb +23 -35
- data/lib/redis/distributed.rb +92 -32
- data/lib/redis/errors.rb +4 -2
- data/lib/redis/pipeline.rb +17 -6
- data/lib/redis/version.rb +1 -1
- data/redis.gemspec +4 -6
- data/test/blocking_commands_test.rb +42 -0
- data/test/command_map_test.rb +18 -17
- data/test/commands_on_hashes_test.rb +13 -12
- data/test/commands_on_lists_test.rb +35 -45
- data/test/commands_on_sets_test.rb +55 -54
- data/test/commands_on_sorted_sets_test.rb +106 -105
- data/test/commands_on_strings_test.rb +64 -55
- data/test/commands_on_value_types_test.rb +66 -54
- data/test/connection_handling_test.rb +136 -151
- data/test/distributed_blocking_commands_test.rb +33 -40
- data/test/distributed_commands_on_hashes_test.rb +6 -7
- data/test/distributed_commands_on_lists_test.rb +13 -14
- data/test/distributed_commands_on_sets_test.rb +57 -58
- data/test/distributed_commands_on_sorted_sets_test.rb +11 -12
- data/test/distributed_commands_on_strings_test.rb +31 -32
- data/test/distributed_commands_on_value_types_test.rb +61 -46
- data/test/distributed_commands_requiring_clustering_test.rb +108 -108
- data/test/distributed_connection_handling_test.rb +14 -15
- data/test/distributed_internals_test.rb +7 -19
- data/test/distributed_key_tags_test.rb +36 -36
- data/test/distributed_persistence_control_commands_test.rb +17 -14
- data/test/distributed_publish_subscribe_test.rb +61 -69
- data/test/distributed_remote_server_control_commands_test.rb +39 -28
- data/test/distributed_sorting_test.rb +12 -13
- data/test/distributed_test.rb +40 -41
- data/test/distributed_transactions_test.rb +20 -21
- data/test/encoding_test.rb +12 -9
- data/test/error_replies_test.rb +42 -36
- data/test/helper.rb +118 -85
- data/test/helper_test.rb +20 -6
- data/test/internals_test.rb +167 -103
- data/test/lint/blocking_commands.rb +124 -0
- data/test/lint/hashes.rb +115 -93
- data/test/lint/lists.rb +86 -80
- data/test/lint/sets.rb +68 -62
- data/test/lint/sorted_sets.rb +200 -195
- data/test/lint/strings.rb +112 -94
- data/test/lint/value_types.rb +76 -55
- data/test/persistence_control_commands_test.rb +17 -12
- data/test/pipelining_commands_test.rb +135 -126
- data/test/publish_subscribe_test.rb +105 -110
- data/test/remote_server_control_commands_test.rb +74 -58
- data/test/sorting_test.rb +31 -29
- data/test/support/connection/hiredis.rb +1 -0
- data/test/support/connection/ruby.rb +1 -0
- data/test/support/connection/synchrony.rb +17 -0
- data/test/{redis_mock.rb → support/redis_mock.rb} +24 -21
- data/test/support/wire/synchrony.rb +24 -0
- data/test/support/wire/thread.rb +5 -0
- data/test/synchrony_driver.rb +9 -9
- data/test/test.conf +1 -1
- data/test/thread_safety_test.rb +21 -19
- data/test/transactions_test.rb +189 -118
- data/test/unknown_commands_test.rb +9 -8
- data/test/url_param_test.rb +46 -41
- metadata +28 -43
- data/TODO.md +0 -4
- data/benchmarking/thread_safety.rb +0 -38
- data/test/lint/internals.rb +0 -36
@@ -60,30 +60,36 @@ class Redis
|
|
60
60
|
class Synchrony
|
61
61
|
include Redis::Connection::CommandHelper
|
62
62
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
63
|
+
def self.connect(config)
|
64
|
+
if config[:scheme] == "unix"
|
65
|
+
conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
|
66
|
+
else
|
67
|
+
conn = EventMachine.connect(config[:host], config[:port], RedisClient) do |c|
|
68
|
+
c.pending_connect_timeout = [config[:timeout], 0.1].max
|
69
|
+
end
|
70
|
+
end
|
67
71
|
|
68
|
-
|
69
|
-
|
70
|
-
|
72
|
+
fiber = Fiber.current
|
73
|
+
conn.callback { fiber.resume }
|
74
|
+
conn.errback { fiber.resume :refused }
|
71
75
|
|
72
|
-
|
73
|
-
|
76
|
+
raise Errno::ECONNREFUSED if Fiber.yield == :refused
|
77
|
+
|
78
|
+
instance = new(conn)
|
79
|
+
instance.timeout = config[:timeout]
|
80
|
+
instance
|
74
81
|
end
|
75
82
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
end
|
83
|
+
def initialize(connection)
|
84
|
+
@connection = connection
|
85
|
+
end
|
80
86
|
|
81
|
-
|
87
|
+
def connected?
|
88
|
+
@connection && @connection.connected?
|
82
89
|
end
|
83
90
|
|
84
|
-
def
|
85
|
-
|
86
|
-
setup_connect_callbacks(conn, Fiber.current)
|
91
|
+
def timeout=(timeout)
|
92
|
+
@timeout = timeout
|
87
93
|
end
|
88
94
|
|
89
95
|
def disconnect
|
@@ -106,24 +112,6 @@ class Redis
|
|
106
112
|
raise "Unknown type #{type.inspect}"
|
107
113
|
end
|
108
114
|
end
|
109
|
-
|
110
|
-
private
|
111
|
-
|
112
|
-
def setup_connect_callbacks(conn, f)
|
113
|
-
conn.callback do
|
114
|
-
@connection = conn
|
115
|
-
f.resume conn
|
116
|
-
end
|
117
|
-
|
118
|
-
conn.errback do
|
119
|
-
@connection = conn
|
120
|
-
f.resume :refused
|
121
|
-
end
|
122
|
-
|
123
|
-
r = Fiber.yield
|
124
|
-
raise Errno::ECONNREFUSED if r == :refused
|
125
|
-
r
|
126
|
-
end
|
127
115
|
end
|
128
116
|
end
|
129
117
|
end
|
data/lib/redis/distributed.rb
CHANGED
@@ -18,7 +18,7 @@ class Redis
|
|
18
18
|
def initialize(urls, options = {})
|
19
19
|
@tag = options.delete(:tag) || /^\{(.+?)\}/
|
20
20
|
@default_options = options
|
21
|
-
@ring = HashRing.new urls.map { |url| Redis.
|
21
|
+
@ring = HashRing.new urls.map { |url| Redis.new(options.merge(:url => url)) }
|
22
22
|
@subscribed_node = nil
|
23
23
|
end
|
24
24
|
|
@@ -31,7 +31,7 @@ class Redis
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def add_node(url)
|
34
|
-
@ring.add_node Redis.
|
34
|
+
@ring.add_node Redis.new(@default_options.merge(:url => url))
|
35
35
|
end
|
36
36
|
|
37
37
|
# Close the connection.
|
@@ -84,14 +84,14 @@ class Redis
|
|
84
84
|
|
85
85
|
# Rename a key.
|
86
86
|
def rename(old_name, new_name)
|
87
|
-
ensure_same_node(:rename, old_name, new_name) do |node|
|
87
|
+
ensure_same_node(:rename, [old_name, new_name]) do |node|
|
88
88
|
node.rename(old_name, new_name)
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
92
|
# Rename a key, only if the new key does not exist.
|
93
93
|
def renamenx(old_name, new_name)
|
94
|
-
ensure_same_node(:renamenx, old_name, new_name) do |node|
|
94
|
+
ensure_same_node(:renamenx, [old_name, new_name]) do |node|
|
95
95
|
node.renamenx(old_name, new_name)
|
96
96
|
end
|
97
97
|
end
|
@@ -106,21 +106,36 @@ class Redis
|
|
106
106
|
node_for(key).expire(key, seconds)
|
107
107
|
end
|
108
108
|
|
109
|
+
# Set a key's time to live in milliseconds.
|
110
|
+
def pexpire(key, milliseconds)
|
111
|
+
node_for(key).pexpire(key, milliseconds)
|
112
|
+
end
|
113
|
+
|
109
114
|
# Set the expiration for a key as a UNIX timestamp.
|
110
115
|
def expireat(key, unix_time)
|
111
116
|
node_for(key).expireat(key, unix_time)
|
112
117
|
end
|
113
118
|
|
119
|
+
# Set the expiration for a key as number of milliseconds from UNIX Epoch.
|
120
|
+
def pexpireat(key, ms_unix_time)
|
121
|
+
node_for(key).pexpireat(key, ms_unix_time)
|
122
|
+
end
|
123
|
+
|
114
124
|
# Remove the expiration from a key.
|
115
125
|
def persist(key)
|
116
126
|
node_for(key).persist(key)
|
117
127
|
end
|
118
128
|
|
119
|
-
# Get the time to live for a key.
|
129
|
+
# Get the time to live (in seconds) for a key.
|
120
130
|
def ttl(key)
|
121
131
|
node_for(key).ttl(key)
|
122
132
|
end
|
123
133
|
|
134
|
+
# Get the time to live (in milliseconds) for a key.
|
135
|
+
def pttl(key)
|
136
|
+
node_for(key).pttl(key)
|
137
|
+
end
|
138
|
+
|
124
139
|
# Move a key to another database.
|
125
140
|
def move(key, db)
|
126
141
|
node_for(key).move(key, db)
|
@@ -146,11 +161,16 @@ class Redis
|
|
146
161
|
node_for(key).setrange(key, offset, value)
|
147
162
|
end
|
148
163
|
|
149
|
-
# Set the
|
164
|
+
# Set the time to live in seconds of a key.
|
150
165
|
def setex(key, ttl, value)
|
151
166
|
node_for(key).setex(key, ttl, value)
|
152
167
|
end
|
153
168
|
|
169
|
+
# Set the time to live in milliseconds of a key.
|
170
|
+
def psetex(key, ttl, value)
|
171
|
+
node_for(key).psetex(key, ttl, value)
|
172
|
+
end
|
173
|
+
|
154
174
|
# Get the value of a key.
|
155
175
|
def get(key)
|
156
176
|
node_for(key).get(key)
|
@@ -221,11 +241,16 @@ class Redis
|
|
221
241
|
node_for(key).incr(key)
|
222
242
|
end
|
223
243
|
|
224
|
-
# Increment the integer value of a key by the given number.
|
244
|
+
# Increment the integer value of a key by the given integer number.
|
225
245
|
def incrby(key, increment)
|
226
246
|
node_for(key).incrby(key, increment)
|
227
247
|
end
|
228
248
|
|
249
|
+
# Increment the numeric value of a key by the given float number.
|
250
|
+
def incrbyfloat(key, increment)
|
251
|
+
node_for(key).incrbyfloat(key, increment)
|
252
|
+
end
|
253
|
+
|
229
254
|
# Decrement the integer value of a key by one.
|
230
255
|
def decr(key)
|
231
256
|
node_for(key).decr(key)
|
@@ -289,28 +314,56 @@ class Redis
|
|
289
314
|
# Remove the last element in a list, append it to another list and return
|
290
315
|
# it.
|
291
316
|
def rpoplpush(source, destination)
|
292
|
-
ensure_same_node(:rpoplpush, source, destination) do |node|
|
317
|
+
ensure_same_node(:rpoplpush, [source, destination]) do |node|
|
293
318
|
node.rpoplpush(source, destination)
|
294
319
|
end
|
295
320
|
end
|
296
321
|
|
322
|
+
def _bpop(cmd, args)
|
323
|
+
options = {}
|
324
|
+
|
325
|
+
case args.last
|
326
|
+
when Hash
|
327
|
+
options = args.pop
|
328
|
+
when Integer
|
329
|
+
# Issue deprecation notice in obnoxious mode...
|
330
|
+
options[:timeout] = args.pop
|
331
|
+
end
|
332
|
+
|
333
|
+
if args.size > 1
|
334
|
+
# Issue deprecation notice in obnoxious mode...
|
335
|
+
end
|
336
|
+
|
337
|
+
keys = args.flatten
|
338
|
+
|
339
|
+
ensure_same_node(cmd, keys) do |node|
|
340
|
+
node.__send__(cmd, keys, options)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
297
344
|
# Remove and get the first element in a list, or block until one is
|
298
345
|
# available.
|
299
|
-
def blpop(
|
300
|
-
|
346
|
+
def blpop(*args)
|
347
|
+
_bpop(:blpop, args)
|
301
348
|
end
|
302
349
|
|
303
350
|
# Remove and get the last element in a list, or block until one is
|
304
351
|
# available.
|
305
|
-
def brpop(
|
306
|
-
|
352
|
+
def brpop(*args)
|
353
|
+
_bpop(:brpop, args)
|
307
354
|
end
|
308
355
|
|
309
356
|
# Pop a value from a list, push it to another list and return it; or block
|
310
357
|
# until one is available.
|
311
|
-
def brpoplpush(source, destination,
|
312
|
-
|
313
|
-
|
358
|
+
def brpoplpush(source, destination, options = {})
|
359
|
+
case options
|
360
|
+
when Integer
|
361
|
+
# Issue deprecation notice in obnoxious mode...
|
362
|
+
options = { :timeout => options }
|
363
|
+
end
|
364
|
+
|
365
|
+
ensure_same_node(:brpoplpush, [source, destination]) do |node|
|
366
|
+
node.brpoplpush(source, destination, options)
|
314
367
|
end
|
315
368
|
end
|
316
369
|
|
@@ -331,7 +384,7 @@ class Redis
|
|
331
384
|
|
332
385
|
# Move a member from one set to another.
|
333
386
|
def smove(source, destination, member)
|
334
|
-
ensure_same_node(:smove, source, destination) do |node|
|
387
|
+
ensure_same_node(:smove, [source, destination]) do |node|
|
335
388
|
node.smove(source, destination, member)
|
336
389
|
end
|
337
390
|
end
|
@@ -348,42 +401,42 @@ class Redis
|
|
348
401
|
|
349
402
|
# Intersect multiple sets.
|
350
403
|
def sinter(*keys)
|
351
|
-
ensure_same_node(:sinter,
|
404
|
+
ensure_same_node(:sinter, keys) do |node|
|
352
405
|
node.sinter(*keys)
|
353
406
|
end
|
354
407
|
end
|
355
408
|
|
356
409
|
# Intersect multiple sets and store the resulting set in a key.
|
357
410
|
def sinterstore(destination, *keys)
|
358
|
-
ensure_same_node(:sinterstore, destination
|
411
|
+
ensure_same_node(:sinterstore, [destination] + keys) do |node|
|
359
412
|
node.sinterstore(destination, *keys)
|
360
413
|
end
|
361
414
|
end
|
362
415
|
|
363
416
|
# Add multiple sets.
|
364
417
|
def sunion(*keys)
|
365
|
-
ensure_same_node(:sunion,
|
418
|
+
ensure_same_node(:sunion, keys) do |node|
|
366
419
|
node.sunion(*keys)
|
367
420
|
end
|
368
421
|
end
|
369
422
|
|
370
423
|
# Add multiple sets and store the resulting set in a key.
|
371
424
|
def sunionstore(destination, *keys)
|
372
|
-
ensure_same_node(:sunionstore, destination
|
425
|
+
ensure_same_node(:sunionstore, [destination] + keys) do |node|
|
373
426
|
node.sunionstore(destination, *keys)
|
374
427
|
end
|
375
428
|
end
|
376
429
|
|
377
430
|
# Subtract multiple sets.
|
378
431
|
def sdiff(*keys)
|
379
|
-
ensure_same_node(:sdiff,
|
432
|
+
ensure_same_node(:sdiff, keys) do |node|
|
380
433
|
node.sdiff(*keys)
|
381
434
|
end
|
382
435
|
end
|
383
436
|
|
384
437
|
# Subtract multiple sets and store the resulting set in a key.
|
385
438
|
def sdiffstore(destination, *keys)
|
386
|
-
ensure_same_node(:sdiffstore, destination
|
439
|
+
ensure_same_node(:sdiffstore, [destination] + keys) do |node|
|
387
440
|
node.sdiffstore(destination, *keys)
|
388
441
|
end
|
389
442
|
end
|
@@ -475,14 +528,14 @@ class Redis
|
|
475
528
|
# Intersect multiple sorted sets and store the resulting sorted set in a new
|
476
529
|
# key.
|
477
530
|
def zinterstore(destination, keys, options = {})
|
478
|
-
ensure_same_node(:zinterstore, destination
|
531
|
+
ensure_same_node(:zinterstore, [destination] + keys) do |node|
|
479
532
|
node.zinterstore(destination, keys, options)
|
480
533
|
end
|
481
534
|
end
|
482
535
|
|
483
536
|
# Add multiple sorted sets and store the resulting sorted set in a new key.
|
484
537
|
def zunionstore(destination, keys, options = {})
|
485
|
-
ensure_same_node(:zunionstore, destination
|
538
|
+
ensure_same_node(:zunionstore, [destination] + keys) do |node|
|
486
539
|
node.zunionstore(destination, keys, options)
|
487
540
|
end
|
488
541
|
end
|
@@ -550,16 +603,21 @@ class Redis
|
|
550
603
|
Hash[*fields.zip(hmget(key, *fields)).flatten]
|
551
604
|
end
|
552
605
|
|
553
|
-
# Increment the integer value of a hash field by the given number.
|
606
|
+
# Increment the integer value of a hash field by the given integer number.
|
554
607
|
def hincrby(key, field, increment)
|
555
608
|
node_for(key).hincrby(key, field, increment)
|
556
609
|
end
|
557
610
|
|
611
|
+
# Increment the numeric value of a hash field by the given float number.
|
612
|
+
def hincrbyfloat(key, field, increment)
|
613
|
+
node_for(key).hincrbyfloat(key, field, increment)
|
614
|
+
end
|
615
|
+
|
558
616
|
# Sort the elements in a list, set or sorted set.
|
559
617
|
def sort(key, options = {})
|
560
618
|
keys = [key, options[:by], options[:store], *Array(options[:get])].compact
|
561
619
|
|
562
|
-
ensure_same_node(:sort,
|
620
|
+
ensure_same_node(:sort, keys) do |node|
|
563
621
|
node.sort(key, options)
|
564
622
|
end
|
565
623
|
end
|
@@ -610,7 +668,7 @@ class Redis
|
|
610
668
|
@subscribed_node = node_for(channel)
|
611
669
|
@subscribed_node.subscribe(channel, &block)
|
612
670
|
else
|
613
|
-
ensure_same_node(:subscribe, channel
|
671
|
+
ensure_same_node(:subscribe, [channel] + channels) do |node|
|
614
672
|
@subscribed_node = node
|
615
673
|
node.subscribe(channel, *channels, &block)
|
616
674
|
end
|
@@ -658,15 +716,17 @@ class Redis
|
|
658
716
|
on_each_node :echo, value
|
659
717
|
end
|
660
718
|
|
719
|
+
# Get server time: an UNIX timestamp and the elapsed microseconds in the current second.
|
720
|
+
def time
|
721
|
+
on_each_node :time
|
722
|
+
end
|
723
|
+
|
661
724
|
def pipelined
|
662
725
|
raise CannotDistribute, :pipelined
|
663
726
|
end
|
664
727
|
|
665
728
|
def inspect
|
666
|
-
|
667
|
-
"#{node.id} (Redis v#{node.info['redis_version']})"
|
668
|
-
end
|
669
|
-
"#<Redis client v#{Redis::VERSION} connected to #{node_info.join(', ')}>"
|
729
|
+
"#<Redis client v#{Redis::VERSION} for #{nodes.map(&:id).join(', ')}>"
|
670
730
|
end
|
671
731
|
|
672
732
|
protected
|
@@ -685,7 +745,7 @@ class Redis
|
|
685
745
|
key.to_s[@tag, 1] if @tag
|
686
746
|
end
|
687
747
|
|
688
|
-
def ensure_same_node(command,
|
748
|
+
def ensure_same_node(command, keys)
|
689
749
|
tags = keys.map { |key| key_tag(key) }
|
690
750
|
|
691
751
|
raise CannotDistribute, command if !tags.all? || tags.uniq.size != 1
|
data/lib/redis/errors.rb
CHANGED
@@ -8,8 +8,6 @@ class Redis
|
|
8
8
|
def initialize(reply_type)
|
9
9
|
super(<<-EOS.gsub(/(?:^|\n)\s*/, " "))
|
10
10
|
Got '#{reply_type}' as initial reply byte.
|
11
|
-
If you're running in a multi-threaded environment, make sure you
|
12
|
-
pass the :thread_safe option when initializing the connection.
|
13
11
|
If you're in a forking environment, such as Unicorn, you need to
|
14
12
|
connect to Redis after forking.
|
15
13
|
EOS
|
@@ -35,4 +33,8 @@ class Redis
|
|
35
33
|
# Raised when performing I/O times out.
|
36
34
|
class TimeoutError < BaseConnectionError
|
37
35
|
end
|
36
|
+
|
37
|
+
# Raised when the connection was inherited by a child process.
|
38
|
+
class InheritedError < BaseConnectionError
|
39
|
+
end
|
38
40
|
end
|
data/lib/redis/pipeline.rb
CHANGED
@@ -46,9 +46,15 @@ class Redis
|
|
46
46
|
yield
|
47
47
|
end
|
48
48
|
|
49
|
-
def finish(replies)
|
50
|
-
|
51
|
-
future
|
49
|
+
def finish(replies, &blk)
|
50
|
+
if blk
|
51
|
+
futures.each_with_index.map do |future, i|
|
52
|
+
future._set(blk.call(replies[i]))
|
53
|
+
end
|
54
|
+
else
|
55
|
+
futures.each_with_index.map do |future, i|
|
56
|
+
future._set(replies[i])
|
57
|
+
end
|
52
58
|
end
|
53
59
|
end
|
54
60
|
|
@@ -58,10 +64,15 @@ class Redis
|
|
58
64
|
|
59
65
|
if replies.last.size < futures.size - 2
|
60
66
|
# Some command wasn't recognized by Redis.
|
61
|
-
raise replies.detect { |r| r.kind_of?(::
|
67
|
+
raise replies.detect { |r| r.kind_of?(::RuntimeError) }
|
62
68
|
end
|
63
69
|
|
64
|
-
super(replies.last)
|
70
|
+
super(replies.last) do |reply|
|
71
|
+
# Because an EXEC returns nested replies, hiredis won't be able to
|
72
|
+
# convert an error reply to a CommandError instance itself. This is
|
73
|
+
# specific to MULTI/EXEC, so we solve this here.
|
74
|
+
reply.is_a?(::RuntimeError) ? CommandError.new(reply.message) : reply
|
75
|
+
end
|
65
76
|
end
|
66
77
|
|
67
78
|
def commands
|
@@ -99,7 +110,7 @@ class Redis
|
|
99
110
|
end
|
100
111
|
|
101
112
|
def value
|
102
|
-
::Kernel.raise(@object) if @object.kind_of?(::
|
113
|
+
::Kernel.raise(@object) if @object.kind_of?(::RuntimeError)
|
103
114
|
@object
|
104
115
|
end
|
105
116
|
end
|
data/lib/redis/version.rb
CHANGED