redis 3.0.0.rc1 → 3.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +50 -0
- data/.travis/Gemfile +11 -0
- data/CHANGELOG.md +47 -19
- data/README.md +160 -149
- data/Rakefile +15 -50
- data/examples/pubsub.rb +1 -1
- data/examples/unicorn/config.ru +1 -1
- data/examples/unicorn/unicorn.rb +1 -1
- data/lib/redis.rb +790 -390
- data/lib/redis/client.rb +137 -49
- data/lib/redis/connection/hiredis.rb +26 -15
- data/lib/redis/connection/ruby.rb +170 -53
- data/lib/redis/connection/synchrony.rb +23 -35
- data/lib/redis/distributed.rb +92 -32
- data/lib/redis/errors.rb +4 -2
- data/lib/redis/pipeline.rb +17 -6
- data/lib/redis/version.rb +1 -1
- data/redis.gemspec +4 -6
- data/test/blocking_commands_test.rb +42 -0
- data/test/command_map_test.rb +18 -17
- data/test/commands_on_hashes_test.rb +13 -12
- data/test/commands_on_lists_test.rb +35 -45
- data/test/commands_on_sets_test.rb +55 -54
- data/test/commands_on_sorted_sets_test.rb +106 -105
- data/test/commands_on_strings_test.rb +64 -55
- data/test/commands_on_value_types_test.rb +66 -54
- data/test/connection_handling_test.rb +136 -151
- data/test/distributed_blocking_commands_test.rb +33 -40
- data/test/distributed_commands_on_hashes_test.rb +6 -7
- data/test/distributed_commands_on_lists_test.rb +13 -14
- data/test/distributed_commands_on_sets_test.rb +57 -58
- data/test/distributed_commands_on_sorted_sets_test.rb +11 -12
- data/test/distributed_commands_on_strings_test.rb +31 -32
- data/test/distributed_commands_on_value_types_test.rb +61 -46
- data/test/distributed_commands_requiring_clustering_test.rb +108 -108
- data/test/distributed_connection_handling_test.rb +14 -15
- data/test/distributed_internals_test.rb +7 -19
- data/test/distributed_key_tags_test.rb +36 -36
- data/test/distributed_persistence_control_commands_test.rb +17 -14
- data/test/distributed_publish_subscribe_test.rb +61 -69
- data/test/distributed_remote_server_control_commands_test.rb +39 -28
- data/test/distributed_sorting_test.rb +12 -13
- data/test/distributed_test.rb +40 -41
- data/test/distributed_transactions_test.rb +20 -21
- data/test/encoding_test.rb +12 -9
- data/test/error_replies_test.rb +42 -36
- data/test/helper.rb +118 -85
- data/test/helper_test.rb +20 -6
- data/test/internals_test.rb +167 -103
- data/test/lint/blocking_commands.rb +124 -0
- data/test/lint/hashes.rb +115 -93
- data/test/lint/lists.rb +86 -80
- data/test/lint/sets.rb +68 -62
- data/test/lint/sorted_sets.rb +200 -195
- data/test/lint/strings.rb +112 -94
- data/test/lint/value_types.rb +76 -55
- data/test/persistence_control_commands_test.rb +17 -12
- data/test/pipelining_commands_test.rb +135 -126
- data/test/publish_subscribe_test.rb +105 -110
- data/test/remote_server_control_commands_test.rb +74 -58
- data/test/sorting_test.rb +31 -29
- data/test/support/connection/hiredis.rb +1 -0
- data/test/support/connection/ruby.rb +1 -0
- data/test/support/connection/synchrony.rb +17 -0
- data/test/{redis_mock.rb → support/redis_mock.rb} +24 -21
- data/test/support/wire/synchrony.rb +24 -0
- data/test/support/wire/thread.rb +5 -0
- data/test/synchrony_driver.rb +9 -9
- data/test/test.conf +1 -1
- data/test/thread_safety_test.rb +21 -19
- data/test/transactions_test.rb +189 -118
- data/test/unknown_commands_test.rb +9 -8
- data/test/url_param_test.rb +46 -41
- metadata +28 -43
- data/TODO.md +0 -4
- data/benchmarking/thread_safety.rb +0 -38
- data/test/lint/internals.rb +0 -36
data/lib/redis/client.rb
CHANGED
@@ -2,40 +2,76 @@ require "redis/errors"
|
|
2
2
|
|
3
3
|
class Redis
|
4
4
|
class Client
|
5
|
-
|
6
|
-
|
5
|
+
|
6
|
+
DEFAULTS = {
|
7
|
+
:scheme => "redis",
|
8
|
+
:host => "127.0.0.1",
|
9
|
+
:port => 6379,
|
10
|
+
:path => nil,
|
11
|
+
:timeout => 5.0,
|
12
|
+
:password => nil,
|
13
|
+
:db => 0,
|
14
|
+
}
|
15
|
+
|
16
|
+
def scheme
|
17
|
+
@options[:scheme]
|
18
|
+
end
|
19
|
+
|
20
|
+
def host
|
21
|
+
@options[:host]
|
22
|
+
end
|
23
|
+
|
24
|
+
def port
|
25
|
+
@options[:port]
|
26
|
+
end
|
27
|
+
|
28
|
+
def path
|
29
|
+
@options[:path]
|
30
|
+
end
|
31
|
+
|
32
|
+
def timeout
|
33
|
+
@options[:timeout]
|
34
|
+
end
|
35
|
+
|
36
|
+
def password
|
37
|
+
@options[:password]
|
38
|
+
end
|
39
|
+
|
40
|
+
def db
|
41
|
+
@options[:db]
|
42
|
+
end
|
43
|
+
|
44
|
+
def db=(db)
|
45
|
+
@options[:db] = db.to_i
|
46
|
+
end
|
47
|
+
|
48
|
+
attr :logger
|
7
49
|
attr :connection
|
8
50
|
attr :command_map
|
9
51
|
|
10
52
|
def initialize(options = {})
|
11
|
-
@
|
12
|
-
if @path.nil?
|
13
|
-
@host = options[:host] || "127.0.0.1"
|
14
|
-
@port = (options[:port] || 6379).to_i
|
15
|
-
end
|
16
|
-
|
17
|
-
@db = (options[:db] || 0).to_i
|
18
|
-
@timeout = (options[:timeout] || 5).to_f
|
19
|
-
@password = options[:password]
|
20
|
-
@logger = options[:logger]
|
53
|
+
@options = _parse_options(options)
|
21
54
|
@reconnect = true
|
22
|
-
@
|
55
|
+
@logger = @options[:logger]
|
56
|
+
@connection = nil
|
23
57
|
@command_map = {}
|
24
58
|
end
|
25
59
|
|
26
60
|
def connect
|
61
|
+
@pid = Process.pid
|
62
|
+
|
27
63
|
establish_connection
|
28
|
-
call [:auth,
|
29
|
-
call [:select,
|
64
|
+
call [:auth, password] if password
|
65
|
+
call [:select, db] if db != 0
|
30
66
|
self
|
31
67
|
end
|
32
68
|
|
33
69
|
def id
|
34
|
-
"redis://#{location}/#{db}"
|
70
|
+
@options[:id] || "redis://#{location}/#{db}"
|
35
71
|
end
|
36
72
|
|
37
73
|
def location
|
38
|
-
|
74
|
+
path || "#{host}:#{port}"
|
39
75
|
end
|
40
76
|
|
41
77
|
def call(command, &block)
|
@@ -155,11 +191,11 @@ class Redis
|
|
155
191
|
end
|
156
192
|
|
157
193
|
def connected?
|
158
|
-
connection.connected?
|
194
|
+
connection && connection.connected?
|
159
195
|
end
|
160
196
|
|
161
197
|
def disconnect
|
162
|
-
connection.disconnect if
|
198
|
+
connection.disconnect if connected?
|
163
199
|
end
|
164
200
|
|
165
201
|
def reconnect
|
@@ -169,7 +205,7 @@ class Redis
|
|
169
205
|
|
170
206
|
def io
|
171
207
|
yield
|
172
|
-
rescue
|
208
|
+
rescue TimeoutError
|
173
209
|
raise TimeoutError, "Connection timed out"
|
174
210
|
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL => e
|
175
211
|
raise ConnectionError, "Connection lost (%s)" % [e.class.name.split("::").last]
|
@@ -191,10 +227,10 @@ class Redis
|
|
191
227
|
connect unless connected?
|
192
228
|
|
193
229
|
begin
|
194
|
-
|
230
|
+
connection.timeout = 0
|
195
231
|
yield
|
196
232
|
ensure
|
197
|
-
|
233
|
+
connection.timeout = timeout if connected?
|
198
234
|
end
|
199
235
|
end
|
200
236
|
|
@@ -209,57 +245,44 @@ class Redis
|
|
209
245
|
|
210
246
|
protected
|
211
247
|
|
212
|
-
def deprecated(old, new = nil, trace = caller[0])
|
213
|
-
message = "The method #{old} is deprecated and will be removed in 2.0"
|
214
|
-
message << " - use #{new} instead" if new
|
215
|
-
Redis.deprecate(message, trace)
|
216
|
-
end
|
217
|
-
|
218
248
|
def logging(commands)
|
219
249
|
return yield unless @logger && @logger.debug?
|
220
250
|
|
221
251
|
begin
|
222
252
|
commands.each do |name, *args|
|
223
|
-
@logger.debug("Redis >> #{name.to_s.upcase} #{args.join(" ")}")
|
253
|
+
@logger.debug("Redis >> #{name.to_s.upcase} #{args.map(&:to_s).join(" ")}")
|
224
254
|
end
|
225
255
|
|
226
256
|
t1 = Time.now
|
227
257
|
yield
|
228
258
|
ensure
|
229
|
-
@logger.debug("Redis >> %0.2fms" % ((Time.now - t1) * 1000))
|
259
|
+
@logger.debug("Redis >> %0.2fms" % ((Time.now - t1) * 1000)) if t1
|
230
260
|
end
|
231
261
|
end
|
232
262
|
|
233
263
|
def establish_connection
|
234
|
-
|
235
|
-
timeout = Integer(@timeout * 1_000_000)
|
236
|
-
|
237
|
-
if @path
|
238
|
-
connection.connect_unix(@path, timeout)
|
239
|
-
else
|
240
|
-
connection.connect(@host, @port, timeout)
|
241
|
-
end
|
242
|
-
|
243
|
-
# If the timeout is set we set the low level socket options in order
|
244
|
-
# to make sure a blocking read will return after the specified number
|
245
|
-
# of seconds. This hack is from memcached ruby client.
|
246
|
-
self.timeout = @timeout
|
264
|
+
@connection = @options[:driver].connect(@options.dup)
|
247
265
|
|
248
|
-
rescue
|
266
|
+
rescue TimeoutError
|
249
267
|
raise CannotConnectError, "Timed out connecting to Redis on #{location}"
|
250
268
|
rescue Errno::ECONNREFUSED
|
251
269
|
raise CannotConnectError, "Error connecting to Redis on #{location} (ECONNREFUSED)"
|
252
270
|
end
|
253
271
|
|
254
|
-
def timeout=(timeout)
|
255
|
-
connection.timeout = Integer(timeout * 1_000_000)
|
256
|
-
end
|
257
|
-
|
258
272
|
def ensure_connected
|
259
273
|
tries = 0
|
260
274
|
|
261
275
|
begin
|
262
|
-
|
276
|
+
if connected?
|
277
|
+
if Process.pid != @pid
|
278
|
+
raise InheritedError,
|
279
|
+
"Tried to use a connection from a child process without reconnecting. " +
|
280
|
+
"You need to reconnect to Redis after forking."
|
281
|
+
end
|
282
|
+
else
|
283
|
+
connect
|
284
|
+
end
|
285
|
+
|
263
286
|
tries += 1
|
264
287
|
|
265
288
|
yield
|
@@ -276,5 +299,70 @@ class Redis
|
|
276
299
|
raise
|
277
300
|
end
|
278
301
|
end
|
302
|
+
|
303
|
+
def _parse_options(options)
|
304
|
+
defaults = DEFAULTS.dup
|
305
|
+
|
306
|
+
url = options[:url] || ENV["REDIS_URL"]
|
307
|
+
|
308
|
+
# Override defaults from URL if given
|
309
|
+
if url
|
310
|
+
require "uri"
|
311
|
+
|
312
|
+
uri = URI(url)
|
313
|
+
|
314
|
+
if uri.scheme == "unix"
|
315
|
+
defaults[:path] = uri.path
|
316
|
+
else
|
317
|
+
# Require the URL to have at least a host
|
318
|
+
raise ArgumentError, "invalid url" unless uri.host
|
319
|
+
|
320
|
+
defaults[:scheme] = uri.scheme
|
321
|
+
defaults[:host] = uri.host
|
322
|
+
defaults[:port] = uri.port if uri.port
|
323
|
+
defaults[:password] = uri.password if uri.password
|
324
|
+
defaults[:db] = uri.path[1..-1].to_i if uri.path
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
options = defaults.merge(options)
|
329
|
+
|
330
|
+
if options[:path]
|
331
|
+
options[:scheme] = "unix"
|
332
|
+
options.delete(:host)
|
333
|
+
options.delete(:port)
|
334
|
+
else
|
335
|
+
options[:host] = options[:host].to_s
|
336
|
+
options[:port] = options[:port].to_i
|
337
|
+
end
|
338
|
+
|
339
|
+
options[:timeout] = options[:timeout].to_f
|
340
|
+
options[:db] = options[:db].to_i
|
341
|
+
options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last
|
342
|
+
|
343
|
+
options
|
344
|
+
end
|
345
|
+
|
346
|
+
def _parse_driver(driver)
|
347
|
+
driver = driver.to_s if driver.is_a?(Symbol)
|
348
|
+
|
349
|
+
if driver.kind_of?(String)
|
350
|
+
case driver
|
351
|
+
when "ruby"
|
352
|
+
require "redis/connection/ruby"
|
353
|
+
driver = Connection::Ruby
|
354
|
+
when "hiredis"
|
355
|
+
require "redis/connection/hiredis"
|
356
|
+
driver = Connection::Hiredis
|
357
|
+
when "synchrony"
|
358
|
+
require "redis/connection/synchrony"
|
359
|
+
driver = Connection::Synchrony
|
360
|
+
else
|
361
|
+
raise "Unknown driver: #{driver}"
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
driver
|
366
|
+
end
|
279
367
|
end
|
280
368
|
end
|
@@ -6,42 +6,53 @@ require "timeout"
|
|
6
6
|
class Redis
|
7
7
|
module Connection
|
8
8
|
class Hiredis
|
9
|
-
def initialize
|
10
|
-
@connection = ::Hiredis::Connection.new
|
11
|
-
end
|
12
9
|
|
13
|
-
def
|
14
|
-
|
10
|
+
def self.connect(config)
|
11
|
+
connection = ::Hiredis::Connection.new
|
12
|
+
|
13
|
+
if config[:scheme] == "unix"
|
14
|
+
connection.connect_unix(config[:path], Integer(config[:timeout] * 1_000_000))
|
15
|
+
else
|
16
|
+
connection.connect(config[:host], config[:port], Integer(config[:timeout] * 1_000_000))
|
17
|
+
end
|
18
|
+
|
19
|
+
instance = new(connection)
|
20
|
+
instance.timeout = config[:timeout]
|
21
|
+
instance
|
22
|
+
rescue Errno::ETIMEDOUT
|
23
|
+
raise TimeoutError
|
15
24
|
end
|
16
25
|
|
17
|
-
def
|
18
|
-
@connection
|
26
|
+
def initialize(connection)
|
27
|
+
@connection = connection
|
19
28
|
end
|
20
29
|
|
21
|
-
def
|
22
|
-
@connection
|
23
|
-
rescue Errno::ETIMEDOUT
|
24
|
-
raise Timeout::Error
|
30
|
+
def connected?
|
31
|
+
@connection && @connection.connected?
|
25
32
|
end
|
26
33
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
raise Timeout::Error
|
34
|
+
def timeout=(timeout)
|
35
|
+
# Hiredis works with microsecond timeouts
|
36
|
+
@connection.timeout = Integer(timeout * 1_000_000)
|
31
37
|
end
|
32
38
|
|
33
39
|
def disconnect
|
34
40
|
@connection.disconnect
|
41
|
+
@connection = nil
|
35
42
|
end
|
36
43
|
|
37
44
|
def write(command)
|
38
45
|
@connection.write(command.flatten(1))
|
46
|
+
rescue Errno::EAGAIN
|
47
|
+
raise TimeoutError
|
39
48
|
end
|
40
49
|
|
41
50
|
def read
|
42
51
|
reply = @connection.read
|
43
52
|
reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
|
44
53
|
reply
|
54
|
+
rescue Errno::EAGAIN
|
55
|
+
raise TimeoutError
|
45
56
|
rescue RuntimeError => err
|
46
57
|
raise ProtocolError.new(err.message)
|
47
58
|
end
|
@@ -5,6 +5,155 @@ require "socket"
|
|
5
5
|
|
6
6
|
class Redis
|
7
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
|
+
class UNIXSocket < ::UNIXSocket
|
83
|
+
|
84
|
+
# This class doesn't include the mixin, because JRuby raises
|
85
|
+
# Errno::EAGAIN on #read_nonblock even when IO.select says it is
|
86
|
+
# readable. This behavior shows in 1.6.6 in both 1.8 and 1.9 mode.
|
87
|
+
# Therefore, fall back on the default Unix socket implementation,
|
88
|
+
# without timeouts.
|
89
|
+
|
90
|
+
def self.connect(path, timeout)
|
91
|
+
Timeout.timeout(timeout) do
|
92
|
+
sock = new(path)
|
93
|
+
sock
|
94
|
+
end
|
95
|
+
rescue Timeout::Error
|
96
|
+
raise TimeoutError
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
else
|
101
|
+
|
102
|
+
class TCPSocket < ::Socket
|
103
|
+
|
104
|
+
include SocketMixin
|
105
|
+
|
106
|
+
def self.connect(host, port, timeout)
|
107
|
+
# Limit lookup to IPv4, as Redis doesn't yet do IPv6...
|
108
|
+
addr = ::Socket.getaddrinfo(host, nil, Socket::AF_INET)
|
109
|
+
sock = new(::Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
110
|
+
sockaddr = ::Socket.pack_sockaddr_in(port, addr[0][3])
|
111
|
+
|
112
|
+
begin
|
113
|
+
sock.connect_nonblock(sockaddr)
|
114
|
+
rescue Errno::EINPROGRESS
|
115
|
+
if IO.select(nil, [sock], nil, timeout) == nil
|
116
|
+
raise TimeoutError
|
117
|
+
end
|
118
|
+
|
119
|
+
begin
|
120
|
+
sock.connect_nonblock(sockaddr)
|
121
|
+
rescue Errno::EISCONN
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
sock
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class UNIXSocket < ::Socket
|
130
|
+
|
131
|
+
# This class doesn't include the mixin to keep its behavior in sync
|
132
|
+
# with the JRuby implementation.
|
133
|
+
|
134
|
+
def self.connect(path, timeout)
|
135
|
+
sock = new(::Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
|
136
|
+
sockaddr = ::Socket.pack_sockaddr_un(path)
|
137
|
+
|
138
|
+
begin
|
139
|
+
sock.connect_nonblock(sockaddr)
|
140
|
+
rescue Errno::EINPROGRESS
|
141
|
+
if IO.select(nil, [sock], nil, timeout) == nil
|
142
|
+
raise TimeoutError
|
143
|
+
end
|
144
|
+
|
145
|
+
begin
|
146
|
+
sock.connect_nonblock(sockaddr)
|
147
|
+
rescue Errno::EISCONN
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
sock
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
8
157
|
class Ruby
|
9
158
|
include Redis::Connection::CommandHelper
|
10
159
|
|
@@ -14,25 +163,24 @@ class Redis
|
|
14
163
|
DOLLAR = "$".freeze
|
15
164
|
ASTERISK = "*".freeze
|
16
165
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
166
|
+
def self.connect(config)
|
167
|
+
if config[:scheme] == "unix"
|
168
|
+
sock = UNIXSocket.connect(config[:path], config[:timeout])
|
169
|
+
else
|
170
|
+
sock = TCPSocket.connect(config[:host], config[:port], config[:timeout])
|
171
|
+
end
|
20
172
|
|
21
|
-
|
22
|
-
|
173
|
+
instance = new(sock)
|
174
|
+
instance.timeout = config[:timeout]
|
175
|
+
instance
|
23
176
|
end
|
24
177
|
|
25
|
-
def
|
26
|
-
|
27
|
-
@sock = TCPSocket.new(host, port)
|
28
|
-
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
29
|
-
end
|
178
|
+
def initialize(sock)
|
179
|
+
@sock = sock
|
30
180
|
end
|
31
181
|
|
32
|
-
def
|
33
|
-
|
34
|
-
@sock = UNIXSocket.new(path)
|
35
|
-
end
|
182
|
+
def connected?
|
183
|
+
!! @sock
|
36
184
|
end
|
37
185
|
|
38
186
|
def disconnect
|
@@ -42,16 +190,9 @@ class Redis
|
|
42
190
|
@sock = nil
|
43
191
|
end
|
44
192
|
|
45
|
-
def timeout=(
|
46
|
-
|
47
|
-
|
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
|
193
|
+
def timeout=(timeout)
|
194
|
+
if @sock.respond_to?(:timeout=)
|
195
|
+
@sock.timeout = timeout
|
55
196
|
end
|
56
197
|
end
|
57
198
|
|
@@ -60,13 +201,12 @@ class Redis
|
|
60
201
|
end
|
61
202
|
|
62
203
|
def read
|
63
|
-
|
64
|
-
|
65
|
-
reply_type
|
204
|
+
line = @sock.gets
|
205
|
+
reply_type = line.slice!(0, 1)
|
206
|
+
format_reply(reply_type, line)
|
66
207
|
|
67
|
-
|
68
|
-
|
69
|
-
format_reply(reply_type, @sock.gets)
|
208
|
+
rescue Errno::EAGAIN
|
209
|
+
raise TimeoutError
|
70
210
|
end
|
71
211
|
|
72
212
|
def format_reply(reply_type, line)
|
@@ -106,29 +246,6 @@ class Redis
|
|
106
246
|
|
107
247
|
Array.new(n) { read }
|
108
248
|
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
249
|
end
|
133
250
|
end
|
134
251
|
end
|