redis2-namespaced 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
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?