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