discourse-redis 3.2.2

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 (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