redis 3.3.5 → 4.0.0

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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +36 -52
  3. data/.travis/Gemfile +3 -1
  4. data/CHANGELOG.md +8 -6
  5. data/Gemfile +0 -1
  6. data/README.md +31 -75
  7. data/benchmarking/logging.rb +1 -1
  8. data/bors.toml +14 -0
  9. data/lib/redis.rb +68 -41
  10. data/lib/redis/client.rb +12 -8
  11. data/lib/redis/connection.rb +2 -2
  12. data/lib/redis/connection/command_helper.rb +2 -8
  13. data/lib/redis/connection/hiredis.rb +2 -2
  14. data/lib/redis/connection/ruby.rb +8 -28
  15. data/lib/redis/connection/synchrony.rb +12 -4
  16. data/lib/redis/distributed.rb +3 -3
  17. data/lib/redis/hash_ring.rb +20 -64
  18. data/lib/redis/pipeline.rb +0 -6
  19. data/lib/redis/version.rb +1 -1
  20. data/makefile +42 -0
  21. data/redis.gemspec +7 -9
  22. data/test/bitpos_test.rb +13 -19
  23. data/test/blocking_commands_test.rb +3 -5
  24. data/test/client_test.rb +1 -1
  25. data/test/command_map_test.rb +3 -5
  26. data/test/commands_on_hashes_test.rb +2 -4
  27. data/test/commands_on_hyper_log_log_test.rb +3 -5
  28. data/test/commands_on_lists_test.rb +2 -4
  29. data/test/commands_on_sets_test.rb +2 -4
  30. data/test/commands_on_sorted_sets_test.rb +17 -4
  31. data/test/commands_on_strings_test.rb +3 -5
  32. data/test/commands_on_value_types_test.rb +4 -6
  33. data/test/connection_handling_test.rb +5 -7
  34. data/test/distributed_blocking_commands_test.rb +2 -4
  35. data/test/distributed_commands_on_hashes_test.rb +2 -4
  36. data/test/distributed_commands_on_hyper_log_log_test.rb +2 -4
  37. data/test/distributed_commands_on_lists_test.rb +2 -4
  38. data/test/distributed_commands_on_sets_test.rb +2 -4
  39. data/test/distributed_commands_on_sorted_sets_test.rb +2 -4
  40. data/test/distributed_commands_on_strings_test.rb +2 -4
  41. data/test/distributed_commands_on_value_types_test.rb +2 -4
  42. data/test/distributed_commands_requiring_clustering_test.rb +1 -3
  43. data/test/distributed_connection_handling_test.rb +1 -3
  44. data/test/distributed_internals_test.rb +8 -19
  45. data/test/distributed_key_tags_test.rb +4 -6
  46. data/test/distributed_persistence_control_commands_test.rb +1 -3
  47. data/test/distributed_publish_subscribe_test.rb +1 -3
  48. data/test/distributed_remote_server_control_commands_test.rb +1 -3
  49. data/test/distributed_scripting_test.rb +1 -3
  50. data/test/distributed_sorting_test.rb +1 -3
  51. data/test/distributed_test.rb +12 -14
  52. data/test/distributed_transactions_test.rb +1 -3
  53. data/test/encoding_test.rb +4 -8
  54. data/test/error_replies_test.rb +2 -4
  55. data/test/fork_safety_test.rb +1 -6
  56. data/test/helper.rb +10 -41
  57. data/test/helper_test.rb +1 -3
  58. data/test/internals_test.rb +67 -55
  59. data/test/lint/strings.rb +6 -20
  60. data/test/lint/value_types.rb +8 -0
  61. data/test/persistence_control_commands_test.rb +1 -3
  62. data/test/pipelining_commands_test.rb +4 -8
  63. data/test/publish_subscribe_test.rb +1 -3
  64. data/test/remote_server_control_commands_test.rb +60 -3
  65. data/test/scanning_test.rb +1 -7
  66. data/test/scripting_test.rb +1 -3
  67. data/test/sentinel_command_test.rb +1 -3
  68. data/test/sentinel_test.rb +1 -3
  69. data/test/sorting_test.rb +1 -3
  70. data/test/ssl_test.rb +45 -49
  71. data/test/support/connection/hiredis.rb +1 -1
  72. data/test/support/connection/ruby.rb +1 -1
  73. data/test/support/connection/synchrony.rb +1 -1
  74. data/test/synchrony_driver.rb +6 -9
  75. data/test/thread_safety_test.rb +1 -3
  76. data/test/transactions_test.rb +1 -3
  77. data/test/unknown_commands_test.rb +1 -3
  78. data/test/url_param_test.rb +44 -46
  79. metadata +30 -18
  80. data/Rakefile +0 -87
  81. data/test/connection_test.rb +0 -57
data/lib/redis/client.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "redis/errors"
1
+ require_relative "errors"
2
2
  require "socket"
3
3
  require "cgi"
4
4
 
@@ -21,9 +21,7 @@ class Redis
21
21
  :inherit_socket => false
22
22
  }
23
23
 
24
- def options
25
- Marshal.load(Marshal.dump(@options))
26
- end
24
+ attr_reader :options
27
25
 
28
26
  def scheme
29
27
  @options[:scheme]
@@ -340,6 +338,7 @@ class Redis
340
338
  Errno::EHOSTDOWN,
341
339
  Errno::EHOSTUNREACH,
342
340
  Errno::ENETUNREACH,
341
+ Errno::ENOENT,
343
342
  Errno::ETIMEDOUT
344
343
 
345
344
  raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})"
@@ -478,11 +477,16 @@ class Redis
478
477
 
479
478
  if driver.kind_of?(String)
480
479
  begin
481
- require "redis/connection/#{driver}"
482
- driver = Connection.const_get(driver.capitalize)
483
- rescue LoadError, NameError
484
- raise RuntimeError, "Cannot load driver #{driver.inspect}"
480
+ require_relative "connection/#{driver}"
481
+ rescue LoadError, NameError => e
482
+ begin
483
+ require "connection/#{driver}"
484
+ rescue LoadError, NameError => e
485
+ raise RuntimeError, "Cannot load driver #{driver.inspect}: #{e.message}"
486
+ end
485
487
  end
488
+
489
+ driver = Connection.const_get(driver.capitalize)
486
490
  end
487
491
 
488
492
  driver
@@ -1,4 +1,4 @@
1
- require "redis/connection/registry"
1
+ require_relative "connection/registry"
2
2
 
3
3
  # If a connection driver was required before this file, the array
4
4
  # Redis::Connection.drivers will contain one or more classes. The last driver
@@ -6,4 +6,4 @@ require "redis/connection/registry"
6
6
  # the plain Ruby driver as our default. Another driver can be required at a
7
7
  # later point in time, causing it to be the last element of the #drivers array
8
8
  # and therefore be chosen by default.
9
- require "redis/connection/ruby" if Redis::Connection.drivers.empty?
9
+ require_relative "connection/ruby" if Redis::Connection.drivers.empty?
@@ -30,14 +30,8 @@ class Redis
30
30
 
31
31
  protected
32
32
 
33
- if defined?(Encoding::default_external)
34
- def encode(string)
35
- string.force_encoding(Encoding::default_external)
36
- end
37
- else
38
- def encode(string)
39
- string
40
- end
33
+ def encode(string)
34
+ string.force_encoding(Encoding.default_external)
41
35
  end
42
36
  end
43
37
  end
@@ -1,5 +1,5 @@
1
- require "redis/connection/registry"
2
- require "redis/errors"
1
+ require_relative "registry"
2
+ require_relative "../errors"
3
3
  require "hiredis/connection"
4
4
  require "timeout"
5
5
 
@@ -1,6 +1,6 @@
1
- require "redis/connection/registry"
2
- require "redis/connection/command_helper"
3
- require "redis/errors"
1
+ require_relative "registry"
2
+ require_relative "command_helper"
3
+ require_relative "../errors"
4
4
  require "socket"
5
5
  require "timeout"
6
6
 
@@ -10,36 +10,17 @@ rescue LoadError
10
10
  # Not all systems have OpenSSL support
11
11
  end
12
12
 
13
- if RUBY_VERSION < "1.9.3"
14
- class String
15
- # Ruby 1.8.7 does not have byteslice, but it handles encodings differently anyway.
16
- # We can simply slice the string, which is a byte array there.
17
- def byteslice(*args)
18
- slice(*args)
19
- end
20
- end
21
- end
22
-
23
13
  class Redis
24
14
  module Connection
25
15
  module SocketMixin
26
16
 
27
17
  CRLF = "\r\n".freeze
28
18
 
29
- # Exceptions raised during non-blocking I/O ops that require retrying the op
30
- if RUBY_VERSION >= "1.9.3"
31
- NBIO_READ_EXCEPTIONS = [IO::WaitReadable]
32
- NBIO_WRITE_EXCEPTIONS = [IO::WaitWritable]
33
- else
34
- NBIO_READ_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN]
35
- NBIO_WRITE_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN]
36
- end
37
-
38
19
  def initialize(*args)
39
20
  super(*args)
40
21
 
41
22
  @timeout = @write_timeout = nil
42
- @buffer = ""
23
+ @buffer = "".dup
43
24
  end
44
25
 
45
26
  def timeout=(timeout)
@@ -83,13 +64,13 @@ class Redis
83
64
  begin
84
65
  read_nonblock(nbytes)
85
66
 
86
- rescue *NBIO_READ_EXCEPTIONS
67
+ rescue IO::WaitReadable
87
68
  if IO.select([self], nil, nil, @timeout)
88
69
  retry
89
70
  else
90
71
  raise Redis::TimeoutError
91
72
  end
92
- rescue *NBIO_WRITE_EXCEPTIONS
73
+ rescue IO::WaitWritable
93
74
  if IO.select(nil, [self], nil, @timeout)
94
75
  retry
95
76
  else
@@ -105,13 +86,13 @@ class Redis
105
86
  begin
106
87
  write_nonblock(data)
107
88
 
108
- rescue *NBIO_WRITE_EXCEPTIONS
89
+ rescue IO::WaitWritable
109
90
  if IO.select(nil, [self], nil, @write_timeout)
110
91
  retry
111
92
  else
112
93
  raise Redis::TimeoutError
113
94
  end
114
- rescue *NBIO_READ_EXCEPTIONS
95
+ rescue IO::WaitReadable
115
96
  if IO.select([self], nil, nil, @write_timeout)
116
97
  retry
117
98
  else
@@ -307,7 +288,6 @@ class Redis
307
288
  raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
308
289
  sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
309
290
  elsif config[:scheme] == "rediss" || config[:ssl]
310
- raise ArgumentError, "This library does not support SSL on Ruby < 1.9" if RUBY_VERSION < "1.9.3"
311
291
  sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
312
292
  else
313
293
  sock = TCPSocket.connect(config[:host], config[:port], config[:connect_timeout])
@@ -1,6 +1,6 @@
1
- require "redis/connection/command_helper"
2
- require "redis/connection/registry"
3
- require "redis/errors"
1
+ require_relative "command_helper"
2
+ require_relative "registry"
3
+ require_relative "../errors"
4
4
  require "em-synchrony"
5
5
  require "hiredis/reader"
6
6
 
@@ -72,7 +72,15 @@ class Redis
72
72
 
73
73
  def self.connect(config)
74
74
  if config[:scheme] == "unix"
75
- conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
75
+ begin
76
+ conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
77
+ rescue RuntimeError => e
78
+ if e.message == "no connection"
79
+ raise Errno::ECONNREFUSED
80
+ else
81
+ raise e
82
+ end
83
+ end
76
84
  elsif config[:scheme] == "rediss" || config[:ssl]
77
85
  raise NotImplementedError, "SSL not supported by synchrony driver"
78
86
  else
@@ -1,4 +1,4 @@
1
- require "redis/hash_ring"
1
+ require_relative "hash_ring"
2
2
 
3
3
  class Redis
4
4
  class Distributed
@@ -144,8 +144,8 @@ class Redis
144
144
  end
145
145
 
146
146
  # Create a key using the serialized value, previously obtained using DUMP.
147
- def restore(key, ttl, serialized_value)
148
- node_for(key).restore(key, ttl, serialized_value)
147
+ def restore(key, ttl, serialized_value, options = {})
148
+ node_for(key).restore(key, ttl, serialized_value, options)
149
149
  end
150
150
 
151
151
  # Transfer a key from the connected instance to another instance.
@@ -25,7 +25,6 @@ class Redis
25
25
  @nodes << node
26
26
  @replicas.times do |i|
27
27
  key = Zlib.crc32("#{node.id}:#{i}")
28
- raise "Node ID collision" if @ring.has_key?(key)
29
28
  @ring[key] = node
30
29
  @sorted_keys << key
31
30
  end
@@ -61,72 +60,29 @@ class Redis
61
60
  end
62
61
  end
63
62
 
64
- class << self
65
-
66
- # gem install RubyInline to use this code
67
- # Native extension to perform the binary search within the hashring.
68
- # There's a pure ruby version below so this is purely optional
69
- # for performance. In testing 20k gets and sets, the native
70
- # binary search shaved about 12% off the runtime (9sec -> 8sec).
71
- begin
72
- require 'inline'
73
- inline do |builder|
74
- builder.c <<-EOM
75
- int binary_search(VALUE ary, unsigned int r) {
76
- int upper = RARRAY_LEN(ary) - 1;
77
- int lower = 0;
78
- int idx = 0;
79
-
80
- while (lower <= upper) {
81
- idx = (lower + upper) / 2;
82
-
83
- VALUE continuumValue = RARRAY_PTR(ary)[idx];
84
- unsigned int l = NUM2UINT(continuumValue);
85
- if (l == r) {
86
- return idx;
87
- }
88
- else if (l > r) {
89
- upper = idx - 1;
90
- }
91
- else {
92
- lower = idx + 1;
93
- }
94
- }
95
- if (upper < 0) {
96
- upper = RARRAY_LEN(ary) - 1;
97
- }
98
- return upper;
99
- }
100
- EOM
101
- end
102
- rescue Exception
103
- # Find the closest index in HashRing with value <= the given value
104
- def binary_search(ary, value, &block)
105
- upper = ary.size - 1
106
- lower = 0
107
- idx = 0
108
-
109
- while(lower <= upper) do
110
- idx = (lower + upper) / 2
111
- comp = ary[idx] <=> value
112
-
113
- if comp == 0
114
- return idx
115
- elsif comp > 0
116
- upper = idx - 1
117
- else
118
- lower = idx + 1
119
- end
120
- end
121
-
122
- if upper < 0
123
- upper = ary.size - 1
124
- end
125
- return upper
63
+ # Find the closest index in HashRing with value <= the given value
64
+ def self.binary_search(ary, value, &block)
65
+ upper = ary.size - 1
66
+ lower = 0
67
+ idx = 0
68
+
69
+ while(lower <= upper) do
70
+ idx = (lower + upper) / 2
71
+ comp = ary[idx] <=> value
72
+
73
+ if comp == 0
74
+ return idx
75
+ elsif comp > 0
76
+ upper = idx - 1
77
+ else
78
+ lower = idx + 1
126
79
  end
80
+ end
127
81
 
82
+ if upper < 0
83
+ upper = ary.size - 1
128
84
  end
85
+ return upper
129
86
  end
130
-
131
87
  end
132
88
  end
@@ -1,10 +1,4 @@
1
1
  class Redis
2
- unless defined?(::BasicObject)
3
- class BasicObject
4
- instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A(__|instance_eval)/ }
5
- end
6
- end
7
-
8
2
  class Pipeline
9
3
  attr_accessor :db
10
4
 
data/lib/redis/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "3.3.5"
2
+ VERSION = "4.0.0"
3
3
  end
data/makefile ADDED
@@ -0,0 +1,42 @@
1
+ TEST_FILES := $(shell find test -name *_test.rb -type f)
2
+ REDIS_BRANCH := unstable
3
+ TMP := tmp
4
+ BUILD_DIR := ${TMP}/redis-${REDIS_BRANCH}
5
+ TARBALL := ${TMP}/redis-${REDIS_BRANCH}.tar.gz
6
+ BINARY := ${BUILD_DIR}/src/redis-server
7
+ PID_PATH := ${BUILD_DIR}/redis.pid
8
+ SOCKET_PATH := ${BUILD_DIR}/redis.sock
9
+ PORT := 6381
10
+
11
+ test: ${TEST_FILES}
12
+ make start
13
+ env SOCKET_PATH=${SOCKET_PATH} \
14
+ ruby -v $$(echo $? | tr ' ' '\n' | awk '{ print "-r./" $$0 }') -e ''
15
+ make stop
16
+
17
+ ${TMP}:
18
+ mkdir $@
19
+
20
+ ${TARBALL}: ${TMP}
21
+ wget https://github.com/antirez/redis/archive/${REDIS_BRANCH}.tar.gz -O $@
22
+
23
+ ${BINARY}: ${TARBALL} ${TMP}
24
+ rm -rf ${BUILD_DIR}
25
+ mkdir -p ${BUILD_DIR}
26
+ tar xf ${TARBALL} -C ${TMP}
27
+ cd ${BUILD_DIR} && make
28
+
29
+ stop:
30
+ (test -f ${PID_PATH} && (kill $$(cat ${PID_PATH}) || true) && rm -f ${PID_PATH}) || true
31
+
32
+ start: ${BINARY}
33
+ ${BINARY} \
34
+ --daemonize yes \
35
+ --pidfile ${PID_PATH} \
36
+ --port ${PORT} \
37
+ --unixsocket ${SOCKET_PATH}
38
+
39
+ clean:
40
+ (test -d ${BUILD_DIR} && cd ${BUILD_DIR}/src && make clean distclean) || true
41
+
42
+ .PHONY: test start stop
data/redis.gemspec CHANGED
@@ -1,8 +1,4 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
- $:.unshift File.expand_path("../lib", __FILE__)
4
-
5
- require "redis/version"
1
+ require "./lib/redis/version"
6
2
 
7
3
  Gem::Specification.new do |s|
8
4
  s.name = "redis"
@@ -15,8 +11,7 @@ Gem::Specification.new do |s|
15
11
 
16
12
  s.description = <<-EOS
17
13
  A Ruby client that tries to match Redis' API one-to-one, while still
18
- providing an idiomatic interface. It features thread-safety,
19
- client-side sharding, pipelining, and an obsession for performance.
14
+ providing an idiomatic interface.
20
15
  EOS
21
16
 
22
17
  s.license = "MIT"
@@ -39,6 +34,9 @@ Gem::Specification.new do |s|
39
34
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
40
35
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
41
36
 
42
- s.add_development_dependency("rake", "<11.0.0")
43
- s.add_development_dependency("test-unit", "3.1.5")
37
+ s.required_ruby_version = '>= 2.2.2'
38
+
39
+ s.add_development_dependency("test-unit", ">= 3.1.5")
40
+ s.add_development_dependency("hiredis")
41
+ s.add_development_dependency("em-synchrony")
44
42
  end
data/test/bitpos_test.rb CHANGED
@@ -1,10 +1,4 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("helper", File.dirname(__FILE__))
4
-
5
- unless defined?(Enumerator)
6
- Enumerator = Enumerable::Enumerator
7
- end
1
+ require_relative "helper"
8
2
 
9
3
  class TestBitpos < Test::Unit::TestCase
10
4
 
@@ -13,48 +7,48 @@ class TestBitpos < Test::Unit::TestCase
13
7
  def test_bitpos_empty_zero
14
8
  target_version "2.9.11" do
15
9
  r.del "foo"
16
- assert_equal 0, r.bitpos("foo", 0)
10
+ assert_equal(0, r.bitpos("foo", 0))
17
11
  end
18
12
  end
19
13
 
20
14
  def test_bitpos_empty_one
21
15
  target_version "2.9.11" do
22
16
  r.del "foo"
23
- assert_equal -1, r.bitpos("foo", 1)
17
+ assert_equal(-1, r.bitpos("foo", 1))
24
18
  end
25
19
  end
26
20
 
27
21
  def test_bitpos_zero
28
22
  target_version "2.9.11" do
29
23
  r.set "foo", "\xff\xf0\x00"
30
- assert_equal 12, r.bitpos("foo", 0)
24
+ assert_equal(12, r.bitpos("foo", 0))
31
25
  end
32
26
  end
33
27
 
34
28
  def test_bitpos_one
35
29
  target_version "2.9.11" do
36
30
  r.set "foo", "\x00\x0f\x00"
37
- assert_equal 12, r.bitpos("foo", 1)
31
+ assert_equal(12, r.bitpos("foo", 1))
38
32
  end
39
33
  end
40
34
 
41
35
  def test_bitpos_zero_end_is_given
42
36
  target_version "2.9.11" do
43
37
  r.set "foo", "\xff\xff\xff"
44
- assert_equal 24, r.bitpos("foo", 0)
45
- assert_equal 24, r.bitpos("foo", 0, 0)
46
- assert_equal -1, r.bitpos("foo", 0, 0, -1)
38
+ assert_equal(24, r.bitpos("foo", 0))
39
+ assert_equal(24, r.bitpos("foo", 0, 0))
40
+ assert_equal(-1, r.bitpos("foo", 0, 0, -1))
47
41
  end
48
42
  end
49
43
 
50
44
  def test_bitpos_one_intervals
51
45
  target_version "2.9.11" do
52
46
  r.set "foo", "\x00\xff\x00"
53
- assert_equal 8, r.bitpos("foo", 1, 0, -1)
54
- assert_equal 8, r.bitpos("foo", 1, 1, -1)
55
- assert_equal -1, r.bitpos("foo", 1, 2, -1)
56
- assert_equal -1, r.bitpos("foo", 1, 2, 200)
57
- assert_equal 8, r.bitpos("foo", 1, 1, 1)
47
+ assert_equal(8, r.bitpos("foo", 1, 0, -1))
48
+ assert_equal(8, r.bitpos("foo", 1, 1, -1))
49
+ assert_equal(-1, r.bitpos("foo", 1, 2, -1))
50
+ assert_equal(-1, r.bitpos("foo", 1, 2, 200))
51
+ assert_equal(8, r.bitpos("foo", 1, 1, 1))
58
52
  end
59
53
  end
60
54