discourse-redis 3.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +59 -0
  4. data/.travis/Gemfile +11 -0
  5. data/.yardopts +3 -0
  6. data/CHANGELOG.md +349 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +20 -0
  9. data/README.md +328 -0
  10. data/Rakefile +87 -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/consistency.rb +114 -0
  18. data/examples/dist_redis.rb +43 -0
  19. data/examples/incr-decr.rb +17 -0
  20. data/examples/list.rb +26 -0
  21. data/examples/pubsub.rb +37 -0
  22. data/examples/sentinel.rb +41 -0
  23. data/examples/sentinel/start +49 -0
  24. data/examples/sets.rb +36 -0
  25. data/examples/unicorn/config.ru +3 -0
  26. data/examples/unicorn/unicorn.rb +20 -0
  27. data/lib/redis.rb +2731 -0
  28. data/lib/redis/client.rb +575 -0
  29. data/lib/redis/connection.rb +9 -0
  30. data/lib/redis/connection/command_helper.rb +44 -0
  31. data/lib/redis/connection/hiredis.rb +64 -0
  32. data/lib/redis/connection/registry.rb +12 -0
  33. data/lib/redis/connection/ruby.rb +322 -0
  34. data/lib/redis/connection/synchrony.rb +124 -0
  35. data/lib/redis/distributed.rb +873 -0
  36. data/lib/redis/errors.rb +40 -0
  37. data/lib/redis/hash_ring.rb +132 -0
  38. data/lib/redis/pipeline.rb +141 -0
  39. data/lib/redis/subscribe.rb +83 -0
  40. data/lib/redis/version.rb +3 -0
  41. data/redis.gemspec +34 -0
  42. data/test/bitpos_test.rb +69 -0
  43. data/test/blocking_commands_test.rb +42 -0
  44. data/test/command_map_test.rb +30 -0
  45. data/test/commands_on_hashes_test.rb +21 -0
  46. data/test/commands_on_hyper_log_log_test.rb +21 -0
  47. data/test/commands_on_lists_test.rb +20 -0
  48. data/test/commands_on_sets_test.rb +77 -0
  49. data/test/commands_on_sorted_sets_test.rb +137 -0
  50. data/test/commands_on_strings_test.rb +101 -0
  51. data/test/commands_on_value_types_test.rb +133 -0
  52. data/test/connection_handling_test.rb +250 -0
  53. data/test/distributed_blocking_commands_test.rb +46 -0
  54. data/test/distributed_commands_on_hashes_test.rb +10 -0
  55. data/test/distributed_commands_on_hyper_log_log_test.rb +33 -0
  56. data/test/distributed_commands_on_lists_test.rb +22 -0
  57. data/test/distributed_commands_on_sets_test.rb +83 -0
  58. data/test/distributed_commands_on_sorted_sets_test.rb +18 -0
  59. data/test/distributed_commands_on_strings_test.rb +59 -0
  60. data/test/distributed_commands_on_value_types_test.rb +95 -0
  61. data/test/distributed_commands_requiring_clustering_test.rb +164 -0
  62. data/test/distributed_connection_handling_test.rb +23 -0
  63. data/test/distributed_internals_test.rb +79 -0
  64. data/test/distributed_key_tags_test.rb +52 -0
  65. data/test/distributed_persistence_control_commands_test.rb +26 -0
  66. data/test/distributed_publish_subscribe_test.rb +92 -0
  67. data/test/distributed_remote_server_control_commands_test.rb +66 -0
  68. data/test/distributed_scripting_test.rb +102 -0
  69. data/test/distributed_sorting_test.rb +20 -0
  70. data/test/distributed_test.rb +58 -0
  71. data/test/distributed_transactions_test.rb +32 -0
  72. data/test/encoding_test.rb +18 -0
  73. data/test/error_replies_test.rb +59 -0
  74. data/test/fork_safety_test.rb +65 -0
  75. data/test/helper.rb +232 -0
  76. data/test/helper_test.rb +24 -0
  77. data/test/internals_test.rb +437 -0
  78. data/test/lint/blocking_commands.rb +150 -0
  79. data/test/lint/hashes.rb +162 -0
  80. data/test/lint/hyper_log_log.rb +60 -0
  81. data/test/lint/lists.rb +143 -0
  82. data/test/lint/sets.rb +125 -0
  83. data/test/lint/sorted_sets.rb +316 -0
  84. data/test/lint/strings.rb +260 -0
  85. data/test/lint/value_types.rb +122 -0
  86. data/test/persistence_control_commands_test.rb +26 -0
  87. data/test/pipelining_commands_test.rb +242 -0
  88. data/test/publish_subscribe_test.rb +254 -0
  89. data/test/remote_server_control_commands_test.rb +118 -0
  90. data/test/scanning_test.rb +413 -0
  91. data/test/scripting_test.rb +78 -0
  92. data/test/sentinel_command_test.rb +80 -0
  93. data/test/sentinel_test.rb +255 -0
  94. data/test/sorting_test.rb +59 -0
  95. data/test/support/connection/hiredis.rb +1 -0
  96. data/test/support/connection/ruby.rb +1 -0
  97. data/test/support/connection/synchrony.rb +17 -0
  98. data/test/support/redis_mock.rb +119 -0
  99. data/test/support/wire/synchrony.rb +24 -0
  100. data/test/support/wire/thread.rb +5 -0
  101. data/test/synchrony_driver.rb +88 -0
  102. data/test/test.conf.erb +9 -0
  103. data/test/thread_safety_test.rb +32 -0
  104. data/test/transactions_test.rb +264 -0
  105. data/test/unknown_commands_test.rb +14 -0
  106. data/test/url_param_test.rb +138 -0
  107. metadata +182 -0
@@ -0,0 +1,9 @@
1
+ require "redis/connection/registry"
2
+
3
+ # If a connection driver was required before this file, the array
4
+ # Redis::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 "redis/connection/ruby" if Redis::Connection.drivers.empty?
@@ -0,0 +1,44 @@
1
+ class Redis
2
+ module Connection
3
+ module CommandHelper
4
+
5
+ COMMAND_DELIMITER = "\r\n"
6
+
7
+ def build_command(args)
8
+ command = [nil]
9
+
10
+ args.each do |i|
11
+ if i.is_a? Array
12
+ i.each do |j|
13
+ j = j.to_s
14
+ command << "$#{j.bytesize}"
15
+ command << j
16
+ end
17
+ else
18
+ i = i.to_s
19
+ command << "$#{i.bytesize}"
20
+ command << i
21
+ end
22
+ end
23
+
24
+ command[0] = "*#{(command.length - 1) / 2}"
25
+
26
+ # Trailing delimiter
27
+ command << ""
28
+ command.join(COMMAND_DELIMITER)
29
+ end
30
+
31
+ protected
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
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,64 @@
1
+ require "redis/connection/registry"
2
+ require "redis/errors"
3
+ require "hiredis/connection"
4
+ require "timeout"
5
+
6
+ class Redis
7
+ module Connection
8
+ class Hiredis
9
+
10
+ def self.connect(config)
11
+ connection = ::Hiredis::Connection.new
12
+ connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
13
+
14
+ if config[:scheme] == "unix"
15
+ connection.connect_unix(config[:path], connect_timeout)
16
+ else
17
+ connection.connect(config[:host], config[:port], connect_timeout)
18
+ end
19
+
20
+ instance = new(connection)
21
+ instance.timeout = config[:timeout]
22
+ instance
23
+ rescue Errno::ETIMEDOUT
24
+ raise TimeoutError
25
+ end
26
+
27
+ def initialize(connection)
28
+ @connection = connection
29
+ end
30
+
31
+ def connected?
32
+ @connection && @connection.connected?
33
+ end
34
+
35
+ def timeout=(timeout)
36
+ # Hiredis works with microsecond timeouts
37
+ @connection.timeout = Integer(timeout * 1_000_000)
38
+ end
39
+
40
+ def disconnect
41
+ @connection.disconnect
42
+ @connection = nil
43
+ end
44
+
45
+ def write(command)
46
+ @connection.write(command.flatten(1))
47
+ rescue Errno::EAGAIN
48
+ raise TimeoutError
49
+ end
50
+
51
+ def read
52
+ reply = @connection.read
53
+ reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
54
+ reply
55
+ rescue Errno::EAGAIN
56
+ raise TimeoutError
57
+ rescue RuntimeError => err
58
+ raise ProtocolError.new(err.message)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ Redis::Connection.drivers << Redis::Connection::Hiredis
@@ -0,0 +1,12 @@
1
+ class Redis
2
+ module Connection
3
+
4
+ # Store a list of loaded connection drivers in the Connection module.
5
+ # Redis::Client uses the last required driver by default, and will be aware
6
+ # of the loaded connection drivers if the user chooses to override the
7
+ # default connection driver.
8
+ def self.drivers
9
+ @drivers ||= []
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,322 @@
1
+ require "redis/connection/registry"
2
+ require "redis/connection/command_helper"
3
+ require "redis/errors"
4
+ require "socket"
5
+
6
+ class Redis
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 Redis::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 Redis::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[:connect_timeout])
210
+ else
211
+ sock = TCPSocket.connect(config[:host], config[:port], config[:connect_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
+ Redis::Connection.drivers << Redis::Connection::Ruby