gorsuch-redis 3.0.0.rc1
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.
- data/.gitignore +10 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +113 -0
- data/LICENSE +20 -0
- data/README.md +214 -0
- data/Rakefile +260 -0
- data/TODO.md +4 -0
- data/benchmarking/logging.rb +62 -0
- data/benchmarking/pipeline.rb +51 -0
- data/benchmarking/speed.rb +21 -0
- data/benchmarking/suite.rb +24 -0
- data/benchmarking/thread_safety.rb +38 -0
- data/benchmarking/worker.rb +71 -0
- data/examples/basic.rb +15 -0
- data/examples/dist_redis.rb +43 -0
- data/examples/incr-decr.rb +17 -0
- data/examples/list.rb +26 -0
- data/examples/pubsub.rb +31 -0
- data/examples/sets.rb +36 -0
- data/examples/unicorn/config.ru +3 -0
- data/examples/unicorn/unicorn.rb +20 -0
- data/lib/redis/client.rb +303 -0
- data/lib/redis/connection/command_helper.rb +44 -0
- data/lib/redis/connection/hiredis.rb +52 -0
- data/lib/redis/connection/registry.rb +12 -0
- data/lib/redis/connection/ruby.rb +136 -0
- data/lib/redis/connection/synchrony.rb +131 -0
- data/lib/redis/connection.rb +9 -0
- data/lib/redis/distributed.rb +696 -0
- data/lib/redis/errors.rb +38 -0
- data/lib/redis/hash_ring.rb +131 -0
- data/lib/redis/pipeline.rb +106 -0
- data/lib/redis/subscribe.rb +79 -0
- data/lib/redis/version.rb +3 -0
- data/lib/redis.rb +1724 -0
- data/redis.gemspec +43 -0
- data/test/command_map_test.rb +29 -0
- data/test/commands_on_hashes_test.rb +20 -0
- data/test/commands_on_lists_test.rb +60 -0
- data/test/commands_on_sets_test.rb +76 -0
- data/test/commands_on_sorted_sets_test.rb +108 -0
- data/test/commands_on_strings_test.rb +80 -0
- data/test/commands_on_value_types_test.rb +87 -0
- data/test/connection_handling_test.rb +204 -0
- data/test/db/.gitignore +1 -0
- data/test/distributed_blocking_commands_test.rb +53 -0
- data/test/distributed_commands_on_hashes_test.rb +11 -0
- data/test/distributed_commands_on_lists_test.rb +23 -0
- data/test/distributed_commands_on_sets_test.rb +84 -0
- data/test/distributed_commands_on_sorted_sets_test.rb +19 -0
- data/test/distributed_commands_on_strings_test.rb +49 -0
- data/test/distributed_commands_on_value_types_test.rb +72 -0
- data/test/distributed_commands_requiring_clustering_test.rb +148 -0
- data/test/distributed_connection_handling_test.rb +24 -0
- data/test/distributed_internals_test.rb +27 -0
- data/test/distributed_key_tags_test.rb +52 -0
- data/test/distributed_persistence_control_commands_test.rb +23 -0
- data/test/distributed_publish_subscribe_test.rb +100 -0
- data/test/distributed_remote_server_control_commands_test.rb +42 -0
- data/test/distributed_sorting_test.rb +21 -0
- data/test/distributed_test.rb +59 -0
- data/test/distributed_transactions_test.rb +33 -0
- data/test/encoding_test.rb +15 -0
- data/test/error_replies_test.rb +53 -0
- data/test/helper.rb +155 -0
- data/test/helper_test.rb +8 -0
- data/test/internals_test.rb +152 -0
- data/test/lint/hashes.rb +140 -0
- data/test/lint/internals.rb +36 -0
- data/test/lint/lists.rb +107 -0
- data/test/lint/sets.rb +90 -0
- data/test/lint/sorted_sets.rb +196 -0
- data/test/lint/strings.rb +133 -0
- data/test/lint/value_types.rb +81 -0
- data/test/persistence_control_commands_test.rb +21 -0
- data/test/pipelining_commands_test.rb +186 -0
- data/test/publish_subscribe_test.rb +158 -0
- data/test/redis_mock.rb +89 -0
- data/test/remote_server_control_commands_test.rb +88 -0
- data/test/sorting_test.rb +43 -0
- data/test/synchrony_driver.rb +57 -0
- data/test/test.conf +9 -0
- data/test/thread_safety_test.rb +30 -0
- data/test/transactions_test.rb +173 -0
- data/test/unknown_commands_test.rb +13 -0
- data/test/url_param_test.rb +59 -0
- metadata +236 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
require "redis"
|
2
|
+
|
3
|
+
worker_processes 3
|
4
|
+
|
5
|
+
# If you set the connection to Redis *before* forking,
|
6
|
+
# you will cause forks to share a file descriptor.
|
7
|
+
#
|
8
|
+
# This causes a concurrency problem by which one fork
|
9
|
+
# can read or write to the socket while others are
|
10
|
+
# performing other operations.
|
11
|
+
#
|
12
|
+
# Most likely you'll be getting ProtocolError exceptions
|
13
|
+
# mentioning a wrong initial byte in the reply.
|
14
|
+
#
|
15
|
+
# Thus we need to connect to Redis after forking the
|
16
|
+
# worker processes.
|
17
|
+
|
18
|
+
after_fork do |server, worker|
|
19
|
+
Redis.current.quit
|
20
|
+
end
|
data/lib/redis/client.rb
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
require "redis/errors"
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class Client
|
5
|
+
attr_accessor :uri, :db, :logger
|
6
|
+
attr :timeout
|
7
|
+
attr :connection
|
8
|
+
attr :command_map
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
@uri = options[:uri]
|
12
|
+
|
13
|
+
if scheme == 'unix'
|
14
|
+
@db = 0
|
15
|
+
else
|
16
|
+
@db = uri.path[1..-1].to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
@timeout = (options[:timeout] || 5).to_f
|
20
|
+
@logger = options[:logger]
|
21
|
+
@reconnect = true
|
22
|
+
@connection = Connection.drivers.last.new
|
23
|
+
@command_map = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def connect
|
27
|
+
establish_connection
|
28
|
+
call [:auth, password] if password
|
29
|
+
call [:select, @db] if @db != 0
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def id
|
34
|
+
safe_uri
|
35
|
+
end
|
36
|
+
|
37
|
+
def safe_uri
|
38
|
+
temp_uri = @uri
|
39
|
+
temp_uri.user = nil
|
40
|
+
temp_uri.password = nil
|
41
|
+
temp_uri
|
42
|
+
end
|
43
|
+
|
44
|
+
def host
|
45
|
+
@uri.host
|
46
|
+
end
|
47
|
+
|
48
|
+
def password
|
49
|
+
@uri.password
|
50
|
+
end
|
51
|
+
|
52
|
+
def path
|
53
|
+
@uri.path
|
54
|
+
end
|
55
|
+
|
56
|
+
def port
|
57
|
+
@uri.port
|
58
|
+
end
|
59
|
+
|
60
|
+
def scheme
|
61
|
+
@uri.scheme
|
62
|
+
end
|
63
|
+
|
64
|
+
def call(command, &block)
|
65
|
+
reply = process([command]) { read }
|
66
|
+
raise reply if reply.is_a?(CommandError)
|
67
|
+
|
68
|
+
if block
|
69
|
+
block.call(reply)
|
70
|
+
else
|
71
|
+
reply
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def call_loop(command)
|
76
|
+
error = nil
|
77
|
+
|
78
|
+
result = without_socket_timeout do
|
79
|
+
process([command]) do
|
80
|
+
loop do
|
81
|
+
reply = read
|
82
|
+
if reply.is_a?(CommandError)
|
83
|
+
error = reply
|
84
|
+
break
|
85
|
+
else
|
86
|
+
yield reply
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Raise error when previous block broke out of the loop.
|
93
|
+
raise error if error
|
94
|
+
|
95
|
+
# Result is set to the value that the provided block used to break.
|
96
|
+
result
|
97
|
+
end
|
98
|
+
|
99
|
+
def call_pipeline(pipeline)
|
100
|
+
without_reconnect_wrapper = lambda do |&blk| blk.call end
|
101
|
+
without_reconnect_wrapper = lambda do |&blk|
|
102
|
+
without_reconnect(&blk)
|
103
|
+
end if pipeline.without_reconnect?
|
104
|
+
|
105
|
+
shutdown_wrapper = lambda do |&blk| blk.call end
|
106
|
+
shutdown_wrapper = lambda do |&blk|
|
107
|
+
begin
|
108
|
+
blk.call
|
109
|
+
rescue ConnectionError
|
110
|
+
# Assume the pipeline was sent in one piece, but execution of
|
111
|
+
# SHUTDOWN caused none of the replies for commands that were executed
|
112
|
+
# prior to it from coming back around.
|
113
|
+
nil
|
114
|
+
end
|
115
|
+
end if pipeline.shutdown?
|
116
|
+
|
117
|
+
without_reconnect_wrapper.call do
|
118
|
+
shutdown_wrapper.call do
|
119
|
+
pipeline.finish(call_pipelined(pipeline.commands))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def call_pipelined(commands)
|
125
|
+
return [] if commands.empty?
|
126
|
+
|
127
|
+
# The method #ensure_connected (called from #process) reconnects once on
|
128
|
+
# I/O errors. To make an effort in making sure that commands are not
|
129
|
+
# executed more than once, only allow reconnection before the first reply
|
130
|
+
# has been read. When an error occurs after the first reply has been
|
131
|
+
# read, retrying would re-execute the entire pipeline, thus re-issuing
|
132
|
+
# already successfully executed commands. To circumvent this, don't retry
|
133
|
+
# after the first reply has been read successfully.
|
134
|
+
|
135
|
+
result = Array.new(commands.size)
|
136
|
+
reconnect = @reconnect
|
137
|
+
|
138
|
+
begin
|
139
|
+
process(commands) do
|
140
|
+
result[0] = read
|
141
|
+
|
142
|
+
@reconnect = false
|
143
|
+
|
144
|
+
(commands.size - 1).times do |i|
|
145
|
+
result[i + 1] = read
|
146
|
+
end
|
147
|
+
end
|
148
|
+
ensure
|
149
|
+
@reconnect = reconnect
|
150
|
+
end
|
151
|
+
|
152
|
+
result
|
153
|
+
end
|
154
|
+
|
155
|
+
def call_without_timeout(command, &blk)
|
156
|
+
without_socket_timeout do
|
157
|
+
call(command, &blk)
|
158
|
+
end
|
159
|
+
rescue ConnectionError
|
160
|
+
retry
|
161
|
+
end
|
162
|
+
|
163
|
+
def process(commands)
|
164
|
+
logging(commands) do
|
165
|
+
ensure_connected do
|
166
|
+
commands.each do |command|
|
167
|
+
if command_map[command.first]
|
168
|
+
command = command.dup
|
169
|
+
command[0] = command_map[command.first]
|
170
|
+
end
|
171
|
+
|
172
|
+
connection.write(command)
|
173
|
+
end
|
174
|
+
|
175
|
+
yield if block_given?
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def connected?
|
181
|
+
connection.connected?
|
182
|
+
end
|
183
|
+
|
184
|
+
def disconnect
|
185
|
+
connection.disconnect if connection.connected?
|
186
|
+
end
|
187
|
+
|
188
|
+
def reconnect
|
189
|
+
disconnect
|
190
|
+
connect
|
191
|
+
end
|
192
|
+
|
193
|
+
def io
|
194
|
+
yield
|
195
|
+
rescue Errno::EAGAIN
|
196
|
+
raise TimeoutError, "Connection timed out"
|
197
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL => e
|
198
|
+
raise ConnectionError, "Connection lost (%s)" % [e.class.name.split("::").last]
|
199
|
+
end
|
200
|
+
|
201
|
+
def read
|
202
|
+
io do
|
203
|
+
connection.read
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def write(command)
|
208
|
+
io do
|
209
|
+
connection.write(command)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def without_socket_timeout
|
214
|
+
connect unless connected?
|
215
|
+
|
216
|
+
begin
|
217
|
+
self.timeout = 0
|
218
|
+
yield
|
219
|
+
ensure
|
220
|
+
self.timeout = @timeout if connected?
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def without_reconnect
|
225
|
+
begin
|
226
|
+
original, @reconnect = @reconnect, false
|
227
|
+
yield
|
228
|
+
ensure
|
229
|
+
@reconnect = original
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
protected
|
234
|
+
|
235
|
+
def deprecated(old, new = nil, trace = caller[0])
|
236
|
+
message = "The method #{old} is deprecated and will be removed in 2.0"
|
237
|
+
message << " - use #{new} instead" if new
|
238
|
+
Redis.deprecate(message, trace)
|
239
|
+
end
|
240
|
+
|
241
|
+
def logging(commands)
|
242
|
+
return yield unless @logger && @logger.debug?
|
243
|
+
|
244
|
+
begin
|
245
|
+
commands.each do |name, *args|
|
246
|
+
@logger.debug("Redis >> #{name.to_s.upcase} #{args.join(" ")}")
|
247
|
+
end
|
248
|
+
|
249
|
+
t1 = Time.now
|
250
|
+
yield
|
251
|
+
ensure
|
252
|
+
@logger.debug("Redis >> %0.2fms" % ((Time.now - t1) * 1000))
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def establish_connection
|
257
|
+
# Need timeout in usecs, like socket timeout.
|
258
|
+
timeout = Integer(@timeout * 1_000_000)
|
259
|
+
|
260
|
+
if @uri.scheme == 'unix'
|
261
|
+
connection.connect_unix(@uri.path, timeout)
|
262
|
+
else
|
263
|
+
connection.connect(@uri, timeout)
|
264
|
+
end
|
265
|
+
|
266
|
+
# If the timeout is set we set the low level socket options in order
|
267
|
+
# to make sure a blocking read will return after the specified number
|
268
|
+
# of seconds. This hack is from memcached ruby client.
|
269
|
+
self.timeout = @timeout
|
270
|
+
|
271
|
+
rescue Timeout::Error
|
272
|
+
raise CannotConnectError, "Timed out connecting to Redis on #{safe_uri}"
|
273
|
+
rescue Errno::ECONNREFUSED
|
274
|
+
raise CannotConnectError, "Error connecting to Redis on #{safe_uri} (ECONNREFUSED)"
|
275
|
+
end
|
276
|
+
|
277
|
+
def timeout=(timeout)
|
278
|
+
connection.timeout = Integer(timeout * 1_000_000)
|
279
|
+
end
|
280
|
+
|
281
|
+
def ensure_connected
|
282
|
+
tries = 0
|
283
|
+
|
284
|
+
begin
|
285
|
+
connect unless connected?
|
286
|
+
tries += 1
|
287
|
+
|
288
|
+
yield
|
289
|
+
rescue ConnectionError
|
290
|
+
disconnect
|
291
|
+
|
292
|
+
if tries < 2 && @reconnect
|
293
|
+
retry
|
294
|
+
else
|
295
|
+
raise
|
296
|
+
end
|
297
|
+
rescue Exception
|
298
|
+
disconnect
|
299
|
+
raise
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
@@ -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,52 @@
|
|
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
|
+
def initialize
|
10
|
+
@connection = ::Hiredis::Connection.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def connected?
|
14
|
+
@connection.connected?
|
15
|
+
end
|
16
|
+
|
17
|
+
def timeout=(usecs)
|
18
|
+
@connection.timeout = usecs
|
19
|
+
end
|
20
|
+
|
21
|
+
def connect(uri, timeout)
|
22
|
+
@connection.connect(uri.host, uri.port, timeout)
|
23
|
+
rescue Errno::ETIMEDOUT
|
24
|
+
raise Timeout::Error
|
25
|
+
end
|
26
|
+
|
27
|
+
def connect_unix(path, timeout)
|
28
|
+
@connection.connect_unix(path, timeout)
|
29
|
+
rescue Errno::ETIMEDOUT
|
30
|
+
raise Timeout::Error
|
31
|
+
end
|
32
|
+
|
33
|
+
def disconnect
|
34
|
+
@connection.disconnect
|
35
|
+
end
|
36
|
+
|
37
|
+
def write(command)
|
38
|
+
@connection.write(command.flatten(1))
|
39
|
+
end
|
40
|
+
|
41
|
+
def read
|
42
|
+
reply = @connection.read
|
43
|
+
reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
|
44
|
+
reply
|
45
|
+
rescue RuntimeError => err
|
46
|
+
raise ProtocolError.new(err.message)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
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,136 @@
|
|
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
|
+
class Ruby
|
9
|
+
include Redis::Connection::CommandHelper
|
10
|
+
|
11
|
+
MINUS = "-".freeze
|
12
|
+
PLUS = "+".freeze
|
13
|
+
COLON = ":".freeze
|
14
|
+
DOLLAR = "$".freeze
|
15
|
+
ASTERISK = "*".freeze
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@sock = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def connected?
|
22
|
+
!! @sock
|
23
|
+
end
|
24
|
+
|
25
|
+
def connect(uri, timeout)
|
26
|
+
with_timeout(timeout.to_f / 1_000_000) do
|
27
|
+
@sock = TCPSocket.new(uri.host, uri.port)
|
28
|
+
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def connect_unix(path, timeout)
|
33
|
+
with_timeout(timeout.to_f / 1_000_000) do
|
34
|
+
@sock = UNIXSocket.new(path)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def disconnect
|
39
|
+
@sock.close
|
40
|
+
rescue
|
41
|
+
ensure
|
42
|
+
@sock = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def timeout=(usecs)
|
46
|
+
secs = Integer(usecs / 1_000_000)
|
47
|
+
usecs = Integer(usecs - (secs * 1_000_000)) # 0 - 999_999
|
48
|
+
|
49
|
+
optval = [secs, usecs].pack("l_2")
|
50
|
+
|
51
|
+
begin
|
52
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
53
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
54
|
+
rescue Errno::ENOPROTOOPT
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def write(command)
|
59
|
+
@sock.write(build_command(command))
|
60
|
+
end
|
61
|
+
|
62
|
+
def read
|
63
|
+
# We read the first byte using read() mainly because gets() is
|
64
|
+
# immune to raw socket timeouts.
|
65
|
+
reply_type = @sock.read(1)
|
66
|
+
|
67
|
+
raise Errno::ECONNRESET unless reply_type
|
68
|
+
|
69
|
+
format_reply(reply_type, @sock.gets)
|
70
|
+
end
|
71
|
+
|
72
|
+
def format_reply(reply_type, line)
|
73
|
+
case reply_type
|
74
|
+
when MINUS then format_error_reply(line)
|
75
|
+
when PLUS then format_status_reply(line)
|
76
|
+
when COLON then format_integer_reply(line)
|
77
|
+
when DOLLAR then format_bulk_reply(line)
|
78
|
+
when ASTERISK then format_multi_bulk_reply(line)
|
79
|
+
else raise ProtocolError.new(reply_type)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def format_error_reply(line)
|
84
|
+
CommandError.new(line.strip)
|
85
|
+
end
|
86
|
+
|
87
|
+
def format_status_reply(line)
|
88
|
+
line.strip
|
89
|
+
end
|
90
|
+
|
91
|
+
def format_integer_reply(line)
|
92
|
+
line.to_i
|
93
|
+
end
|
94
|
+
|
95
|
+
def format_bulk_reply(line)
|
96
|
+
bulklen = line.to_i
|
97
|
+
return if bulklen == -1
|
98
|
+
reply = encode(@sock.read(bulklen))
|
99
|
+
@sock.read(2) # Discard CRLF.
|
100
|
+
reply
|
101
|
+
end
|
102
|
+
|
103
|
+
def format_multi_bulk_reply(line)
|
104
|
+
n = line.to_i
|
105
|
+
return if n == -1
|
106
|
+
|
107
|
+
Array.new(n) { read }
|
108
|
+
end
|
109
|
+
|
110
|
+
protected
|
111
|
+
|
112
|
+
begin
|
113
|
+
require "system_timer"
|
114
|
+
|
115
|
+
def with_timeout(seconds, &block)
|
116
|
+
SystemTimer.timeout_after(seconds, &block)
|
117
|
+
end
|
118
|
+
|
119
|
+
rescue LoadError
|
120
|
+
if ! defined?(RUBY_ENGINE)
|
121
|
+
# MRI 1.8, all other interpreters define RUBY_ENGINE, JRuby and
|
122
|
+
# Rubinius should have no issues with timeout.
|
123
|
+
warn "WARNING: using the built-in Timeout class which is known to have issues when used for opening connections. Install the SystemTimer gem if you want to make sure the Redis client will not hang."
|
124
|
+
end
|
125
|
+
|
126
|
+
require "timeout"
|
127
|
+
|
128
|
+
def with_timeout(seconds, &block)
|
129
|
+
Timeout.timeout(seconds, &block)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
Redis::Connection.drivers << Redis::Connection::Ruby
|