redis2-namespaced 3.0.7

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 (99) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.order +170 -0
  4. data/.travis/Gemfile +11 -0
  5. data/.travis.yml +55 -0
  6. data/.yardopts +3 -0
  7. data/CHANGELOG.md +285 -0
  8. data/LICENSE +20 -0
  9. data/README.md +251 -0
  10. data/Rakefile +403 -0
  11. data/benchmarking/logging.rb +71 -0
  12. data/benchmarking/pipeline.rb +51 -0
  13. data/benchmarking/speed.rb +21 -0
  14. data/benchmarking/suite.rb +24 -0
  15. data/benchmarking/worker.rb +71 -0
  16. data/examples/basic.rb +15 -0
  17. data/examples/dist_redis.rb +43 -0
  18. data/examples/incr-decr.rb +17 -0
  19. data/examples/list.rb +26 -0
  20. data/examples/pubsub.rb +37 -0
  21. data/examples/sets.rb +36 -0
  22. data/examples/unicorn/config.ru +3 -0
  23. data/examples/unicorn/unicorn.rb +20 -0
  24. data/lib/redis2/client.rb +419 -0
  25. data/lib/redis2/connection/command_helper.rb +44 -0
  26. data/lib/redis2/connection/hiredis.rb +63 -0
  27. data/lib/redis2/connection/registry.rb +12 -0
  28. data/lib/redis2/connection/ruby.rb +322 -0
  29. data/lib/redis2/connection/synchrony.rb +124 -0
  30. data/lib/redis2/connection.rb +9 -0
  31. data/lib/redis2/distributed.rb +853 -0
  32. data/lib/redis2/errors.rb +40 -0
  33. data/lib/redis2/hash_ring.rb +131 -0
  34. data/lib/redis2/pipeline.rb +141 -0
  35. data/lib/redis2/subscribe.rb +83 -0
  36. data/lib/redis2/version.rb +3 -0
  37. data/lib/redis2.rb +2533 -0
  38. data/redis.gemspec +43 -0
  39. data/test/bitpos_test.rb +69 -0
  40. data/test/blocking_commands_test.rb +42 -0
  41. data/test/command_map_test.rb +30 -0
  42. data/test/commands_on_hashes_test.rb +21 -0
  43. data/test/commands_on_lists_test.rb +20 -0
  44. data/test/commands_on_sets_test.rb +77 -0
  45. data/test/commands_on_sorted_sets_test.rb +109 -0
  46. data/test/commands_on_strings_test.rb +101 -0
  47. data/test/commands_on_value_types_test.rb +131 -0
  48. data/test/connection_handling_test.rb +189 -0
  49. data/test/db/.gitkeep +0 -0
  50. data/test/distributed_blocking_commands_test.rb +46 -0
  51. data/test/distributed_commands_on_hashes_test.rb +10 -0
  52. data/test/distributed_commands_on_lists_test.rb +22 -0
  53. data/test/distributed_commands_on_sets_test.rb +83 -0
  54. data/test/distributed_commands_on_sorted_sets_test.rb +18 -0
  55. data/test/distributed_commands_on_strings_test.rb +59 -0
  56. data/test/distributed_commands_on_value_types_test.rb +95 -0
  57. data/test/distributed_commands_requiring_clustering_test.rb +164 -0
  58. data/test/distributed_connection_handling_test.rb +23 -0
  59. data/test/distributed_internals_test.rb +70 -0
  60. data/test/distributed_key_tags_test.rb +52 -0
  61. data/test/distributed_persistence_control_commands_test.rb +26 -0
  62. data/test/distributed_publish_subscribe_test.rb +92 -0
  63. data/test/distributed_remote_server_control_commands_test.rb +66 -0
  64. data/test/distributed_scripting_test.rb +102 -0
  65. data/test/distributed_sorting_test.rb +20 -0
  66. data/test/distributed_test.rb +58 -0
  67. data/test/distributed_transactions_test.rb +32 -0
  68. data/test/encoding_test.rb +18 -0
  69. data/test/error_replies_test.rb +59 -0
  70. data/test/helper.rb +218 -0
  71. data/test/helper_test.rb +24 -0
  72. data/test/internals_test.rb +410 -0
  73. data/test/lint/blocking_commands.rb +150 -0
  74. data/test/lint/hashes.rb +162 -0
  75. data/test/lint/lists.rb +143 -0
  76. data/test/lint/sets.rb +125 -0
  77. data/test/lint/sorted_sets.rb +238 -0
  78. data/test/lint/strings.rb +260 -0
  79. data/test/lint/value_types.rb +122 -0
  80. data/test/persistence_control_commands_test.rb +26 -0
  81. data/test/pipelining_commands_test.rb +242 -0
  82. data/test/publish_subscribe_test.rb +210 -0
  83. data/test/remote_server_control_commands_test.rb +117 -0
  84. data/test/scanning_test.rb +413 -0
  85. data/test/scripting_test.rb +78 -0
  86. data/test/sorting_test.rb +59 -0
  87. data/test/support/connection/hiredis.rb +1 -0
  88. data/test/support/connection/ruby.rb +1 -0
  89. data/test/support/connection/synchrony.rb +17 -0
  90. data/test/support/redis_mock.rb +115 -0
  91. data/test/support/wire/synchrony.rb +24 -0
  92. data/test/support/wire/thread.rb +5 -0
  93. data/test/synchrony_driver.rb +88 -0
  94. data/test/test.conf +9 -0
  95. data/test/thread_safety_test.rb +32 -0
  96. data/test/transactions_test.rb +264 -0
  97. data/test/unknown_commands_test.rb +14 -0
  98. data/test/url_param_test.rb +132 -0
  99. metadata +226 -0
@@ -0,0 +1,322 @@
1
+ require "redis2/connection/registry"
2
+ require "redis2/connection/command_helper"
3
+ require "redis2/errors"
4
+ require "socket"
5
+
6
+ class Redis2
7
+ module Connection
8
+ module SocketMixin
9
+
10
+ CRLF = "\r\n".freeze
11
+
12
+ def initialize(*args)
13
+ super(*args)
14
+
15
+ @timeout = nil
16
+ @buffer = ""
17
+ end
18
+
19
+ def timeout=(timeout)
20
+ if timeout && timeout > 0
21
+ @timeout = timeout
22
+ else
23
+ @timeout = nil
24
+ end
25
+ end
26
+
27
+ def read(nbytes)
28
+ result = @buffer.slice!(0, nbytes)
29
+
30
+ while result.bytesize < nbytes
31
+ result << _read_from_socket(nbytes - result.bytesize)
32
+ end
33
+
34
+ result
35
+ end
36
+
37
+ def gets
38
+ crlf = nil
39
+
40
+ while (crlf = @buffer.index(CRLF)) == nil
41
+ @buffer << _read_from_socket(1024)
42
+ end
43
+
44
+ @buffer.slice!(0, crlf + CRLF.bytesize)
45
+ end
46
+
47
+ def _read_from_socket(nbytes)
48
+ begin
49
+ read_nonblock(nbytes)
50
+
51
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
52
+ if IO.select([self], nil, nil, @timeout)
53
+ retry
54
+ else
55
+ raise Redis2::TimeoutError
56
+ end
57
+ end
58
+
59
+ rescue EOFError
60
+ raise Errno::ECONNRESET
61
+ end
62
+ end
63
+
64
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
65
+
66
+ require "timeout"
67
+
68
+ class TCPSocket < ::TCPSocket
69
+
70
+ include SocketMixin
71
+
72
+ def self.connect(host, port, timeout)
73
+ Timeout.timeout(timeout) do
74
+ sock = new(host, port)
75
+ sock
76
+ end
77
+ rescue Timeout::Error
78
+ raise TimeoutError
79
+ end
80
+ end
81
+
82
+ if defined?(::UNIXSocket)
83
+
84
+ class UNIXSocket < ::UNIXSocket
85
+
86
+ include SocketMixin
87
+
88
+ def self.connect(path, timeout)
89
+ Timeout.timeout(timeout) do
90
+ sock = new(path)
91
+ sock
92
+ end
93
+ rescue Timeout::Error
94
+ raise TimeoutError
95
+ end
96
+
97
+ # JRuby raises Errno::EAGAIN on #read_nonblock even when IO.select
98
+ # says it is readable (1.6.6, in both 1.8 and 1.9 mode).
99
+ # Use the blocking #readpartial method instead.
100
+
101
+ def _read_from_socket(nbytes)
102
+ readpartial(nbytes)
103
+
104
+ rescue EOFError
105
+ raise Errno::ECONNRESET
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+ else
112
+
113
+ class TCPSocket < ::Socket
114
+
115
+ include SocketMixin
116
+
117
+ def self.connect_addrinfo(ai, port, timeout)
118
+ sock = new(::Socket.const_get(ai[0]), Socket::SOCK_STREAM, 0)
119
+ sockaddr = ::Socket.pack_sockaddr_in(port, ai[3])
120
+
121
+ begin
122
+ sock.connect_nonblock(sockaddr)
123
+ rescue Errno::EINPROGRESS
124
+ if IO.select(nil, [sock], nil, timeout) == nil
125
+ raise TimeoutError
126
+ end
127
+
128
+ begin
129
+ sock.connect_nonblock(sockaddr)
130
+ rescue Errno::EISCONN
131
+ end
132
+ end
133
+
134
+ sock
135
+ end
136
+
137
+ def self.connect(host, port, timeout)
138
+ # Don't pass AI_ADDRCONFIG as flag to getaddrinfo(3)
139
+ #
140
+ # From the man page for getaddrinfo(3):
141
+ #
142
+ # If hints.ai_flags includes the AI_ADDRCONFIG flag, then IPv4
143
+ # addresses are returned in the list pointed to by res only if the
144
+ # local system has at least one IPv4 address configured, and IPv6
145
+ # addresses are returned only if the local system has at least one
146
+ # IPv6 address configured. The loopback address is not considered
147
+ # for this case as valid as a configured address.
148
+ #
149
+ # We do want the IPv6 loopback address to be returned if applicable,
150
+ # even if it is the only configured IPv6 address on the machine.
151
+ # Also see: https://github.com/redis/redis-rb/pull/394.
152
+ addrinfo = ::Socket.getaddrinfo(host, nil, Socket::AF_UNSPEC, Socket::SOCK_STREAM)
153
+
154
+ # From the man page for getaddrinfo(3):
155
+ #
156
+ # Normally, the application should try using the addresses in the
157
+ # order in which they are returned. The sorting function used
158
+ # within getaddrinfo() is defined in RFC 3484 [...].
159
+ #
160
+ addrinfo.each_with_index do |ai, i|
161
+ begin
162
+ return connect_addrinfo(ai, port, timeout)
163
+ rescue SystemCallError
164
+ # Raise if this was our last attempt.
165
+ raise if addrinfo.length == i+1
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ class UNIXSocket < ::Socket
172
+
173
+ include SocketMixin
174
+
175
+ def self.connect(path, timeout)
176
+ sock = new(::Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
177
+ sockaddr = ::Socket.pack_sockaddr_un(path)
178
+
179
+ begin
180
+ sock.connect_nonblock(sockaddr)
181
+ rescue Errno::EINPROGRESS
182
+ if IO.select(nil, [sock], nil, timeout) == nil
183
+ raise TimeoutError
184
+ end
185
+
186
+ begin
187
+ sock.connect_nonblock(sockaddr)
188
+ rescue Errno::EISCONN
189
+ end
190
+ end
191
+
192
+ sock
193
+ end
194
+ end
195
+
196
+ end
197
+
198
+ class Ruby
199
+ include Redis2::Connection::CommandHelper
200
+
201
+ MINUS = "-".freeze
202
+ PLUS = "+".freeze
203
+ COLON = ":".freeze
204
+ DOLLAR = "$".freeze
205
+ ASTERISK = "*".freeze
206
+
207
+ def self.connect(config)
208
+ if config[:scheme] == "unix"
209
+ sock = UNIXSocket.connect(config[:path], config[:timeout])
210
+ else
211
+ sock = TCPSocket.connect(config[:host], config[:port], config[:timeout])
212
+ end
213
+
214
+ instance = new(sock)
215
+ instance.timeout = config[:timeout]
216
+ instance.set_tcp_keepalive config[:tcp_keepalive]
217
+ instance
218
+ end
219
+
220
+ if [:SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all?{|c| Socket.const_defined? c}
221
+ def set_tcp_keepalive(keepalive)
222
+ return unless keepalive.is_a?(Hash)
223
+
224
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
225
+ @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, keepalive[:time])
226
+ @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, keepalive[:intvl])
227
+ @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, keepalive[:probes])
228
+ end
229
+
230
+ def get_tcp_keepalive
231
+ {
232
+ :time => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
233
+ :intvl => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
234
+ :probes => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int,
235
+ }
236
+ end
237
+ else
238
+ def set_tcp_keepalive(keepalive)
239
+ end
240
+
241
+ def get_tcp_keepalive
242
+ {
243
+ }
244
+ end
245
+ end
246
+
247
+ def initialize(sock)
248
+ @sock = sock
249
+ end
250
+
251
+ def connected?
252
+ !! @sock
253
+ end
254
+
255
+ def disconnect
256
+ @sock.close
257
+ rescue
258
+ ensure
259
+ @sock = nil
260
+ end
261
+
262
+ def timeout=(timeout)
263
+ if @sock.respond_to?(:timeout=)
264
+ @sock.timeout = timeout
265
+ end
266
+ end
267
+
268
+ def write(command)
269
+ @sock.write(build_command(command))
270
+ end
271
+
272
+ def read
273
+ line = @sock.gets
274
+ reply_type = line.slice!(0, 1)
275
+ format_reply(reply_type, line)
276
+
277
+ rescue Errno::EAGAIN
278
+ raise TimeoutError
279
+ end
280
+
281
+ def format_reply(reply_type, line)
282
+ case reply_type
283
+ when MINUS then format_error_reply(line)
284
+ when PLUS then format_status_reply(line)
285
+ when COLON then format_integer_reply(line)
286
+ when DOLLAR then format_bulk_reply(line)
287
+ when ASTERISK then format_multi_bulk_reply(line)
288
+ else raise ProtocolError.new(reply_type)
289
+ end
290
+ end
291
+
292
+ def format_error_reply(line)
293
+ CommandError.new(line.strip)
294
+ end
295
+
296
+ def format_status_reply(line)
297
+ line.strip
298
+ end
299
+
300
+ def format_integer_reply(line)
301
+ line.to_i
302
+ end
303
+
304
+ def format_bulk_reply(line)
305
+ bulklen = line.to_i
306
+ return if bulklen == -1
307
+ reply = encode(@sock.read(bulklen))
308
+ @sock.read(2) # Discard CRLF.
309
+ reply
310
+ end
311
+
312
+ def format_multi_bulk_reply(line)
313
+ n = line.to_i
314
+ return if n == -1
315
+
316
+ Array.new(n) { read }
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ Redis2::Connection.drivers << Redis2::Connection::Ruby
@@ -0,0 +1,124 @@
1
+ require "redis2/connection/command_helper"
2
+ require "redis2/connection/registry"
3
+ require "redis2/errors"
4
+ require "em-synchrony"
5
+ require "hiredis/reader"
6
+
7
+ class Redis2
8
+ module Connection
9
+ class Redis2Client < EventMachine::Connection
10
+ include EventMachine::Deferrable
11
+
12
+ def post_init
13
+ @req = nil
14
+ @connected = false
15
+ @reader = ::Hiredis::Reader.new
16
+ end
17
+
18
+ def connection_completed
19
+ @connected = true
20
+ succeed
21
+ end
22
+
23
+ def connected?
24
+ @connected
25
+ end
26
+
27
+ def receive_data(data)
28
+ @reader.feed(data)
29
+
30
+ loop do
31
+ begin
32
+ reply = @reader.gets
33
+ rescue RuntimeError => err
34
+ @req.fail [:error, ProtocolError.new(err.message)]
35
+ break
36
+ end
37
+
38
+ break if reply == false
39
+
40
+ reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
41
+ @req.succeed [:reply, reply]
42
+ end
43
+ end
44
+
45
+ def read
46
+ @req = EventMachine::DefaultDeferrable.new
47
+ EventMachine::Synchrony.sync @req
48
+ end
49
+
50
+ def send(data)
51
+ callback { send_data data }
52
+ end
53
+
54
+ def unbind
55
+ @connected = false
56
+ if @req
57
+ @req.fail [:error, Errno::ECONNRESET]
58
+ @req = nil
59
+ else
60
+ fail
61
+ end
62
+ end
63
+ end
64
+
65
+ class Synchrony
66
+ include Redis2::Connection::CommandHelper
67
+
68
+ def self.connect(config)
69
+ if config[:scheme] == "unix"
70
+ conn = EventMachine.connect_unix_domain(config[:path], Redis2Client)
71
+ else
72
+ conn = EventMachine.connect(config[:host], config[:port], Redis2Client) do |c|
73
+ c.pending_connect_timeout = [config[:timeout], 0.1].max
74
+ end
75
+ end
76
+
77
+ fiber = Fiber.current
78
+ conn.callback { fiber.resume }
79
+ conn.errback { fiber.resume :refused }
80
+
81
+ raise Errno::ECONNREFUSED if Fiber.yield == :refused
82
+
83
+ instance = new(conn)
84
+ instance.timeout = config[:timeout]
85
+ instance
86
+ end
87
+
88
+ def initialize(connection)
89
+ @connection = connection
90
+ end
91
+
92
+ def connected?
93
+ @connection && @connection.connected?
94
+ end
95
+
96
+ def timeout=(timeout)
97
+ @timeout = timeout
98
+ end
99
+
100
+ def disconnect
101
+ @connection.close_connection
102
+ @connection = nil
103
+ end
104
+
105
+ def write(command)
106
+ @connection.send(build_command(command))
107
+ end
108
+
109
+ def read
110
+ type, payload = @connection.read
111
+
112
+ if type == :reply
113
+ payload
114
+ elsif type == :error
115
+ raise payload
116
+ else
117
+ raise "Unknown type #{type.inspect}"
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ Redis2::Connection.drivers << Redis2::Connection::Synchrony
@@ -0,0 +1,9 @@
1
+ require "redis2/connection/registry"
2
+
3
+ # If a connection driver was required before this file, the array
4
+ # Redis2::Connection.drivers will contain one or more classes. The last driver
5
+ # in this array will be used as default driver. If this array is empty, we load
6
+ # the plain Ruby driver as our default. Another driver can be required at a
7
+ # later point in time, causing it to be the last element of the #drivers array
8
+ # and therefore be chosen by default.
9
+ require "redis2/connection/ruby" if Redis2::Connection.drivers.empty?