redis 3.0.0.rc1 → 3.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- 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