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.
Files changed (77) hide show
  1. data/.travis.yml +50 -0
  2. data/.travis/Gemfile +11 -0
  3. data/CHANGELOG.md +47 -19
  4. data/README.md +160 -149
  5. data/Rakefile +15 -50
  6. data/examples/pubsub.rb +1 -1
  7. data/examples/unicorn/config.ru +1 -1
  8. data/examples/unicorn/unicorn.rb +1 -1
  9. data/lib/redis.rb +790 -390
  10. data/lib/redis/client.rb +137 -49
  11. data/lib/redis/connection/hiredis.rb +26 -15
  12. data/lib/redis/connection/ruby.rb +170 -53
  13. data/lib/redis/connection/synchrony.rb +23 -35
  14. data/lib/redis/distributed.rb +92 -32
  15. data/lib/redis/errors.rb +4 -2
  16. data/lib/redis/pipeline.rb +17 -6
  17. data/lib/redis/version.rb +1 -1
  18. data/redis.gemspec +4 -6
  19. data/test/blocking_commands_test.rb +42 -0
  20. data/test/command_map_test.rb +18 -17
  21. data/test/commands_on_hashes_test.rb +13 -12
  22. data/test/commands_on_lists_test.rb +35 -45
  23. data/test/commands_on_sets_test.rb +55 -54
  24. data/test/commands_on_sorted_sets_test.rb +106 -105
  25. data/test/commands_on_strings_test.rb +64 -55
  26. data/test/commands_on_value_types_test.rb +66 -54
  27. data/test/connection_handling_test.rb +136 -151
  28. data/test/distributed_blocking_commands_test.rb +33 -40
  29. data/test/distributed_commands_on_hashes_test.rb +6 -7
  30. data/test/distributed_commands_on_lists_test.rb +13 -14
  31. data/test/distributed_commands_on_sets_test.rb +57 -58
  32. data/test/distributed_commands_on_sorted_sets_test.rb +11 -12
  33. data/test/distributed_commands_on_strings_test.rb +31 -32
  34. data/test/distributed_commands_on_value_types_test.rb +61 -46
  35. data/test/distributed_commands_requiring_clustering_test.rb +108 -108
  36. data/test/distributed_connection_handling_test.rb +14 -15
  37. data/test/distributed_internals_test.rb +7 -19
  38. data/test/distributed_key_tags_test.rb +36 -36
  39. data/test/distributed_persistence_control_commands_test.rb +17 -14
  40. data/test/distributed_publish_subscribe_test.rb +61 -69
  41. data/test/distributed_remote_server_control_commands_test.rb +39 -28
  42. data/test/distributed_sorting_test.rb +12 -13
  43. data/test/distributed_test.rb +40 -41
  44. data/test/distributed_transactions_test.rb +20 -21
  45. data/test/encoding_test.rb +12 -9
  46. data/test/error_replies_test.rb +42 -36
  47. data/test/helper.rb +118 -85
  48. data/test/helper_test.rb +20 -6
  49. data/test/internals_test.rb +167 -103
  50. data/test/lint/blocking_commands.rb +124 -0
  51. data/test/lint/hashes.rb +115 -93
  52. data/test/lint/lists.rb +86 -80
  53. data/test/lint/sets.rb +68 -62
  54. data/test/lint/sorted_sets.rb +200 -195
  55. data/test/lint/strings.rb +112 -94
  56. data/test/lint/value_types.rb +76 -55
  57. data/test/persistence_control_commands_test.rb +17 -12
  58. data/test/pipelining_commands_test.rb +135 -126
  59. data/test/publish_subscribe_test.rb +105 -110
  60. data/test/remote_server_control_commands_test.rb +74 -58
  61. data/test/sorting_test.rb +31 -29
  62. data/test/support/connection/hiredis.rb +1 -0
  63. data/test/support/connection/ruby.rb +1 -0
  64. data/test/support/connection/synchrony.rb +17 -0
  65. data/test/{redis_mock.rb → support/redis_mock.rb} +24 -21
  66. data/test/support/wire/synchrony.rb +24 -0
  67. data/test/support/wire/thread.rb +5 -0
  68. data/test/synchrony_driver.rb +9 -9
  69. data/test/test.conf +1 -1
  70. data/test/thread_safety_test.rb +21 -19
  71. data/test/transactions_test.rb +189 -118
  72. data/test/unknown_commands_test.rb +9 -8
  73. data/test/url_param_test.rb +46 -41
  74. metadata +28 -43
  75. data/TODO.md +0 -4
  76. data/benchmarking/thread_safety.rb +0 -38
  77. 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 initialize
64
- @timeout = 5_000_000
65
- @connection = nil
66
- end
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
- def connected?
69
- @connection && @connection.connected?
70
- end
72
+ fiber = Fiber.current
73
+ conn.callback { fiber.resume }
74
+ conn.errback { fiber.resume :refused }
71
75
 
72
- def timeout=(usecs)
73
- @timeout = usecs
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 connect(host, port, timeout)
77
- conn = EventMachine.connect(host, port, RedisClient) do |c|
78
- c.pending_connect_timeout = [Float(timeout / 1_000_000), 0.1].max
79
- end
83
+ def initialize(connection)
84
+ @connection = connection
85
+ end
80
86
 
81
- setup_connect_callbacks(conn, Fiber.current)
87
+ def connected?
88
+ @connection && @connection.connected?
82
89
  end
83
90
 
84
- def connect_unix(path, timeout)
85
- conn = EventMachine.connect_unix_domain(path, RedisClient)
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
@@ -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.connect(options.merge(:url => url)) }
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.connect(@default_options.merge(:url => url))
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 value and expiration of a key.
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(key, timeout)
300
- node_for(key).blpop(key, timeout)
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(key, timeout)
306
- node_for(key).brpop(key, timeout)
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, timeout)
312
- ensure_same_node(:brpoplpush, source, destination) do |node|
313
- node.brpoplpush(source, destination, timeout)
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, *keys) do |node|
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, *keys) do |node|
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, *keys) do |node|
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, *keys) do |node|
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, *keys) do |node|
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, *keys) do |node|
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, *keys) do |node|
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, *keys) do |node|
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, *keys) do |node|
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, *channels) do |node|
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
- node_info = nodes.map do |node|
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, *keys)
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
@@ -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
@@ -46,9 +46,15 @@ class Redis
46
46
  yield
47
47
  end
48
48
 
49
- def finish(replies)
50
- futures.each_with_index.map do |future, i|
51
- future._set(replies[i])
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?(::Exception) }
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?(::Exception)
113
+ ::Kernel.raise(@object) if @object.kind_of?(::RuntimeError)
103
114
  @object
104
115
  end
105
116
  end
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "3.0.0.rc1"
2
+ VERSION = "3.0.0.rc2"
3
3
  end