dsander-redis 1.0.6

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 ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ezra Zygmuntowicz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,112 @@
1
+ # redis-rb
2
+
3
+ A Ruby client library for the [Redis](http://code.google.com/p/redis) key-value store.
4
+
5
+ ## Information about Redis
6
+
7
+ Redis is a key-value store with some interesting features:
8
+
9
+ 1. It's fast.
10
+ 2. Keys are strings but values are typed. Currently Redis supports strings, lists, sets, sorted sets and hashes. [Atomic operations](http://code.google.com/p/redis/wiki/CommandReference) can be done on all of these types.
11
+
12
+ See [the Redis homepage](http://code.google.com/p/redis/wiki/README) for more information.
13
+
14
+ ## Getting started
15
+
16
+ You can connect to Redis by instantiating the `Redis` class:
17
+
18
+ require "redis"
19
+
20
+ redis = Redis.new
21
+
22
+ This assumes Redis was started with default values listening on `localhost`, port 6379. If you need to connect to a remote server or a different port, try:
23
+
24
+ redis = Redis.new(:host => "10.0.1.1", :port => 6380)
25
+
26
+ Once connected, you can start running commands against Redis:
27
+
28
+ >> redis.set "foo", "bar"
29
+ => "OK"
30
+
31
+ >> redis.get "foo"
32
+ => "bar"
33
+
34
+ >> redis.sadd "users", "albert"
35
+ => true
36
+
37
+ >> redis.sadd "users", "bernard"
38
+ => true
39
+
40
+ >> redis.sadd "users", "charles"
41
+ => true
42
+
43
+ How many users?
44
+
45
+ >> redis.scard "users"
46
+ => 3
47
+
48
+ Is `albert` a user?
49
+
50
+ >> redis.sismember "users", "albert"
51
+ => true
52
+
53
+ Is `isabel` a user?
54
+
55
+ >> redis.sismember "users", "isabel"
56
+ => false
57
+
58
+ Handle groups:
59
+
60
+ >> redis.sadd "admins", "albert"
61
+ => true
62
+
63
+ >> redis.sadd "admins", "isabel"
64
+ => true
65
+
66
+ Users who are also admins:
67
+
68
+ >> redis.sinter "users", "admins"
69
+ => ["albert"]
70
+
71
+ Users who are not admins:
72
+
73
+ >> redis.sdiff "users", "admins"
74
+ => ["bernard", "charles"]
75
+
76
+ Admins who are not users:
77
+
78
+ >> redis.sdiff "admins", "users"
79
+ => ["isabel"]
80
+
81
+ All users and admins:
82
+
83
+ >> redis.sunion "admins", "users"
84
+ => ["albert", "bernard", "charles", "isabel"]
85
+
86
+
87
+ ## Storing objects
88
+
89
+ Redis only stores strings as values. If you want to store an object inside a key, you can use a serialization/deseralization mechanism like JSON:
90
+
91
+ >> redis.set "foo", [1, 2, 3].to_json
92
+ => OK
93
+
94
+ >> JSON.parse(redis.get("foo"))
95
+ => [1, 2, 3]
96
+
97
+ ## Executing multiple commands atomically
98
+
99
+ You can use `MULTI/EXEC` to run arbitrary commands in an atomic fashion:
100
+
101
+ redis.multi do
102
+ redis.set "foo", "bar"
103
+ redis.incr "baz"
104
+ end
105
+
106
+ ## More info
107
+
108
+ Check the [Redis Command Reference](http://code.google.com/p/redis/wiki/CommandReference) or check the tests to find out how to use this client.
109
+
110
+ ## Contributing
111
+
112
+ [Fork the project](http://github.com/ezmobius/redis-rb) and send pull requests. You can also ask for help at `#redis` on Freenode.
data/Rakefile ADDED
@@ -0,0 +1,75 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+ require 'tasks/redis.tasks'
5
+
6
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
7
+ require 'redis'
8
+
9
+ GEM = 'dsander-redis'
10
+ GEM_NAME = 'dsander-redis'
11
+ GEM_VERSION = Redis::VERSION
12
+ AUTHORS = ['Ezra Zygmuntowicz', 'Taylor Weibley', 'Matthew Clark', 'Brian McKinney', 'Salvatore Sanfilippo', 'Luca Guidi', 'Dominik Sander']
13
+ EMAIL = "ez@engineyard.com"
14
+ HOMEPAGE = "http://github.com/dsander/redis-rb"
15
+ SUMMARY = "Ruby client library for Redis, the key value storage server"
16
+
17
+ spec = Gem::Specification.new do |s|
18
+ s.name = GEM
19
+ s.version = GEM_VERSION
20
+ s.platform = Gem::Platform::RUBY
21
+ s.has_rdoc = true
22
+ s.extra_rdoc_files = ["LICENSE"]
23
+ s.summary = SUMMARY
24
+ s.description = s.summary
25
+ s.authors = AUTHORS
26
+ s.email = EMAIL
27
+ s.homepage = HOMEPAGE
28
+ s.require_path = 'lib'
29
+ s.autorequire = GEM
30
+ s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{lib,tasks,spec}/**/*")
31
+ end
32
+
33
+ REDIS_DIR = File.expand_path(File.join("..", "test"), __FILE__)
34
+ REDIS_CNF = File.join(REDIS_DIR, "test.conf")
35
+ REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
36
+
37
+ task :default => :run
38
+
39
+ desc "Run tests and manage server start/stop"
40
+ task :run => [:start, :test, :stop]
41
+
42
+ desc "Start the Redis server"
43
+ task :start do
44
+ unless File.exists?(REDIS_PID)
45
+ system "redis-server #{REDIS_CNF}"
46
+ end
47
+ end
48
+
49
+ desc "Stop the Redis server"
50
+ task :stop do
51
+ if File.exists?(REDIS_PID)
52
+ system "kill #{File.read(REDIS_PID)}"
53
+ system "rm #{REDIS_PID}"
54
+ end
55
+ end
56
+
57
+ Rake::TestTask.new(:test) do |t|
58
+ t.pattern = 'test/**/*_test.rb'
59
+ end
60
+
61
+ Rake::GemPackageTask.new(spec) do |pkg|
62
+ pkg.gem_spec = spec
63
+ end
64
+
65
+ desc "install the gem locally"
66
+ task :install => [:package] do
67
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
68
+ end
69
+
70
+ desc "create a gemspec file"
71
+ task :gemspec do
72
+ File.open("#{GEM}.gemspec", "w") do |file|
73
+ file.puts spec.to_ruby
74
+ end
75
+ end
data/lib/edis.rb ADDED
@@ -0,0 +1,3 @@
1
+ # This file allows for the running of redis-rb with a nice
2
+ # command line look-and-feel: irb -rubygems -redis foo.rb
3
+ require 'redis'
data/lib/redis.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'socket'
2
+
3
+ class Redis
4
+ VERSION = "1.0.6"
5
+
6
+ def self.new(*attrs)
7
+ Client.new(*attrs)
8
+ end
9
+ end
10
+
11
+ begin
12
+ if RUBY_VERSION >= '1.9'
13
+ require 'timeout'
14
+ Redis::Timer = Timeout
15
+ else
16
+ require 'system_timer'
17
+ Redis::Timer = SystemTimer
18
+ end
19
+ rescue LoadError
20
+ Redis::Timer = nil
21
+ end
22
+
23
+ require 'redis/client'
24
+ require 'redis/pipeline'
25
+ require 'redis/subscribe'
@@ -0,0 +1,577 @@
1
+ class Redis
2
+ 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
+ MINUS = "-".freeze
11
+ PLUS = "+".freeze
12
+ COLON = ":".freeze
13
+ DOLLAR = "$".freeze
14
+ ASTERISK = "*".freeze
15
+
16
+ BULK_COMMANDS = {
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 = {}
67
+ r.each_line {|kv|
68
+ k,v = kv.split(":",2).map{|x| x.chomp}
69
+ info[k.to_sym] = v
70
+ }
71
+ info
72
+ },
73
+ "keys" => lambda{|r|
74
+ if r.is_a?(Array)
75
+ r
76
+ else
77
+ r.split(" ")
78
+ end
79
+ },
80
+ "hgetall" => lambda{|r|
81
+ Hash[*r]
82
+ }
83
+ }
84
+
85
+ ALIASES = {
86
+ "flush_db" => "flushdb",
87
+ "flush_all" => "flushall",
88
+ "last_save" => "lastsave",
89
+ "key?" => "exists",
90
+ "delete" => "del",
91
+ "randkey" => "randomkey",
92
+ "list_length" => "llen",
93
+ "push_tail" => "rpush",
94
+ "push_head" => "lpush",
95
+ "pop_tail" => "rpop",
96
+ "pop_head" => "lpop",
97
+ "list_set" => "lset",
98
+ "list_range" => "lrange",
99
+ "list_trim" => "ltrim",
100
+ "list_index" => "lindex",
101
+ "list_rm" => "lrem",
102
+ "set_add" => "sadd",
103
+ "set_delete" => "srem",
104
+ "set_count" => "scard",
105
+ "set_member?" => "sismember",
106
+ "set_members" => "smembers",
107
+ "set_intersect" => "sinter",
108
+ "set_intersect_store" => "sinterstore",
109
+ "set_inter_store" => "sinterstore",
110
+ "set_union" => "sunion",
111
+ "set_union_store" => "sunionstore",
112
+ "set_diff" => "sdiff",
113
+ "set_diff_store" => "sdiffstore",
114
+ "set_move" => "smove",
115
+ "set_unless_exists" => "setnx",
116
+ "rename_unless_exists" => "renamenx",
117
+ "type?" => "type",
118
+ "zset_add" => "zadd",
119
+ "zset_count" => "zcard",
120
+ "zset_range_by_score" => "zrangebyscore",
121
+ "zset_reverse_range" => "zrevrange",
122
+ "zset_range" => "zrange",
123
+ "zset_delete" => "zrem",
124
+ "zset_score" => "zscore",
125
+ "zset_incr_by" => "zincrby",
126
+ "zset_increment_by" => "zincrby"
127
+ }
128
+
129
+ DISABLED_COMMANDS = {
130
+ "monitor" => true,
131
+ "sync" => true
132
+ }
133
+
134
+ BLOCKING_COMMANDS = {
135
+ "blpop" => true,
136
+ "brpop" => true
137
+ }
138
+
139
+ def initialize(options = {})
140
+ @host = options[:host] || '127.0.0.1'
141
+ @port = (options[:port] || 6379).to_i
142
+ @db = (options[:db] || 0).to_i
143
+ @timeout = (options[:timeout] || 5).to_i
144
+ @password = options[:password]
145
+ @logger = options[:logger]
146
+ @thread_safe = options[:thread_safe]
147
+ @binary_keys = options[:binary_keys]
148
+ @evented = defined?(EM) && EM.reactor_running?
149
+ @mutex = ::Mutex.new if @thread_safe && !@evented
150
+ @sock = nil
151
+ @pubsub = false
152
+ @sock = nil
153
+ self.extend(Redis::EventedClient) if defined?(EM) and EM.reactor_running?
154
+ log(self)
155
+ end
156
+
157
+ def to_s
158
+ "Redis Client connected to #{server} against DB #{@db}"
159
+ end
160
+
161
+ def select(*args)
162
+ raise "SELECT not allowed, use the :db option when creating the object"
163
+ end
164
+
165
+ def [](key)
166
+ get(key)
167
+ end
168
+
169
+ def []=(key,value)
170
+ set(key, value)
171
+ end
172
+
173
+ def get(key)
174
+ call_command([:get, key])
175
+ end
176
+
177
+ def set(key, value, ttl = nil)
178
+ if ttl
179
+ deprecated("set with an expire", :set_with_expire, caller[0])
180
+ set_with_expire(key, value, ttl)
181
+ else
182
+ call_command([:set, key, value])
183
+ end
184
+ end
185
+
186
+ def set_with_expire(key, value, ttl)
187
+ multi do
188
+ set(key, value)
189
+ expire(key, ttl)
190
+ end
191
+ end
192
+
193
+ def mset(*args)
194
+ if args.size == 1
195
+ deprecated("mset with a hash", :mapped_mset, caller[0])
196
+ mapped_mset(args[0])
197
+ else
198
+ call_command(args.unshift(:mset))
199
+ end
200
+ end
201
+
202
+ def mapped_mset(hash)
203
+ mset(*hash.to_a.flatten)
204
+ end
205
+
206
+ def msetnx(*args)
207
+ if args.size == 1
208
+ deprecated("msetnx with a hash", :mapped_msetnx, caller[0])
209
+ mapped_msetnx(args[0])
210
+ else
211
+ call_command(args.unshift(:msetnx))
212
+ end
213
+ end
214
+
215
+ def mapped_msetnx(hash)
216
+ msetnx(*hash.to_a.flatten)
217
+ end
218
+
219
+ # Similar to memcache.rb's #get_multi, returns a hash mapping
220
+ # keys to values.
221
+ def mapped_mget(*keys)
222
+ result = {}
223
+ mget(*keys).each do |value|
224
+ key = keys.shift
225
+ result.merge!(key => value) unless value.nil?
226
+ end
227
+ result
228
+ end
229
+
230
+ def sort(key, options = {})
231
+ cmd = []
232
+ cmd << "SORT #{key}"
233
+ cmd << "BY #{options[:by]}" if options[:by]
234
+ cmd << "GET #{[options[:get]].flatten * ' GET '}" if options[:get]
235
+ cmd << "#{options[:order]}" if options[:order]
236
+ cmd << "LIMIT #{options[:limit].join(' ')}" if options[:limit]
237
+ cmd << "STORE #{options[:store]}" if options[:store]
238
+ call_command(cmd)
239
+ end
240
+
241
+ def incr(key, increment = nil)
242
+ if increment
243
+ deprecated("incr with an increment", :incrby, caller[0])
244
+ incrby(key, increment)
245
+ else
246
+ call_command([:incr, key])
247
+ end
248
+ end
249
+
250
+ def decr(key, decrement = nil)
251
+ if decrement
252
+ deprecated("decr with a decrement", :decrby, caller[0])
253
+ decrby(key, decrement)
254
+ else
255
+ call_command([:decr, key])
256
+ end
257
+ end
258
+
259
+ # Ruby defines a now deprecated type method so we need to override it here
260
+ # since it will never hit method_missing
261
+ def type(key)
262
+ call_command(['type', key])
263
+ end
264
+
265
+ def quit
266
+ call_command(['quit'])
267
+ rescue Errno::ECONNRESET
268
+ end
269
+
270
+ def pipelined(&block)
271
+ pipeline = Pipeline.new self
272
+ yield pipeline
273
+ pipeline.execute
274
+ end
275
+
276
+ def exec
277
+ # Need to override Kernel#exec.
278
+ call_command([:exec])
279
+ end
280
+
281
+ def multi(&block)
282
+ result = call_command [:multi]
283
+
284
+ return result unless block_given?
285
+
286
+ begin
287
+ yield(self)
288
+ exec
289
+ rescue Exception => e
290
+ discard
291
+ raise e
292
+ end
293
+ end
294
+
295
+ def subscribe(*classes)
296
+ # Top-level `subscribe` MUST be called with a block,
297
+ # nested `subscribe` MUST NOT be called with a block
298
+ if !@pubsub && !block_given?
299
+ raise "Top-level subscribe requires a block"
300
+ elsif @pubsub == true && block_given?
301
+ raise "Nested subscribe does not take a block"
302
+ elsif @pubsub
303
+ # If we're already pubsub'ing, just subscribe us to some more classes
304
+ call_command [:subscribe,*classes]
305
+ return true
306
+ end
307
+
308
+ @pubsub = true
309
+ call_command [:subscribe,*classes]
310
+ sub = Subscription.new
311
+ yield(sub)
312
+ begin
313
+ while true
314
+ type, *reply = read_reply # type, [class,data]
315
+ case type
316
+ when 'subscribe','unsubscribe'
317
+ sub.send(type) && sub.send(type).call(reply[0],reply[1])
318
+ when 'message'
319
+ sub.send(type) && sub.send(type).call(reply[0],reply[1])
320
+ end
321
+ break if type == 'unsubscribe' && reply[1] == 0
322
+ end
323
+ rescue RuntimeError
324
+ call_command [:unsubscribe]
325
+ raise
326
+ ensure
327
+ @pubsub = false
328
+ end
329
+ end
330
+
331
+ # Wrap raw_call_command to handle reconnection on socket error. We
332
+ # try to reconnect just one time, otherwise let the error araise.
333
+ def call_command(argv)
334
+ log(argv.inspect, :debug)
335
+
336
+ connect_to_server unless connected?
337
+
338
+ begin
339
+ raw_call_command(argv.dup)
340
+ rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED
341
+ if reconnect
342
+ raw_call_command(argv.dup)
343
+ else
344
+ raise Errno::ECONNRESET
345
+ end
346
+ end
347
+ end
348
+
349
+ def server
350
+ "#{@host}:#{@port}"
351
+ end
352
+
353
+ def connect_to(host, port)
354
+
355
+ # We support connect_to() timeout only if system_timer is availabe
356
+ # or if we are running against Ruby >= 1.9
357
+ # Timeout reading from the socket instead will be supported anyway.
358
+ if @timeout != 0 and Timer
359
+ begin
360
+ @sock = TCPSocket.new(host, port)
361
+ rescue Timeout::Error
362
+ @sock = nil
363
+ raise Timeout::Error, "Timeout connecting to the server"
364
+ end
365
+ else
366
+ @sock = TCPSocket.new(host, port)
367
+ end
368
+
369
+ @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
370
+
371
+ # If the timeout is set we set the low level socket options in order
372
+ # to make sure a blocking read will return after the specified number
373
+ # of seconds. This hack is from memcached ruby client.
374
+ set_socket_timeout!(@timeout) if @timeout
375
+
376
+ rescue Errno::ECONNREFUSED
377
+ raise Errno::ECONNREFUSED, "Unable to connect to Redis on #{host}:#{port}"
378
+ end
379
+
380
+ def connect_to_server
381
+ connect_to(@host, @port)
382
+ call_command([:auth, @password]) if @password
383
+ call_command([:select, @db]) if @db != 0
384
+ @sock
385
+ end
386
+
387
+ def method_missing(*argv)
388
+ call_command(argv)
389
+ end
390
+
391
+ def raw_call_command(argvp)
392
+ if argvp[0].is_a?(Array)
393
+ argvv = argvp
394
+ pipeline = true
395
+ else
396
+ argvv = [argvp]
397
+ pipeline = false
398
+ end
399
+
400
+ if @binary_keys or pipeline or MULTI_BULK_COMMANDS[argvv[0][0].to_s]
401
+ command = ""
402
+ argvv.each do |argv|
403
+ command << "*#{argv.size}\r\n"
404
+ argv.each{|a|
405
+ a = a.to_s
406
+ command << "$#{get_size(a)}\r\n"
407
+ command << a
408
+ command << "\r\n"
409
+ }
410
+ end
411
+ else
412
+ command = ""
413
+ argvv.each do |argv|
414
+ bulk = nil
415
+ argv[0] = argv[0].to_s
416
+ if ALIASES[argv[0]]
417
+ deprecated(argv[0], ALIASES[argv[0]], caller[4])
418
+ argv[0] = ALIASES[argv[0]]
419
+ end
420
+ raise "#{argv[0]} command is disabled" if DISABLED_COMMANDS[argv[0]]
421
+ if BULK_COMMANDS[argv[0]] and argv.length > 1
422
+ bulk = argv[-1].to_s
423
+ argv[-1] = get_size(bulk)
424
+ end
425
+ command << "#{argv.join(' ')}\r\n"
426
+ command << "#{bulk}\r\n" if bulk
427
+ end
428
+ end
429
+ # When in Pub/Sub mode we don't read replies synchronously.
430
+ if @pubsub
431
+ @sock.write(command)
432
+ return true
433
+ end
434
+ # The normal command execution is reading and processing the reply.
435
+ results = maybe_lock do
436
+ begin
437
+ set_socket_timeout!(0) if requires_timeout_reset?(argvv[0][0].to_s)
438
+ process_command(command, argvv)
439
+ ensure
440
+ set_socket_timeout!(@timeout) if requires_timeout_reset?(argvv[0][0].to_s)
441
+ end
442
+ end
443
+
444
+ return pipeline ? results : results[0]
445
+ end
446
+
447
+ def process_command(command, argvv)
448
+ @sock.write(command)
449
+ argvv.map do |argv|
450
+ processor = REPLY_PROCESSOR[argv[0].to_s]
451
+ processor ? processor.call(read_reply) : read_reply
452
+ end
453
+ end
454
+
455
+ def maybe_lock(&block)
456
+ if @thread_safe && !@evented
457
+ @mutex.synchronize(&block)
458
+ else
459
+ block.call
460
+ end
461
+ end
462
+
463
+ def read_reply
464
+
465
+ # We read the first byte using read() mainly because gets() is
466
+ # immune to raw socket timeouts.
467
+ begin
468
+ reply_type = @sock.read(1)
469
+ rescue Errno::EAGAIN
470
+
471
+ # We want to make sure it reconnects on the next command after the
472
+ # timeout. Otherwise the server may reply in the meantime leaving
473
+ # the protocol in a desync status.
474
+ disconnect
475
+
476
+ raise Errno::EAGAIN, "Timeout reading from the socket"
477
+ end
478
+
479
+ raise Errno::ECONNRESET, "Connection lost" unless reply_type
480
+
481
+ format_reply(reply_type, @sock.gets)
482
+ end
483
+
484
+
485
+ if "".respond_to?(:bytesize)
486
+ def get_size(string)
487
+ string.bytesize
488
+ end
489
+ else
490
+ def get_size(string)
491
+ string.size
492
+ end
493
+ end
494
+
495
+ private
496
+
497
+ def log(str, level = :info)
498
+ @logger.send(level, str.to_s) if @logger
499
+ end
500
+
501
+ def deprecated(old, new, trace = caller[0])
502
+ $stderr.puts "\nRedis: The method #{old} is deprecated. Use #{new} instead (in #{trace})"
503
+ end
504
+
505
+ def requires_timeout_reset?(command)
506
+ BLOCKING_COMMANDS[command] && @timeout
507
+ end
508
+
509
+ def set_socket_timeout!(timeout)
510
+ secs = Integer(timeout)
511
+ usecs = Integer((timeout - secs) * 1_000_000)
512
+ optval = [secs, usecs].pack("l_2")
513
+ begin
514
+ @sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
515
+ @sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
516
+ rescue Exception => e
517
+ # Solaris, for one, does not like/support socket timeouts.
518
+ log("Unable to use raw socket timeouts: #{e.class.name}: #{e.message}")
519
+ end
520
+ end
521
+
522
+ def connected?
523
+ !! @sock
524
+ end
525
+
526
+ def disconnect
527
+ begin
528
+ @sock.close
529
+ rescue
530
+ ensure
531
+ @sock = nil
532
+ end
533
+ true
534
+ end
535
+
536
+ def reconnect
537
+ disconnect && connect_to_server
538
+ end
539
+
540
+ def format_reply(reply_type, line)
541
+ case reply_type
542
+ when MINUS then format_error_reply(line)
543
+ when PLUS then format_status_reply(line)
544
+ when COLON then format_integer_reply(line)
545
+ when DOLLAR then format_bulk_reply(line)
546
+ when ASTERISK then format_multi_bulk_reply(line)
547
+ else raise ProtocolError.new(reply_type)
548
+ end
549
+ end
550
+
551
+ def format_error_reply(line)
552
+ raise "-" + line.strip
553
+ end
554
+
555
+ def format_status_reply(line)
556
+ line.strip
557
+ end
558
+
559
+ def format_integer_reply(line)
560
+ line.to_i
561
+ end
562
+
563
+ def format_bulk_reply(line)
564
+ bulklen = line.to_i
565
+ return if bulklen == -1
566
+ reply = @sock.read(bulklen)
567
+ @sock.read(2) # Discard CRLF.
568
+ reply
569
+ end
570
+
571
+ def format_multi_bulk_reply(line)
572
+ reply = []
573
+ line.to_i.times { reply << read_reply }
574
+ reply
575
+ end
576
+ end
577
+ end