redis 1.0.7 → 2.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/README.markdown +8 -0
- data/lib/redis.rb +467 -3
- data/lib/redis/client.rb +145 -464
- data/lib/redis/distributed.rb +388 -67
- data/lib/redis/pipeline.rb +5 -11
- data/lib/redis/subscribe.rb +83 -10
- metadata +11 -9
- data/lib/redis/dist_redis.rb +0 -16
data/lib/redis/client.rb
CHANGED
@@ -1,470 +1,78 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
class Client
|
3
|
-
class ProtocolError < RuntimeError
|
4
|
-
def initialize(reply_type)
|
5
|
-
super("Protocol error, got '#{reply_type}' as initial reply byte")
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
|
-
OK = "OK".freeze
|
10
5
|
MINUS = "-".freeze
|
11
6
|
PLUS = "+".freeze
|
12
7
|
COLON = ":".freeze
|
13
8
|
DOLLAR = "$".freeze
|
14
9
|
ASTERISK = "*".freeze
|
15
10
|
|
16
|
-
|
17
|
-
"set" => true,
|
18
|
-
"setnx" => true,
|
19
|
-
"rpush" => true,
|
20
|
-
"lpush" => true,
|
21
|
-
"lset" => true,
|
22
|
-
"lrem" => true,
|
23
|
-
"sadd" => true,
|
24
|
-
"srem" => true,
|
25
|
-
"sismember" => true,
|
26
|
-
"echo" => true,
|
27
|
-
"getset" => true,
|
28
|
-
"smove" => true,
|
29
|
-
"zadd" => true,
|
30
|
-
"zincrby" => true,
|
31
|
-
"zrem" => true,
|
32
|
-
"zscore" => true,
|
33
|
-
"zrank" => true,
|
34
|
-
"zrevrank" => true,
|
35
|
-
"hget" => true,
|
36
|
-
"hdel" => true,
|
37
|
-
"hexists" => true,
|
38
|
-
"publish" => true
|
39
|
-
}
|
40
|
-
|
41
|
-
MULTI_BULK_COMMANDS = {
|
42
|
-
"mset" => true,
|
43
|
-
"msetnx" => true,
|
44
|
-
"hset" => true,
|
45
|
-
"hmset" => true
|
46
|
-
}
|
47
|
-
|
48
|
-
BOOLEAN_PROCESSOR = lambda{|r| r == 1 }
|
49
|
-
|
50
|
-
REPLY_PROCESSOR = {
|
51
|
-
"exists" => BOOLEAN_PROCESSOR,
|
52
|
-
"sismember" => BOOLEAN_PROCESSOR,
|
53
|
-
"sadd" => BOOLEAN_PROCESSOR,
|
54
|
-
"srem" => BOOLEAN_PROCESSOR,
|
55
|
-
"smove" => BOOLEAN_PROCESSOR,
|
56
|
-
"zadd" => BOOLEAN_PROCESSOR,
|
57
|
-
"zrem" => BOOLEAN_PROCESSOR,
|
58
|
-
"move" => BOOLEAN_PROCESSOR,
|
59
|
-
"setnx" => BOOLEAN_PROCESSOR,
|
60
|
-
"del" => BOOLEAN_PROCESSOR,
|
61
|
-
"renamenx" => BOOLEAN_PROCESSOR,
|
62
|
-
"expire" => BOOLEAN_PROCESSOR,
|
63
|
-
"hset" => BOOLEAN_PROCESSOR,
|
64
|
-
"hexists" => BOOLEAN_PROCESSOR,
|
65
|
-
"info" => lambda{|r|
|
66
|
-
info = Hash.new do |hash, key|
|
67
|
-
if hash.include?(key.to_s)
|
68
|
-
Redis.deprecate "Redis#info will return a hash of string keys, not symbols", caller[2]
|
69
|
-
hash[key.to_s]
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
r.each_line {|kv|
|
74
|
-
k,v = kv.split(":",2).map{|x| x.chomp}
|
75
|
-
info[k] = v
|
76
|
-
}
|
77
|
-
info
|
78
|
-
},
|
79
|
-
"keys" => lambda{|r|
|
80
|
-
if r.is_a?(Array)
|
81
|
-
r
|
82
|
-
else
|
83
|
-
r.split(" ")
|
84
|
-
end
|
85
|
-
},
|
86
|
-
"hgetall" => lambda{|r|
|
87
|
-
Hash[*r]
|
88
|
-
}
|
89
|
-
}
|
90
|
-
|
91
|
-
ALIASES = {
|
92
|
-
"flush_db" => "flushdb",
|
93
|
-
"flush_all" => "flushall",
|
94
|
-
"last_save" => "lastsave",
|
95
|
-
"key?" => "exists",
|
96
|
-
"delete" => "del",
|
97
|
-
"randkey" => "randomkey",
|
98
|
-
"list_length" => "llen",
|
99
|
-
"push_tail" => "rpush",
|
100
|
-
"push_head" => "lpush",
|
101
|
-
"pop_tail" => "rpop",
|
102
|
-
"pop_head" => "lpop",
|
103
|
-
"list_set" => "lset",
|
104
|
-
"list_range" => "lrange",
|
105
|
-
"list_trim" => "ltrim",
|
106
|
-
"list_index" => "lindex",
|
107
|
-
"list_rm" => "lrem",
|
108
|
-
"set_add" => "sadd",
|
109
|
-
"set_delete" => "srem",
|
110
|
-
"set_count" => "scard",
|
111
|
-
"set_member?" => "sismember",
|
112
|
-
"set_members" => "smembers",
|
113
|
-
"set_intersect" => "sinter",
|
114
|
-
"set_intersect_store" => "sinterstore",
|
115
|
-
"set_inter_store" => "sinterstore",
|
116
|
-
"set_union" => "sunion",
|
117
|
-
"set_union_store" => "sunionstore",
|
118
|
-
"set_diff" => "sdiff",
|
119
|
-
"set_diff_store" => "sdiffstore",
|
120
|
-
"set_move" => "smove",
|
121
|
-
"set_unless_exists" => "setnx",
|
122
|
-
"rename_unless_exists" => "renamenx",
|
123
|
-
"type?" => "type",
|
124
|
-
"zset_add" => "zadd",
|
125
|
-
"zset_count" => "zcard",
|
126
|
-
"zset_range_by_score" => "zrangebyscore",
|
127
|
-
"zset_reverse_range" => "zrevrange",
|
128
|
-
"zset_range" => "zrange",
|
129
|
-
"zset_delete" => "zrem",
|
130
|
-
"zset_score" => "zscore",
|
131
|
-
"zset_incr_by" => "zincrby",
|
132
|
-
"zset_increment_by" => "zincrby"
|
133
|
-
}
|
134
|
-
|
135
|
-
DISABLED_COMMANDS = {
|
136
|
-
"monitor" => true,
|
137
|
-
"sync" => true
|
138
|
-
}
|
139
|
-
|
140
|
-
BLOCKING_COMMANDS = {
|
141
|
-
"blpop" => true,
|
142
|
-
"brpop" => true
|
143
|
-
}
|
11
|
+
attr_accessor :db, :host, :port, :password, :timeout, :logger
|
144
12
|
|
145
13
|
def initialize(options = {})
|
146
|
-
@host
|
147
|
-
@port
|
148
|
-
@db
|
14
|
+
@host = options[:host] || "127.0.0.1"
|
15
|
+
@port = (options[:port] || 6379).to_i
|
16
|
+
@db = (options[:db] || 0).to_i
|
149
17
|
@timeout = (options[:timeout] || 5).to_i
|
150
18
|
@password = options[:password]
|
151
19
|
@logger = options[:logger]
|
152
|
-
@
|
153
|
-
@binary_keys = options[:binary_keys]
|
154
|
-
@mutex = Mutex.new if @thread_safe
|
20
|
+
@mutex = ::Mutex.new
|
155
21
|
@sock = nil
|
156
|
-
@pubsub = false
|
157
|
-
|
158
|
-
log(self)
|
159
|
-
end
|
160
|
-
|
161
|
-
def to_s
|
162
|
-
"Redis Client connected to #{server} against DB #{@db}"
|
163
|
-
end
|
164
|
-
|
165
|
-
def select(*args)
|
166
|
-
raise "SELECT not allowed, use the :db option when creating the object"
|
167
|
-
end
|
168
|
-
|
169
|
-
def [](key)
|
170
|
-
get(key)
|
171
|
-
end
|
172
|
-
|
173
|
-
def []=(key,value)
|
174
|
-
set(key, value)
|
175
|
-
end
|
176
|
-
|
177
|
-
def get(key)
|
178
|
-
call_command([:get, key])
|
179
|
-
end
|
180
|
-
|
181
|
-
def set(key, value, ttl = nil)
|
182
|
-
if ttl
|
183
|
-
deprecated("set with an expire", :set_with_expire, caller[0])
|
184
|
-
set_with_expire(key, value, ttl)
|
185
|
-
else
|
186
|
-
call_command([:set, key, value])
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
def set_with_expire(key, value, ttl)
|
191
|
-
Redis.deprecate "Using a non-atomic set with expire. Use setex if your Redis version allows it.", caller[0]
|
192
|
-
set(key, value)
|
193
|
-
expire(key, ttl)
|
194
22
|
end
|
195
23
|
|
196
|
-
def
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
call_command(args.unshift(:mset))
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def mapped_mset(hash)
|
206
|
-
mset(*hash.to_a.flatten)
|
24
|
+
def connect
|
25
|
+
connect_to(@host, @port)
|
26
|
+
call(:auth, @password) if @password
|
27
|
+
call(:select, @db) if @db != 0
|
28
|
+
@sock
|
207
29
|
end
|
208
30
|
|
209
|
-
def
|
210
|
-
|
211
|
-
|
212
|
-
mapped_msetnx(args[0])
|
213
|
-
else
|
214
|
-
call_command(args.unshift(:msetnx))
|
31
|
+
def call(name, *args)
|
32
|
+
ensure_connected do
|
33
|
+
process_and_read([[name, *args]]).first
|
215
34
|
end
|
216
35
|
end
|
217
36
|
|
218
|
-
def
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
# Similar to memcache.rb's #get_multi, returns a hash mapping
|
223
|
-
# keys to values.
|
224
|
-
def mapped_mget(*keys)
|
225
|
-
result = {}
|
226
|
-
mget(*keys).each do |value|
|
227
|
-
key = keys.shift
|
228
|
-
result.merge!(key => value) unless value.nil?
|
37
|
+
def call_async(name, *args)
|
38
|
+
ensure_connected do
|
39
|
+
process([[name, *args]])
|
229
40
|
end
|
230
|
-
result
|
231
|
-
end
|
232
|
-
|
233
|
-
def sort(key, options = {})
|
234
|
-
cmd = []
|
235
|
-
cmd << "SORT #{key}"
|
236
|
-
cmd << "BY #{options[:by]}" if options[:by]
|
237
|
-
cmd << "GET #{[options[:get]].flatten * ' GET '}" if options[:get]
|
238
|
-
cmd << "#{options[:order]}" if options[:order]
|
239
|
-
cmd << "LIMIT #{options[:limit].join(' ')}" if options[:limit]
|
240
|
-
cmd << "STORE #{options[:store]}" if options[:store]
|
241
|
-
call_command(cmd)
|
242
41
|
end
|
243
42
|
|
244
|
-
def
|
245
|
-
|
246
|
-
|
247
|
-
incrby(key, increment)
|
248
|
-
else
|
249
|
-
call_command([:incr, key])
|
43
|
+
def call_pipelined(commands)
|
44
|
+
ensure_connected do
|
45
|
+
process_and_read(commands)
|
250
46
|
end
|
251
47
|
end
|
252
48
|
|
253
|
-
def
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
call_command([:decr, key])
|
49
|
+
def call_blocking(name, *args)
|
50
|
+
ensure_connected do
|
51
|
+
without_socket_timeout do
|
52
|
+
call(name, *args)
|
53
|
+
end
|
259
54
|
end
|
260
55
|
end
|
261
56
|
|
262
|
-
|
263
|
-
|
264
|
-
def type(key)
|
265
|
-
call_command(['type', key])
|
266
|
-
end
|
267
|
-
|
268
|
-
def quit
|
269
|
-
call_command(['quit'])
|
270
|
-
rescue Errno::ECONNRESET
|
271
|
-
end
|
272
|
-
|
273
|
-
def pipelined(&block)
|
274
|
-
Redis.deprecate("Calling pipelined commands on the yielded object will be deprecated in 2.0", caller[0])
|
275
|
-
pipeline = Pipeline.new self
|
276
|
-
yield pipeline
|
277
|
-
pipeline.execute
|
278
|
-
end
|
279
|
-
|
280
|
-
def exec
|
281
|
-
# Need to override Kernel#exec.
|
282
|
-
call_command([:exec])
|
283
|
-
end
|
284
|
-
|
285
|
-
def multi(&block)
|
286
|
-
result = call_command [:multi]
|
287
|
-
|
288
|
-
return result unless block_given?
|
289
|
-
|
290
|
-
begin
|
291
|
-
yield(self)
|
292
|
-
exec
|
293
|
-
rescue Exception => e
|
294
|
-
discard
|
295
|
-
raise e
|
296
|
-
end
|
57
|
+
def connected?
|
58
|
+
!! @sock
|
297
59
|
end
|
298
60
|
|
299
|
-
def
|
300
|
-
# Top-level `subscribe` MUST be called with a block,
|
301
|
-
# nested `subscribe` MUST NOT be called with a block
|
302
|
-
if !@pubsub && !block_given?
|
303
|
-
raise "Top-level subscribe requires a block"
|
304
|
-
elsif @pubsub == true && block_given?
|
305
|
-
raise "Nested subscribe does not take a block"
|
306
|
-
elsif @pubsub
|
307
|
-
# If we're already pubsub'ing, just subscribe us to some more classes
|
308
|
-
call_command [:subscribe,*classes]
|
309
|
-
return true
|
310
|
-
end
|
311
|
-
|
312
|
-
@pubsub = true
|
313
|
-
call_command [:subscribe,*classes]
|
314
|
-
sub = Subscription.new
|
315
|
-
yield(sub)
|
61
|
+
def disconnect
|
316
62
|
begin
|
317
|
-
|
318
|
-
|
319
|
-
case type
|
320
|
-
when 'subscribe','unsubscribe'
|
321
|
-
sub.send(type) && sub.send(type).call(reply[0],reply[1])
|
322
|
-
when 'message'
|
323
|
-
sub.send(type) && sub.send(type).call(reply[0],reply[1])
|
324
|
-
end
|
325
|
-
break if type == 'unsubscribe' && reply[1] == 0
|
326
|
-
end
|
327
|
-
rescue RuntimeError
|
328
|
-
call_command [:unsubscribe]
|
329
|
-
raise
|
63
|
+
@sock.close
|
64
|
+
rescue
|
330
65
|
ensure
|
331
|
-
@
|
332
|
-
end
|
333
|
-
end
|
334
|
-
|
335
|
-
# Wrap raw_call_command to handle reconnection on socket error. We
|
336
|
-
# try to reconnect just one time, otherwise let the error araise.
|
337
|
-
def call_command(argv)
|
338
|
-
log(argv.inspect, :debug)
|
339
|
-
|
340
|
-
connect_to_server unless connected?
|
341
|
-
|
342
|
-
begin
|
343
|
-
raw_call_command(argv.dup)
|
344
|
-
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED
|
345
|
-
if reconnect
|
346
|
-
raw_call_command(argv.dup)
|
347
|
-
else
|
348
|
-
raise Errno::ECONNRESET
|
349
|
-
end
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
def server
|
354
|
-
"#{@host}:#{@port}"
|
355
|
-
end
|
356
|
-
|
357
|
-
def connect_to(host, port)
|
358
|
-
|
359
|
-
# We support connect_to() timeout only if system_timer is availabe
|
360
|
-
# or if we are running against Ruby >= 1.9
|
361
|
-
# Timeout reading from the socket instead will be supported anyway.
|
362
|
-
if @timeout != 0 and Timer
|
363
|
-
begin
|
364
|
-
@sock = TCPSocket.new(host, port)
|
365
|
-
rescue Timeout::Error
|
366
|
-
@sock = nil
|
367
|
-
raise Timeout::Error, "Timeout connecting to the server"
|
368
|
-
end
|
369
|
-
else
|
370
|
-
@sock = TCPSocket.new(host, port)
|
371
|
-
end
|
372
|
-
|
373
|
-
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
374
|
-
|
375
|
-
# If the timeout is set we set the low level socket options in order
|
376
|
-
# to make sure a blocking read will return after the specified number
|
377
|
-
# of seconds. This hack is from memcached ruby client.
|
378
|
-
set_socket_timeout!(@timeout) if @timeout
|
379
|
-
|
380
|
-
rescue Errno::ECONNREFUSED
|
381
|
-
raise Errno::ECONNREFUSED, "Unable to connect to Redis on #{host}:#{port}"
|
382
|
-
end
|
383
|
-
|
384
|
-
def connect_to_server
|
385
|
-
connect_to(@host, @port)
|
386
|
-
call_command([:auth, @password]) if @password
|
387
|
-
call_command([:select, @db]) if @db != 0
|
388
|
-
@sock
|
389
|
-
end
|
390
|
-
|
391
|
-
def method_missing(*argv)
|
392
|
-
call_command(argv)
|
393
|
-
end
|
394
|
-
|
395
|
-
def raw_call_command(argvp)
|
396
|
-
if argvp[0].is_a?(Array)
|
397
|
-
argvv = argvp
|
398
|
-
pipeline = true
|
399
|
-
else
|
400
|
-
argvv = [argvp]
|
401
|
-
pipeline = false
|
402
|
-
end
|
403
|
-
|
404
|
-
if @binary_keys or pipeline or MULTI_BULK_COMMANDS[argvv[0][0].to_s]
|
405
|
-
command = ""
|
406
|
-
argvv.each do |argv|
|
407
|
-
command << "*#{argv.size}\r\n"
|
408
|
-
argv.each{|a|
|
409
|
-
a = a.to_s
|
410
|
-
command << "$#{get_size(a)}\r\n"
|
411
|
-
command << a
|
412
|
-
command << "\r\n"
|
413
|
-
}
|
414
|
-
end
|
415
|
-
else
|
416
|
-
command = ""
|
417
|
-
argvv.each do |argv|
|
418
|
-
bulk = nil
|
419
|
-
argv[0] = argv[0].to_s
|
420
|
-
if ALIASES[argv[0]]
|
421
|
-
deprecated(argv[0], ALIASES[argv[0]], caller[4])
|
422
|
-
argv[0] = ALIASES[argv[0]]
|
423
|
-
end
|
424
|
-
raise "#{argv[0]} command is disabled" if DISABLED_COMMANDS[argv[0]]
|
425
|
-
if BULK_COMMANDS[argv[0]] and argv.length > 1
|
426
|
-
bulk = argv[-1].to_s
|
427
|
-
argv[-1] = get_size(bulk)
|
428
|
-
end
|
429
|
-
command << "#{argv.join(' ')}\r\n"
|
430
|
-
command << "#{bulk}\r\n" if bulk
|
431
|
-
end
|
432
|
-
end
|
433
|
-
# When in Pub/Sub mode we don't read replies synchronously.
|
434
|
-
if @pubsub
|
435
|
-
@sock.write(command)
|
436
|
-
return true
|
437
|
-
end
|
438
|
-
# The normal command execution is reading and processing the reply.
|
439
|
-
results = maybe_lock do
|
440
|
-
begin
|
441
|
-
set_socket_timeout!(0) if requires_timeout_reset?(argvv[0][0].to_s)
|
442
|
-
process_command(command, argvv)
|
443
|
-
ensure
|
444
|
-
set_socket_timeout!(@timeout) if requires_timeout_reset?(argvv[0][0].to_s)
|
445
|
-
end
|
446
|
-
end
|
447
|
-
|
448
|
-
return pipeline ? results : results[0]
|
449
|
-
end
|
450
|
-
|
451
|
-
def process_command(command, argvv)
|
452
|
-
@sock.write(command)
|
453
|
-
argvv.map do |argv|
|
454
|
-
processor = REPLY_PROCESSOR[argv[0].to_s]
|
455
|
-
processor ? processor.call(read_reply) : read_reply
|
66
|
+
@sock = nil
|
456
67
|
end
|
68
|
+
true
|
457
69
|
end
|
458
70
|
|
459
|
-
def
|
460
|
-
|
461
|
-
@mutex.synchronize(&block)
|
462
|
-
else
|
463
|
-
block.call
|
464
|
-
end
|
71
|
+
def reconnect
|
72
|
+
disconnect && connect
|
465
73
|
end
|
466
74
|
|
467
|
-
def
|
75
|
+
def read
|
468
76
|
|
469
77
|
# We read the first byte using read() mainly because gets() is
|
470
78
|
# immune to raw socket timeouts.
|
@@ -485,21 +93,30 @@ class Redis
|
|
485
93
|
format_reply(reply_type, @sock.gets)
|
486
94
|
end
|
487
95
|
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
def get_size(string)
|
495
|
-
string.size
|
96
|
+
def without_socket_timeout
|
97
|
+
begin
|
98
|
+
self.timeout = 0
|
99
|
+
yield
|
100
|
+
ensure
|
101
|
+
self.timeout = @timeout
|
496
102
|
end
|
497
103
|
end
|
498
104
|
|
499
|
-
|
105
|
+
protected
|
106
|
+
|
107
|
+
def build_command(name, *args)
|
108
|
+
command = []
|
109
|
+
command << "*#{args.size + 1}"
|
110
|
+
command << "$#{string_size name}"
|
111
|
+
command << name
|
500
112
|
|
501
|
-
|
502
|
-
|
113
|
+
args.each do |arg|
|
114
|
+
arg = arg.to_s
|
115
|
+
command << "$#{string_size arg}"
|
116
|
+
command << arg
|
117
|
+
end
|
118
|
+
|
119
|
+
command
|
503
120
|
end
|
504
121
|
|
505
122
|
def deprecated(old, new = nil, trace = caller[0])
|
@@ -508,39 +125,37 @@ class Redis
|
|
508
125
|
Redis.deprecate(message, trace)
|
509
126
|
end
|
510
127
|
|
511
|
-
|
512
|
-
BLOCKING_COMMANDS[command] && @timeout
|
513
|
-
end
|
128
|
+
COMMAND_DELIMITER = "\r\n"
|
514
129
|
|
515
|
-
def
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
begin
|
520
|
-
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
521
|
-
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
522
|
-
rescue Exception => e
|
523
|
-
# Solaris, for one, does not like/support socket timeouts.
|
524
|
-
log("Unable to use raw socket timeouts: #{e.class.name}: #{e.message}")
|
130
|
+
def process(commands)
|
131
|
+
logging(commands) do
|
132
|
+
@sock.write(join_commands(commands))
|
133
|
+
yield if block_given?
|
525
134
|
end
|
526
135
|
end
|
527
136
|
|
528
|
-
def
|
529
|
-
|
137
|
+
def join_commands(commands)
|
138
|
+
commands.map do |command|
|
139
|
+
build_command(*command).join(COMMAND_DELIMITER) + COMMAND_DELIMITER
|
140
|
+
end.join(COMMAND_DELIMITER) + COMMAND_DELIMITER
|
530
141
|
end
|
531
142
|
|
532
|
-
def
|
533
|
-
|
534
|
-
@
|
535
|
-
|
536
|
-
|
537
|
-
@sock = nil
|
143
|
+
def process_and_read(commands)
|
144
|
+
process(commands) do
|
145
|
+
@mutex.synchronize do
|
146
|
+
Array.new(commands.size).map { read }
|
147
|
+
end
|
538
148
|
end
|
539
|
-
true
|
540
149
|
end
|
541
150
|
|
542
|
-
|
543
|
-
|
151
|
+
if "".respond_to?(:bytesize)
|
152
|
+
def string_size(string)
|
153
|
+
string.to_s.bytesize
|
154
|
+
end
|
155
|
+
else
|
156
|
+
def string_size(string)
|
157
|
+
string.to_s.size
|
158
|
+
end
|
544
159
|
end
|
545
160
|
|
546
161
|
def format_reply(reply_type, line)
|
@@ -576,8 +191,74 @@ class Redis
|
|
576
191
|
|
577
192
|
def format_multi_bulk_reply(line)
|
578
193
|
reply = []
|
579
|
-
line.to_i.times { reply <<
|
194
|
+
line.to_i.times { reply << read }
|
580
195
|
reply
|
581
196
|
end
|
197
|
+
|
198
|
+
def logging(commands)
|
199
|
+
return yield unless @logger
|
200
|
+
|
201
|
+
begin
|
202
|
+
commands.each do |name, *args|
|
203
|
+
@logger.debug("Redis >> #{name.to_s.upcase} #{args.join(" ")}")
|
204
|
+
end
|
205
|
+
|
206
|
+
t1 = Time.now
|
207
|
+
yield
|
208
|
+
ensure
|
209
|
+
@logger.debug("Redis >> %0.2fms" % ((Time.now - t1) * 1000))
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
if defined?(Timeout)
|
214
|
+
TimeoutError = Timeout::Error
|
215
|
+
else
|
216
|
+
TimeoutError = Exception
|
217
|
+
end
|
218
|
+
|
219
|
+
def connect_to(host, port)
|
220
|
+
begin
|
221
|
+
@sock = TCPSocket.new(host, port)
|
222
|
+
rescue TimeoutError
|
223
|
+
@sock = nil
|
224
|
+
raise
|
225
|
+
end
|
226
|
+
|
227
|
+
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
228
|
+
|
229
|
+
# If the timeout is set we set the low level socket options in order
|
230
|
+
# to make sure a blocking read will return after the specified number
|
231
|
+
# of seconds. This hack is from memcached ruby client.
|
232
|
+
self.timeout = @timeout
|
233
|
+
|
234
|
+
rescue Errno::ECONNREFUSED
|
235
|
+
raise Errno::ECONNREFUSED, "Unable to connect to Redis on #{host}:#{port}"
|
236
|
+
end
|
237
|
+
|
238
|
+
def timeout=(timeout)
|
239
|
+
secs = Integer(timeout)
|
240
|
+
usecs = Integer((timeout - secs) * 1_000_000)
|
241
|
+
optval = [secs, usecs].pack("l_2")
|
242
|
+
|
243
|
+
begin
|
244
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
245
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
246
|
+
rescue Errno::ENOPROTOOPT
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def ensure_connected
|
251
|
+
connect unless connected?
|
252
|
+
|
253
|
+
begin
|
254
|
+
yield
|
255
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED
|
256
|
+
if reconnect
|
257
|
+
yield
|
258
|
+
else
|
259
|
+
raise Errno::ECONNRESET
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
582
263
|
end
|
583
264
|
end
|