ezmobius-redis 0.0.3.4
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/LICENSE +20 -0
- data/README.markdown +31 -0
- data/Rakefile +59 -0
- data/lib/dist_redis.rb +111 -0
- data/lib/hash_ring.rb +127 -0
- data/lib/pipeline.rb +31 -0
- data/lib/redis.rb +527 -0
- data/lib/server.rb +160 -0
- data/spec/redis_spec.rb +419 -0
- data/spec/spec_helper.rb +4 -0
- metadata +64 -0
data/lib/redis.rb
ADDED
@@ -0,0 +1,527 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'set'
|
3
|
+
require File.join(File.dirname(__FILE__),'server')
|
4
|
+
require File.join(File.dirname(__FILE__),'pipeline')
|
5
|
+
|
6
|
+
|
7
|
+
class RedisError < StandardError
|
8
|
+
end
|
9
|
+
class RedisRenameError < StandardError
|
10
|
+
end
|
11
|
+
class Redis
|
12
|
+
ERR = "-".freeze
|
13
|
+
OK = 'OK'.freeze
|
14
|
+
SINGLE = '+'.freeze
|
15
|
+
BULK = '$'.freeze
|
16
|
+
MULTI = '*'.freeze
|
17
|
+
INT = ':'.freeze
|
18
|
+
|
19
|
+
attr_reader :server
|
20
|
+
|
21
|
+
|
22
|
+
def initialize(opts={})
|
23
|
+
@opts = {:host => 'localhost', :port => '6379', :db => 0}.merge(opts)
|
24
|
+
$debug = @opts[:debug]
|
25
|
+
@db = @opts[:db]
|
26
|
+
@server = Server.new(@opts[:host], @opts[:port], (@opts[:timeout]||10))
|
27
|
+
end
|
28
|
+
|
29
|
+
def pipelined
|
30
|
+
pipeline = Pipeline.new(self)
|
31
|
+
yield pipeline
|
32
|
+
pipeline.finish
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
"#{host}:#{port}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def port
|
40
|
+
@opts[:port]
|
41
|
+
end
|
42
|
+
|
43
|
+
def host
|
44
|
+
@opts[:host]
|
45
|
+
end
|
46
|
+
|
47
|
+
def with_socket_management(server, &block)
|
48
|
+
begin
|
49
|
+
socket = server.socket
|
50
|
+
block.call(socket)
|
51
|
+
#Timeout or server down
|
52
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED, Timeout::Error => e
|
53
|
+
server.close
|
54
|
+
puts "Client (#{server.inspect}) disconnected from server: #{e.inspect}\n" if $debug
|
55
|
+
retry
|
56
|
+
#Server down
|
57
|
+
rescue NoMethodError => e
|
58
|
+
puts "Client (#{server.inspect}) tryin server that is down: #{e.inspect}\n Dying!" if $debug
|
59
|
+
raise Errno::ECONNREFUSED
|
60
|
+
#exit
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def monitor
|
65
|
+
with_socket_management(@server) do |socket|
|
66
|
+
trap("INT") { puts "\nGot ^C! Dying!"; exit }
|
67
|
+
write "MONITOR\r\n"
|
68
|
+
puts "Now Monitoring..."
|
69
|
+
socket.read(12)
|
70
|
+
loop do
|
71
|
+
x = socket.gets
|
72
|
+
puts x unless x.nil?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def quit
|
78
|
+
write "QUIT\r\n"
|
79
|
+
end
|
80
|
+
|
81
|
+
def select_db(index)
|
82
|
+
@db = index
|
83
|
+
write "SELECT #{index}\r\n"
|
84
|
+
get_response
|
85
|
+
end
|
86
|
+
|
87
|
+
def flush_db
|
88
|
+
write "FLUSHDB\r\n"
|
89
|
+
get_response == OK
|
90
|
+
end
|
91
|
+
|
92
|
+
def flush_all
|
93
|
+
puts "Warning!\nFlushing *ALL* databases!\n5 Seconds to Hit ^C!"
|
94
|
+
trap('INT') {quit; return false}
|
95
|
+
sleep 5
|
96
|
+
write "FLUSHALL\r\n"
|
97
|
+
get_response == OK
|
98
|
+
end
|
99
|
+
|
100
|
+
def last_save
|
101
|
+
write "LASTSAVE\r\n"
|
102
|
+
get_response.to_i
|
103
|
+
end
|
104
|
+
|
105
|
+
def bgsave
|
106
|
+
write "BGSAVE\r\n"
|
107
|
+
get_response == OK
|
108
|
+
end
|
109
|
+
|
110
|
+
def info
|
111
|
+
info = {}
|
112
|
+
write("INFO\r\n")
|
113
|
+
x = get_response
|
114
|
+
x.each do |kv|
|
115
|
+
k,v = kv.split(':', 2)
|
116
|
+
k,v = k.chomp, v = v.chomp
|
117
|
+
info[k.to_sym] = v
|
118
|
+
end
|
119
|
+
info
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def bulk_reply
|
124
|
+
begin
|
125
|
+
x = read
|
126
|
+
puts "bulk_reply read value is #{x.inspect}" if $debug
|
127
|
+
return x
|
128
|
+
rescue => e
|
129
|
+
puts "error in bulk_reply #{e}" if $debug
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def write(data)
|
135
|
+
with_socket_management(@server) do |socket|
|
136
|
+
puts "writing: #{data}" if $debug
|
137
|
+
socket.write(data)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def fetch(len)
|
142
|
+
with_socket_management(@server) do |socket|
|
143
|
+
len = [0, len.to_i].max
|
144
|
+
res = socket.read(len + 2)
|
145
|
+
res = res.chomp if res
|
146
|
+
res
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def read(length = read_proto)
|
151
|
+
with_socket_management(@server) do |socket|
|
152
|
+
res = socket.read(length)
|
153
|
+
puts "read is #{res.inspect}" if $debug
|
154
|
+
res
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def keys(glob)
|
159
|
+
write "KEYS #{glob}\r\n"
|
160
|
+
get_response.split(' ')
|
161
|
+
end
|
162
|
+
|
163
|
+
def rename!(oldkey, newkey)
|
164
|
+
write "RENAME #{oldkey} #{newkey}\r\n"
|
165
|
+
get_response
|
166
|
+
end
|
167
|
+
|
168
|
+
def rename(oldkey, newkey)
|
169
|
+
write "RENAMENX #{oldkey} #{newkey}\r\n"
|
170
|
+
case get_response
|
171
|
+
when -1
|
172
|
+
raise RedisRenameError, "source key: #{oldkey} does not exist"
|
173
|
+
when 0
|
174
|
+
raise RedisRenameError, "target key: #{oldkey} already exists"
|
175
|
+
when -3
|
176
|
+
raise RedisRenameError, "source and destination keys are the same"
|
177
|
+
when 1
|
178
|
+
true
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def key?(key)
|
183
|
+
write "EXISTS #{key}\r\n"
|
184
|
+
get_response == 1
|
185
|
+
end
|
186
|
+
|
187
|
+
def delete(key)
|
188
|
+
write "DEL #{key}\r\n"
|
189
|
+
get_response == 1
|
190
|
+
end
|
191
|
+
|
192
|
+
def [](key)
|
193
|
+
get(key)
|
194
|
+
end
|
195
|
+
|
196
|
+
def get(key)
|
197
|
+
write "GET #{key}\r\n"
|
198
|
+
get_response
|
199
|
+
end
|
200
|
+
|
201
|
+
def mget(*keys)
|
202
|
+
write "MGET #{keys.join(' ')}\r\n"
|
203
|
+
get_response
|
204
|
+
end
|
205
|
+
|
206
|
+
def incr(key, increment=nil)
|
207
|
+
if increment
|
208
|
+
write "INCRBY #{key} #{increment}\r\n"
|
209
|
+
else
|
210
|
+
write "INCR #{key}\r\n"
|
211
|
+
end
|
212
|
+
get_response
|
213
|
+
end
|
214
|
+
|
215
|
+
def decr(key, decrement=nil)
|
216
|
+
if decrement
|
217
|
+
write "DECRBY #{key} #{decrement}\r\n"
|
218
|
+
else
|
219
|
+
write "DECR #{key}\r\n"
|
220
|
+
end
|
221
|
+
get_response
|
222
|
+
end
|
223
|
+
|
224
|
+
def randkey
|
225
|
+
write "RANDOMKEY\r\n"
|
226
|
+
get_response
|
227
|
+
end
|
228
|
+
|
229
|
+
def list_length(key)
|
230
|
+
write "LLEN #{key}\r\n"
|
231
|
+
case i = get_response
|
232
|
+
when -2
|
233
|
+
raise RedisError, "key: #{key} does not hold a list value"
|
234
|
+
else
|
235
|
+
i
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def type?(key)
|
240
|
+
write "TYPE #{key}\r\n"
|
241
|
+
get_response
|
242
|
+
end
|
243
|
+
|
244
|
+
def push_tail(key, string)
|
245
|
+
write "RPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
|
246
|
+
get_response
|
247
|
+
end
|
248
|
+
|
249
|
+
def push_head(key, string)
|
250
|
+
write "LPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
|
251
|
+
get_response
|
252
|
+
end
|
253
|
+
|
254
|
+
def pop_head(key)
|
255
|
+
write "LPOP #{key}\r\n"
|
256
|
+
get_response
|
257
|
+
end
|
258
|
+
|
259
|
+
def pop_tail(key)
|
260
|
+
write "RPOP #{key}\r\n"
|
261
|
+
get_response
|
262
|
+
end
|
263
|
+
|
264
|
+
def list_set(key, index, val)
|
265
|
+
write "LSET #{key} #{index} #{val.to_s.size}\r\n#{val}\r\n"
|
266
|
+
get_response == OK
|
267
|
+
end
|
268
|
+
|
269
|
+
def list_range(key, start, ending)
|
270
|
+
write "LRANGE #{key} #{start} #{ending}\r\n"
|
271
|
+
get_response
|
272
|
+
end
|
273
|
+
|
274
|
+
def list_trim(key, start, ending)
|
275
|
+
write "LTRIM #{key} #{start} #{ending}\r\n"
|
276
|
+
get_response
|
277
|
+
end
|
278
|
+
|
279
|
+
def list_index(key, index)
|
280
|
+
write "LINDEX #{key} #{index}\r\n"
|
281
|
+
get_response
|
282
|
+
end
|
283
|
+
|
284
|
+
def list_rm(key, count, value)
|
285
|
+
write "LREM #{key} #{count} #{value.to_s.size}\r\n#{value}\r\n"
|
286
|
+
case num = get_response
|
287
|
+
when -1
|
288
|
+
raise RedisError, "key: #{key} does not exist"
|
289
|
+
when -2
|
290
|
+
raise RedisError, "key: #{key} does not hold a list value"
|
291
|
+
else
|
292
|
+
num
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def set_add(key, member)
|
297
|
+
write "SADD #{key} #{member.to_s.size}\r\n#{member}\r\n"
|
298
|
+
case get_response
|
299
|
+
when 1
|
300
|
+
true
|
301
|
+
when 0
|
302
|
+
false
|
303
|
+
when -2
|
304
|
+
raise RedisError, "key: #{key} contains a non set value"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def set_delete(key, member)
|
309
|
+
write "SREM #{key} #{member.to_s.size}\r\n#{member}\r\n"
|
310
|
+
case get_response
|
311
|
+
when 1
|
312
|
+
true
|
313
|
+
when 0
|
314
|
+
false
|
315
|
+
when -2
|
316
|
+
raise RedisError, "key: #{key} contains a non set value"
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def set_count(key)
|
321
|
+
write "SCARD #{key}\r\n"
|
322
|
+
case i = get_response
|
323
|
+
when -2
|
324
|
+
raise RedisError, "key: #{key} contains a non set value"
|
325
|
+
else
|
326
|
+
i
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def set_member?(key, member)
|
331
|
+
write "SISMEMBER #{key} #{member.to_s.size}\r\n#{member}\r\n"
|
332
|
+
case get_response
|
333
|
+
when 1
|
334
|
+
true
|
335
|
+
when 0
|
336
|
+
false
|
337
|
+
when -2
|
338
|
+
raise RedisError, "key: #{key} contains a non set value"
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def set_members(key)
|
343
|
+
write "SMEMBERS #{key}\r\n"
|
344
|
+
Set.new(get_response)
|
345
|
+
end
|
346
|
+
|
347
|
+
def set_intersect(*keys)
|
348
|
+
write "SINTER #{keys.join(' ')}\r\n"
|
349
|
+
Set.new(get_response)
|
350
|
+
end
|
351
|
+
|
352
|
+
def set_inter_store(destkey, *keys)
|
353
|
+
write "SINTERSTORE #{destkey} #{keys.join(' ')}\r\n"
|
354
|
+
get_response
|
355
|
+
end
|
356
|
+
|
357
|
+
def set_union(*keys)
|
358
|
+
write "SUNION #{keys.join(' ')}\r\n"
|
359
|
+
Set.new(get_response)
|
360
|
+
end
|
361
|
+
|
362
|
+
def set_union_store(destkey, *keys)
|
363
|
+
write "SUNIONSTORE #{destkey} #{keys.join(' ')}\r\n"
|
364
|
+
get_response
|
365
|
+
end
|
366
|
+
|
367
|
+
def set_diff(*keys)
|
368
|
+
write "SDIFF #{keys.join(' ')}\r\n"
|
369
|
+
Set.new(get_response)
|
370
|
+
end
|
371
|
+
|
372
|
+
def set_diff_store(destkey, *keys)
|
373
|
+
write "SDIFFSTORE #{destkey} #{keys.join(' ')}\r\n"
|
374
|
+
get_response
|
375
|
+
end
|
376
|
+
|
377
|
+
def set_move(srckey, destkey, member)
|
378
|
+
write "SMOVE #{srckey} #{destkey} #{member.to_s.size}\r\n#{member}\r\n"
|
379
|
+
get_response == 1
|
380
|
+
end
|
381
|
+
|
382
|
+
def sort(key, opts={})
|
383
|
+
cmd = "SORT #{key}"
|
384
|
+
cmd << " BY #{opts[:by]}" if opts[:by]
|
385
|
+
cmd << " GET #{opts[:get]}" if opts[:get]
|
386
|
+
cmd << " INCR #{opts[:incr]}" if opts[:incr]
|
387
|
+
cmd << " DEL #{opts[:del]}" if opts[:del]
|
388
|
+
cmd << " DECR #{opts[:decr]}" if opts[:decr]
|
389
|
+
cmd << " #{opts[:order]}" if opts[:order]
|
390
|
+
cmd << " LIMIT #{opts[:limit].join(' ')}" if opts[:limit]
|
391
|
+
cmd << "\r\n"
|
392
|
+
write(cmd)
|
393
|
+
get_response
|
394
|
+
end
|
395
|
+
|
396
|
+
def multi_bulk
|
397
|
+
res = read_proto
|
398
|
+
puts "mb res is #{res.inspect}" if $debug
|
399
|
+
list = []
|
400
|
+
Integer(res).times do
|
401
|
+
vf = get_response
|
402
|
+
puts "curren vf is #{vf.inspect}" if $debug
|
403
|
+
list << vf
|
404
|
+
puts "current list is #{list.inspect}" if $debug
|
405
|
+
end
|
406
|
+
list
|
407
|
+
end
|
408
|
+
|
409
|
+
def get_reply
|
410
|
+
begin
|
411
|
+
r = read(1)
|
412
|
+
raise RedisError if (r == "\r" || r == "\n")
|
413
|
+
rescue RedisError
|
414
|
+
retry
|
415
|
+
end
|
416
|
+
r
|
417
|
+
end
|
418
|
+
|
419
|
+
def []=(key, val)
|
420
|
+
set(key,val)
|
421
|
+
end
|
422
|
+
|
423
|
+
|
424
|
+
def set(key, val, expiry=nil)
|
425
|
+
write("SET #{key} #{val.to_s.size}\r\n#{val}\r\n")
|
426
|
+
s = get_response == OK
|
427
|
+
return expire(key, expiry) if s && expiry
|
428
|
+
s
|
429
|
+
end
|
430
|
+
|
431
|
+
def dbsize
|
432
|
+
write("DBSIZE\r\n")
|
433
|
+
get_response
|
434
|
+
end
|
435
|
+
|
436
|
+
def expire(key, expiry=nil)
|
437
|
+
write("EXPIRE #{key} #{expiry}\r\n")
|
438
|
+
get_response == 1
|
439
|
+
end
|
440
|
+
|
441
|
+
def set_unless_exists(key, val)
|
442
|
+
write "SETNX #{key} #{val.to_s.size}\r\n#{val}\r\n"
|
443
|
+
get_response == 1
|
444
|
+
end
|
445
|
+
|
446
|
+
def status_code_reply
|
447
|
+
begin
|
448
|
+
res = read_proto
|
449
|
+
if res.index('-') == 0
|
450
|
+
raise RedisError, res
|
451
|
+
else
|
452
|
+
true
|
453
|
+
end
|
454
|
+
rescue RedisError
|
455
|
+
raise RedisError
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
def get_response
|
460
|
+
begin
|
461
|
+
rtype = get_reply
|
462
|
+
rescue => e
|
463
|
+
raise RedisError, e.inspect
|
464
|
+
end
|
465
|
+
puts "reply_type is #{rtype.inspect}" if $debug
|
466
|
+
case rtype
|
467
|
+
when SINGLE
|
468
|
+
single_line
|
469
|
+
when BULK
|
470
|
+
bulk_reply
|
471
|
+
when MULTI
|
472
|
+
multi_bulk
|
473
|
+
when INT
|
474
|
+
integer_reply
|
475
|
+
when ERR
|
476
|
+
raise RedisError, single_line
|
477
|
+
else
|
478
|
+
raise RedisError, "Unknown response.."
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
def integer_reply
|
483
|
+
Integer(read_proto)
|
484
|
+
end
|
485
|
+
|
486
|
+
def single_line
|
487
|
+
buff = ""
|
488
|
+
while buff[-2..-1] != "\r\n"
|
489
|
+
buff << read(1)
|
490
|
+
end
|
491
|
+
puts "single_line value is #{buff[0..-3].inspect}" if $debug
|
492
|
+
buff[0..-3]
|
493
|
+
end
|
494
|
+
|
495
|
+
def read_socket
|
496
|
+
begin
|
497
|
+
socket = @server.socket
|
498
|
+
while res = socket.read(8096)
|
499
|
+
break if res.size != 8096
|
500
|
+
end
|
501
|
+
#Timeout or server down
|
502
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED => e
|
503
|
+
server.close
|
504
|
+
puts "Client (#{server.inspect}) disconnected from server: #{e.inspect}\n" if $debug
|
505
|
+
retry
|
506
|
+
rescue Timeout::Error => e
|
507
|
+
#BTM - Ignore this error so we don't go into an endless loop
|
508
|
+
puts "Client (#{server.inspect}) Timeout\n" if $debug
|
509
|
+
#Server down
|
510
|
+
rescue NoMethodError => e
|
511
|
+
puts "Client (#{server.inspect}) tryin server that is down: #{e.inspect}\n Dying!" if $debug
|
512
|
+
raise Errno::ECONNREFUSED
|
513
|
+
#exit
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
def read_proto
|
518
|
+
with_socket_management(@server) do |socket|
|
519
|
+
if res = socket.gets
|
520
|
+
x = res.chomp
|
521
|
+
puts "read_proto is #{x.inspect}\n\n" if $debug
|
522
|
+
x.to_i
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
end
|
data/lib/server.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
begin
|
2
|
+
# Timeout code is courtesy of Ruby memcache-client
|
3
|
+
# http://github.com/mperham/memcache-client/tree
|
4
|
+
# Try to use the SystemTimer gem instead of Ruby's timeout library
|
5
|
+
# when running on something that looks like Ruby 1.8.x. See:
|
6
|
+
# http://ph7spot.com/articles/system_timer
|
7
|
+
# We don't want to bother trying to load SystemTimer on jruby and
|
8
|
+
# ruby 1.9+.
|
9
|
+
if defined?(JRUBY_VERSION) || (RUBY_VERSION >= '1.9')
|
10
|
+
require 'timeout'
|
11
|
+
RedisTimer = Timeout
|
12
|
+
else
|
13
|
+
require 'system_timer'
|
14
|
+
RedisTimer = SystemTimer
|
15
|
+
end
|
16
|
+
rescue LoadError => e
|
17
|
+
puts "[redis-rb] Could not load SystemTimer gem, falling back to Ruby's slower/unsafe timeout library: #{e.message}"
|
18
|
+
require 'timeout'
|
19
|
+
RedisTimer = Timeout
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# This class represents a redis server instance.
|
24
|
+
|
25
|
+
class Server
|
26
|
+
|
27
|
+
##
|
28
|
+
# The amount of time to wait before attempting to re-establish a
|
29
|
+
# connection with a server that is marked dead.
|
30
|
+
|
31
|
+
RETRY_DELAY = 30.0
|
32
|
+
|
33
|
+
##
|
34
|
+
# The host the redis server is running on.
|
35
|
+
|
36
|
+
attr_reader :host
|
37
|
+
|
38
|
+
##
|
39
|
+
# The port the redis server is listening on.
|
40
|
+
|
41
|
+
attr_reader :port
|
42
|
+
|
43
|
+
##
|
44
|
+
#
|
45
|
+
|
46
|
+
attr_reader :replica
|
47
|
+
|
48
|
+
##
|
49
|
+
# The time of next retry if the connection is dead.
|
50
|
+
|
51
|
+
attr_reader :retry
|
52
|
+
|
53
|
+
##
|
54
|
+
# A text status string describing the state of the server.
|
55
|
+
|
56
|
+
attr_reader :status
|
57
|
+
|
58
|
+
##
|
59
|
+
# Create a new Redis::Server object for the redis instance
|
60
|
+
# listening on the given host and port.
|
61
|
+
|
62
|
+
def initialize(host, port = DEFAULT_PORT, timeout = 10)
|
63
|
+
raise ArgumentError, "No host specified" if host.nil? or host.empty?
|
64
|
+
raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero?
|
65
|
+
|
66
|
+
@host = host
|
67
|
+
@port = port.to_i
|
68
|
+
|
69
|
+
@sock = nil
|
70
|
+
@retry = nil
|
71
|
+
@status = 'NOT CONNECTED'
|
72
|
+
@timeout = timeout
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Return a string representation of the server object.
|
77
|
+
def inspect
|
78
|
+
"<Redis::Server: %s:%d (%s)>" % [@host, @port, @status]
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Try to connect to the redis server targeted by this object.
|
83
|
+
# Returns the connected socket object on success or nil on failure.
|
84
|
+
|
85
|
+
def socket
|
86
|
+
return @sock if @sock and not @sock.closed?
|
87
|
+
|
88
|
+
@sock = nil
|
89
|
+
|
90
|
+
# If the host was dead, don't retry for a while.
|
91
|
+
return if @retry and @retry > Time.now
|
92
|
+
|
93
|
+
# Attempt to connect if not already connected.
|
94
|
+
begin
|
95
|
+
@sock = connect_to(@host, @port, @timeout)
|
96
|
+
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
97
|
+
@retry = nil
|
98
|
+
@status = 'CONNECTED'
|
99
|
+
rescue Errno::EPIPE, Errno::ECONNREFUSED => e
|
100
|
+
puts "Socket died... socket: #{@sock.inspect}\n" if $debug
|
101
|
+
@sock.close
|
102
|
+
retry
|
103
|
+
rescue SocketError, SystemCallError, IOError => err
|
104
|
+
puts "Unable to open socket: #{err.class.name}, #{err.message}" if $debug
|
105
|
+
mark_dead err
|
106
|
+
end
|
107
|
+
@sock
|
108
|
+
end
|
109
|
+
|
110
|
+
def connect_to(host, port, timeout=nil)
|
111
|
+
socket = TCPSocket.new(host, port, 0)
|
112
|
+
if timeout
|
113
|
+
socket.instance_eval <<-EOR
|
114
|
+
alias :blocking_gets :gets
|
115
|
+
def gets(*args)
|
116
|
+
RedisTimer.timeout(#{timeout}) do
|
117
|
+
self.blocking_gets(*args)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
alias :blocking_read :read
|
121
|
+
def read(*args)
|
122
|
+
RedisTimer.timeout(#{timeout}) do
|
123
|
+
self.blocking_read(*args)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
alias :blocking_write :write
|
127
|
+
def write(*args)
|
128
|
+
RedisTimer.timeout(#{timeout}) do
|
129
|
+
self.blocking_write(*args)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
EOR
|
133
|
+
end
|
134
|
+
socket
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Close the connection to the redis server targeted by this
|
139
|
+
# object. The server is not considered dead.
|
140
|
+
|
141
|
+
def close
|
142
|
+
@sock.close if @sock && !@sock.closed?
|
143
|
+
@sock = nil
|
144
|
+
@retry = nil
|
145
|
+
@status = "NOT CONNECTED"
|
146
|
+
end
|
147
|
+
|
148
|
+
##
|
149
|
+
# Mark the server as dead and close its socket.
|
150
|
+
def mark_dead(error)
|
151
|
+
@sock.close if @sock && !@sock.closed?
|
152
|
+
@sock = nil
|
153
|
+
@retry = Time.now #+ RETRY_DELAY
|
154
|
+
|
155
|
+
reason = "#{error.class.name}: #{error.message}"
|
156
|
+
@status = sprintf "%s:%s DEAD (%s), will retry at %s", @host, @port, reason, @retry
|
157
|
+
puts @status
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|