ninjudd-ninjudd-memcache-client 1.5.0.3

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/memcache.rb ADDED
@@ -0,0 +1,791 @@
1
+ $TESTING = defined?($TESTING) && $TESTING
2
+
3
+ require 'socket'
4
+ require 'thread'
5
+ require 'timeout'
6
+ require 'rubygems'
7
+
8
+ class String
9
+
10
+ ##
11
+ # Uses the ITU-T polynomial in the CRC32 algorithm.
12
+ begin
13
+ require 'crc32'
14
+ def crc32_ITU_T
15
+ CRC32.itu_t(self)
16
+ end
17
+ rescue LoadError => e
18
+ puts "Loading with slow CRC32 ITU-T implementation: #{e.message}"
19
+
20
+ def crc32_ITU_T
21
+ n = length
22
+ r = 0xFFFFFFFF
23
+
24
+ n.times do |i|
25
+ r ^= self[i]
26
+ 8.times do
27
+ if (r & 1) != 0 then
28
+ r = (r>>1) ^ 0xEDB88320
29
+ else
30
+ r >>= 1
31
+ end
32
+ end
33
+ end
34
+
35
+ r ^ 0xFFFFFFFF
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ ##
42
+ # A Ruby client library for memcached.
43
+ #
44
+ # This is intended to provide access to basic memcached functionality. It
45
+ # does not attempt to be complete implementation of the entire API, but it is
46
+ # approaching a complete implementation.
47
+
48
+ class MemCache
49
+
50
+ ##
51
+ # The version of MemCache you are using.
52
+
53
+ VERSION = '1.5.0.1'
54
+
55
+ ##
56
+ # Default options for the cache object.
57
+
58
+ DEFAULT_OPTIONS = {
59
+ :namespace => nil,
60
+ :readonly => false,
61
+ :multithread => false,
62
+ }
63
+
64
+ ##
65
+ # Default memcached port.
66
+
67
+ DEFAULT_PORT = 11211
68
+
69
+ ##
70
+ # Default memcached server weight.
71
+
72
+ DEFAULT_WEIGHT = 1
73
+
74
+ ##
75
+ # The amount of time to wait for a response from a memcached server. If a
76
+ # response is not completed within this time, the connection to the server
77
+ # will be closed and an error will be raised.
78
+
79
+ attr_accessor :request_timeout
80
+
81
+ ##
82
+ # The namespace for this instance
83
+
84
+ attr_reader :namespace
85
+
86
+ ##
87
+ # The multithread setting for this instance
88
+
89
+ attr_reader :multithread
90
+
91
+ ##
92
+ # The servers this client talks to. Play at your own peril.
93
+
94
+ attr_reader :servers
95
+
96
+ ##
97
+ # Accepts a list of +servers+ and a list of +opts+. +servers+ may be
98
+ # omitted. See +servers=+ for acceptable server list arguments.
99
+ #
100
+ # Valid options for +opts+ are:
101
+ #
102
+ # [:namespace] Prepends this value to all keys added or retrieved.
103
+ # [:readonly] Raises an exeception on cache writes when true.
104
+ # [:multithread] Wraps cache access in a Mutex for thread safety.
105
+ #
106
+ # Other options are ignored.
107
+
108
+ def initialize(*args)
109
+ servers = []
110
+ opts = {}
111
+
112
+ case args.length
113
+ when 0 then # NOP
114
+ when 1 then
115
+ arg = args.shift
116
+ case arg
117
+ when Hash then opts = arg
118
+ when Array then servers = arg
119
+ when String then servers = [arg]
120
+ else raise ArgumentError, 'first argument must be Array, Hash or String'
121
+ end
122
+ when 2 then
123
+ servers, opts = args
124
+ else
125
+ raise ArgumentError, "wrong number of arguments (#{args.length} for 2)"
126
+ end
127
+
128
+ opts = DEFAULT_OPTIONS.merge opts
129
+ @namespace = opts[:namespace]
130
+ @readonly = opts[:readonly]
131
+ @multithread = opts[:multithread]
132
+ @mutex = Mutex.new if @multithread
133
+ @buckets = []
134
+ self.servers = servers
135
+ end
136
+
137
+ ##
138
+ # Returns a string representation of the cache object.
139
+
140
+ def inspect
141
+ "<MemCache: %d servers, %d buckets, ns: %p, ro: %p>" %
142
+ [@servers.length, @buckets.length, @namespace, @readonly]
143
+ end
144
+
145
+ ##
146
+ # Returns whether there is at least one active server for the object.
147
+
148
+ def active?
149
+ not @servers.empty?
150
+ end
151
+
152
+ ##
153
+ # Returns whether or not the cache object was created read only.
154
+
155
+ def readonly?
156
+ @readonly
157
+ end
158
+
159
+ ##
160
+ # Set the servers that the requests will be distributed between. Entries
161
+ # can be either strings of the form "hostname:port" or
162
+ # "hostname:port:weight" or MemCache::Server objects.
163
+
164
+ def servers=(servers)
165
+ # Create the server objects.
166
+ @servers = servers.collect do |server|
167
+ case server
168
+ when String
169
+ host, port, weight = server.split ':', 3
170
+ port ||= DEFAULT_PORT
171
+ weight ||= DEFAULT_WEIGHT
172
+ Server.new self, host, port, weight
173
+ when Server
174
+ if server.memcache.multithread != @multithread then
175
+ raise ArgumentError, "can't mix threaded and non-threaded servers"
176
+ end
177
+ server
178
+ else
179
+ raise TypeError, "cannot convert #{server.class} into MemCache::Server"
180
+ end
181
+ end
182
+
183
+ # Create an array of server buckets for weight selection of servers.
184
+ @buckets = []
185
+ @servers.each do |server|
186
+ server.weight.times { @buckets.push(server) }
187
+ end
188
+ end
189
+
190
+ ##
191
+ # Decrements the value for +key+ by +amount+ and returns the new value.
192
+ # +key+ must already exist. If +key+ is not an integer, it is assumed to be
193
+ # 0. +key+ can not be decremented below 0.
194
+
195
+ def decr(key, amount = 1)
196
+ raise MemCacheError, "Update of readonly cache" if @readonly
197
+ with_server(key) do |server, cache_key|
198
+ cache_decr server, cache_key, amount
199
+ end
200
+ rescue TypeError => err
201
+ handle_error server, err
202
+ end
203
+
204
+ ##
205
+ # Retrieves +key+ from memcache. If +raw+ is false, the value will be
206
+ # unmarshalled.
207
+
208
+ def get(key, raw = false)
209
+ with_server(key) do |server, cache_key|
210
+ value = cache_get server, cache_key
211
+ return nil if value.nil?
212
+ value = Marshal.load value unless raw
213
+ return value
214
+ end
215
+ rescue TypeError => err
216
+ handle_error server, err
217
+ end
218
+
219
+ ##
220
+ # Retrieves multiple values from memcached in parallel, if possible.
221
+ #
222
+ # The memcached protocol supports the ability to retrieve multiple
223
+ # keys in a single request. Pass in an array of keys to this method
224
+ # and it will:
225
+ #
226
+ # 1. map the key to the appropriate memcached server
227
+ # 2. send a single request to each server that has one or more key values
228
+ #
229
+ # Returns a hash of values.
230
+ #
231
+ # cache["a"] = 1
232
+ # cache["b"] = 2
233
+ # cache.get_multi "a", "b" # => { "a" => 1, "b" => 2 }
234
+
235
+ def get_multi(*keys)
236
+ raise MemCacheError, 'No active servers' unless active?
237
+
238
+ keys.flatten!
239
+ key_count = keys.length
240
+ cache_keys = {}
241
+ server_keys = Hash.new { |h,k| h[k] = [] }
242
+
243
+ # map keys to servers
244
+ keys.each do |key|
245
+ server, cache_key = request_setup key
246
+ cache_keys[cache_key] = key
247
+ server_keys[server] << cache_key
248
+ end
249
+
250
+ results = {}
251
+
252
+ server_keys.each do |server, keys|
253
+ keys = keys.join ' '
254
+ values = cache_get_multi server, keys
255
+ values.each do |key, value|
256
+ results[cache_keys[key]] = Marshal.load value
257
+ end
258
+ end
259
+
260
+ return results
261
+ rescue TypeError => err
262
+ handle_error server, err
263
+ end
264
+
265
+ ##
266
+ # Increments the value for +key+ by +amount+ and returns the new value.
267
+ # +key+ must already exist. If +key+ is not an integer, it is assumed to be
268
+ # 0.
269
+
270
+ def incr(key, amount = 1)
271
+ raise MemCacheError, "Update of readonly cache" if @readonly
272
+ with_server(key) do |server, cache_key|
273
+ cache_incr server, cache_key, amount
274
+ end
275
+ rescue TypeError => err
276
+ handle_error server, err
277
+ end
278
+
279
+ ##
280
+ # Add +key+ to the cache with value +value+ that expires in +expiry+
281
+ # seconds. If +raw+ is true, +value+ will not be Marshalled.
282
+ #
283
+ # Warning: Readers should not call this method in the event of a cache miss;
284
+ # see MemCache#add.
285
+
286
+ def set(key, value, expiry = 0, raw = false)
287
+ raise MemCacheError, "Update of readonly cache" if @readonly
288
+ with_server(key) do |server, cache_key|
289
+
290
+ value = Marshal.dump value unless raw
291
+ command = "set #{cache_key} 0 #{expiry} #{value.to_s.size}\r\n#{value}\r\n"
292
+
293
+ with_socket_management(server) do |socket|
294
+ socket.write command
295
+ result = socket.gets
296
+ if result.nil?
297
+ server.close
298
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
299
+ end
300
+
301
+ if result =~ /^SERVER_ERROR (.*)/
302
+ server.close
303
+ raise MemCacheError, $1.strip
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ ##
310
+ # Add +key+ to the cache with value +value+ that expires in +expiry+
311
+ # seconds, but only if +key+ does not already exist in the cache.
312
+ # If +raw+ is true, +value+ will not be Marshalled.
313
+ #
314
+ # Readers should call this method in the event of a cache miss, not
315
+ # MemCache#set or MemCache#[]=.
316
+
317
+ def add(key, value, expiry = 0, raw = false)
318
+ raise MemCacheError, "Update of readonly cache" if @readonly
319
+ with_server(key) do |server, cache_key|
320
+ value = Marshal.dump value unless raw
321
+ command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
322
+
323
+ with_socket_management(server) do |socket|
324
+ socket.write command
325
+ socket.gets
326
+ end
327
+ end
328
+ end
329
+
330
+ ##
331
+ # Removes +key+ from the cache in +expiry+ seconds.
332
+
333
+ def delete(key, expiry = 0)
334
+ raise MemCacheError, "Update of readonly cache" if @readonly
335
+ server, cache_key = request_setup key
336
+
337
+ with_socket_management(server) do |socket|
338
+ socket.write "delete #{cache_key} #{expiry}\r\n"
339
+ socket.gets
340
+ end
341
+ end
342
+
343
+ ##
344
+ # Flush the cache from all memcache servers.
345
+
346
+ def flush_all
347
+ raise MemCacheError, 'No active servers' unless active?
348
+ raise MemCacheError, "Update of readonly cache" if @readonly
349
+ begin
350
+ @mutex.lock if @multithread
351
+ @servers.each do |server|
352
+ with_socket_management(server) do |socket|
353
+ socket.write "flush_all\r\n"
354
+ result = socket.gets
355
+ raise MemCacheError, $2.strip if result =~ /^(SERVER_)?ERROR(.*)/
356
+ end
357
+ end
358
+ ensure
359
+ @mutex.unlock if @multithread
360
+ end
361
+ end
362
+
363
+ ##
364
+ # Reset the connection to all memcache servers. This should be called if
365
+ # there is a problem with a cache lookup that might have left the connection
366
+ # in a corrupted state.
367
+
368
+ def reset
369
+ @servers.each { |server| server.close }
370
+ end
371
+
372
+ ##
373
+ # Returns statistics for each memcached server. An explanation of the
374
+ # statistics can be found in the memcached docs:
375
+ #
376
+ # http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
377
+ #
378
+ # Example:
379
+ #
380
+ # >> pp CACHE.stats
381
+ # {"localhost:11211"=>
382
+ # {"bytes"=>4718,
383
+ # "pid"=>20188,
384
+ # "connection_structures"=>4,
385
+ # "time"=>1162278121,
386
+ # "pointer_size"=>32,
387
+ # "limit_maxbytes"=>67108864,
388
+ # "cmd_get"=>14532,
389
+ # "version"=>"1.2.0",
390
+ # "bytes_written"=>432583,
391
+ # "cmd_set"=>32,
392
+ # "get_misses"=>0,
393
+ # "total_connections"=>19,
394
+ # "curr_connections"=>3,
395
+ # "curr_items"=>4,
396
+ # "uptime"=>1557,
397
+ # "get_hits"=>14532,
398
+ # "total_items"=>32,
399
+ # "rusage_system"=>0.313952,
400
+ # "rusage_user"=>0.119981,
401
+ # "bytes_read"=>190619}}
402
+ # => nil
403
+
404
+ def stats
405
+ raise MemCacheError, "No active servers" unless active?
406
+ server_stats = {}
407
+
408
+ @servers.each do |server|
409
+ next unless server.alive?
410
+ with_socket_management(server) do |socket|
411
+ value = nil # TODO: why is this line here?
412
+ socket.write "stats\r\n"
413
+ stats = {}
414
+ while line = socket.gets do
415
+ break if line == "END\r\n"
416
+ if line =~ /^STAT ([\w]+) ([\w\.\:]+)/ then
417
+ name, value = $1, $2
418
+ stats[name] = case name
419
+ when 'version'
420
+ value
421
+ when 'rusage_user', 'rusage_system' then
422
+ seconds, microseconds = value.split(/:/, 2)
423
+ microseconds ||= 0
424
+ Float(seconds) + (Float(microseconds) / 1_000_000)
425
+ else
426
+ if value =~ /^\d+$/ then
427
+ value.to_i
428
+ else
429
+ value
430
+ end
431
+ end
432
+ end
433
+ end
434
+ server_stats["#{server.host}:#{server.port}"] = stats
435
+ end
436
+ end
437
+
438
+ server_stats
439
+ end
440
+
441
+ ##
442
+ # Shortcut to get a value from the cache.
443
+
444
+ alias [] get
445
+
446
+ ##
447
+ # Shortcut to save a value in the cache. This method does not set an
448
+ # expiration on the entry. Use set to specify an explicit expiry.
449
+
450
+ def []=(key, value)
451
+ set key, value
452
+ end
453
+
454
+ protected unless $TESTING
455
+
456
+ ##
457
+ # Create a key for the cache, incorporating the namespace qualifier if
458
+ # requested.
459
+
460
+ def make_cache_key(key)
461
+ if namespace.nil? then
462
+ key
463
+ else
464
+ "#{@namespace}:#{key}"
465
+ end
466
+ end
467
+
468
+ ##
469
+ # Pick a server to handle the request based on a hash of the key.
470
+
471
+ def get_server_for_key(key)
472
+ raise ArgumentError, "illegal character in key #{key.inspect}" if
473
+ key =~ /\s/
474
+ raise ArgumentError, "key too long #{key.inspect}" if key.length > 250
475
+ raise MemCacheError, "No servers available" if @servers.empty?
476
+ return @servers.first if @servers.length == 1
477
+
478
+ hkey = hash_for key
479
+
480
+ 20.times do |try|
481
+ server = @buckets[hkey % @buckets.nitems]
482
+ return server if server.alive?
483
+ hkey += hash_for "#{try}#{key}"
484
+ end
485
+
486
+ raise MemCacheError, "No servers available"
487
+ end
488
+
489
+ ##
490
+ # Returns an interoperable hash value for +key+. (I think, docs are
491
+ # sketchy for down servers).
492
+
493
+ def hash_for(key)
494
+ (key.crc32_ITU_T >> 16) & 0x7fff
495
+ end
496
+
497
+ ##
498
+ # Performs a raw decr for +cache_key+ from +server+. Returns nil if not
499
+ # found.
500
+
501
+ def cache_decr(server, cache_key, amount)
502
+ with_socket_management(server) do |socket|
503
+ socket.write "decr #{cache_key} #{amount}\r\n"
504
+ text = socket.gets
505
+ return nil if text == "NOT_FOUND\r\n"
506
+ return text.to_i
507
+ end
508
+ end
509
+
510
+ ##
511
+ # Fetches the raw data for +cache_key+ from +server+. Returns nil on cache
512
+ # miss.
513
+
514
+ def cache_get(server, cache_key)
515
+ with_socket_management(server) do |socket|
516
+ socket.write "get #{cache_key}\r\n"
517
+ keyline = socket.gets # "VALUE <key> <flags> <bytes>\r\n"
518
+
519
+ if keyline.nil? then
520
+ server.close
521
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}" # TODO: retry here too
522
+ end
523
+
524
+ return nil if keyline == "END\r\n"
525
+
526
+ unless keyline =~ /(\d+)\r/ then
527
+ server.close
528
+ raise MemCacheError, "unexpected response #{keyline.inspect}"
529
+ end
530
+ value = socket.read $1.to_i
531
+ socket.read 2 # "\r\n"
532
+ socket.gets # "END\r\n"
533
+ return value
534
+ end
535
+ end
536
+
537
+ ##
538
+ # Fetches +cache_keys+ from +server+ using a multi-get.
539
+
540
+ def cache_get_multi(server, cache_keys)
541
+ with_socket_management(server) do |socket|
542
+ values = {}
543
+ socket.write "get #{cache_keys}\r\n"
544
+
545
+ while keyline = socket.gets do
546
+ return values if keyline == "END\r\n"
547
+
548
+ unless keyline =~ /^VALUE (.+) (.+) (.+)/ then
549
+ server.close
550
+ raise MemCacheError, "unexpected response #{keyline.inspect}"
551
+ end
552
+
553
+ key, data_length = $1, $3
554
+ values[$1] = socket.read data_length.to_i
555
+ socket.read(2) # "\r\n"
556
+ end
557
+
558
+ server.close
559
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}" # TODO: retry here too
560
+ end
561
+ end
562
+
563
+ ##
564
+ # Performs a raw incr for +cache_key+ from +server+. Returns nil if not
565
+ # found.
566
+
567
+ def cache_incr(server, cache_key, amount)
568
+ with_socket_management(server) do |socket|
569
+ socket.write "incr #{cache_key} #{amount}\r\n"
570
+ text = socket.gets
571
+ return nil if text == "NOT_FOUND\r\n"
572
+ return text.to_i
573
+ end
574
+ end
575
+
576
+ ##
577
+ # Gets or creates a socket connected to the given server, and yields it
578
+ # to the block. If a socket error (SocketError, SystemCallError, IOError)
579
+ # or protocol error (MemCacheError) is raised by the block, closes the
580
+ # socket, attempts to connect again, and retries the block (once). If
581
+ # an error is again raised, reraises it as MemCacheError.
582
+ # If unable to connect to the server (or if in the reconnect wait period),
583
+ # raises MemCacheError - note that the socket connect code marks a server
584
+ # dead for a timeout period, so retrying does not apply to connection attempt
585
+ # failures (but does still apply to unexpectedly lost connections etc.).
586
+ # Wraps the whole lot in mutex synchronization if @multithread is true.
587
+
588
+ def with_socket_management(server, &block)
589
+ @mutex.lock if @multithread
590
+ retried = false
591
+ begin
592
+ socket = server.socket
593
+ # Raise an IndexError to show this server is out of whack.
594
+ # We'll catch it in higher-level code and attempt to restart the operation.
595
+ raise IndexError, "No connection to server (#{server.status})" if socket.nil?
596
+ block.call(socket)
597
+ rescue MemCacheError, SocketError, SystemCallError, IOError => err
598
+ handle_error(server, err) if retried || socket.nil?
599
+ retried = true
600
+ retry
601
+ end
602
+ ensure
603
+ @mutex.unlock if @multithread
604
+ end
605
+
606
+ def with_server(key)
607
+ retried = false
608
+ begin
609
+ server, cache_key = request_setup(key)
610
+ yield server, cache_key
611
+ rescue IndexError => e
612
+ if !retried && @servers.size > 1
613
+ puts "Connection to server #{server.inspect} DIED! Retrying operation..."
614
+ retried = true
615
+ retry
616
+ end
617
+ handle_error(nil, e)
618
+ end
619
+ end
620
+
621
+ ##
622
+ # Handles +error+ from +server+.
623
+
624
+ def handle_error(server, error)
625
+ raise error if error.is_a?(MemCacheError)
626
+ server.close if server
627
+ new_error = MemCacheError.new error.message
628
+ new_error.set_backtrace error.backtrace
629
+ raise new_error
630
+ end
631
+
632
+ ##
633
+ # Performs setup for making a request with +key+ from memcached. Returns
634
+ # the server to fetch the key from and the complete key to use.
635
+
636
+ def request_setup(key)
637
+ raise MemCacheError, 'No active servers' unless active?
638
+ cache_key = make_cache_key key
639
+ server = get_server_for_key cache_key
640
+ return server, cache_key
641
+ end
642
+
643
+ ##
644
+ # This class represents a memcached server instance.
645
+
646
+ class Server
647
+
648
+ ##
649
+ # The amount of time to wait to establish a connection with a memcached
650
+ # server. If a connection cannot be established within this time limit,
651
+ # the server will be marked as down.
652
+
653
+ CONNECT_TIMEOUT = 0.25
654
+
655
+ ##
656
+ # The amount of time to wait before attempting to re-establish a
657
+ # connection with a server that is marked dead.
658
+
659
+ RETRY_DELAY = 30.0
660
+
661
+ ##
662
+ # The host the memcached server is running on.
663
+
664
+ attr_reader :host
665
+
666
+ ##
667
+ # The port the memcached server is listening on.
668
+
669
+ attr_reader :port
670
+
671
+ ##
672
+ # The weight given to the server.
673
+
674
+ attr_reader :weight
675
+
676
+ ##
677
+ # The time of next retry if the connection is dead.
678
+
679
+ attr_reader :retry
680
+
681
+ ##
682
+ # A text status string describing the state of the server.
683
+
684
+ attr_reader :status
685
+
686
+ ##
687
+ # Create a new MemCache::Server object for the memcached instance
688
+ # listening on the given host and port, weighted by the given weight.
689
+
690
+ def initialize(memcache, host, port = DEFAULT_PORT, weight = DEFAULT_WEIGHT)
691
+ raise ArgumentError, "No host specified" if host.nil? or host.empty?
692
+ raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero?
693
+
694
+ @memcache = memcache
695
+ @host = host
696
+ @port = port.to_i
697
+ @weight = weight.to_i
698
+
699
+ @multithread = @memcache.multithread
700
+ @mutex = Mutex.new
701
+
702
+ @sock = nil
703
+ @retry = nil
704
+ @status = 'NOT CONNECTED'
705
+ end
706
+
707
+ ##
708
+ # Return a string representation of the server object.
709
+
710
+ def inspect
711
+ "<MemCache::Server: %s:%d [%d] (%s)>" % [@host, @port, @weight, @status]
712
+ end
713
+
714
+ ##
715
+ # Check whether the server connection is alive. This will cause the
716
+ # socket to attempt to connect if it isn't already connected and or if
717
+ # the server was previously marked as down and the retry time has
718
+ # been exceeded.
719
+
720
+ def alive?
721
+ !!socket
722
+ end
723
+
724
+ ##
725
+ # Try to connect to the memcached server targeted by this object.
726
+ # Returns the connected socket object on success or nil on failure.
727
+
728
+ def socket
729
+ @mutex.lock if @multithread
730
+ return @sock if @sock and not @sock.closed?
731
+
732
+ @sock = nil
733
+
734
+ # If the host was dead, don't retry for a while.
735
+ return if @retry and @retry > Time.now
736
+
737
+ # Attempt to connect if not already connected.
738
+ begin
739
+ @sock = timeout CONNECT_TIMEOUT do
740
+ TCPSocket.new @host, @port
741
+ end
742
+ if Socket.constants.include? 'TCP_NODELAY' then
743
+ @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
744
+ end
745
+ @retry = nil
746
+ @status = 'CONNECTED'
747
+ rescue SocketError, SystemCallError, IOError, Timeout::Error => err
748
+ mark_dead err.message
749
+ end
750
+
751
+ return @sock
752
+ ensure
753
+ @mutex.unlock if @multithread
754
+ end
755
+
756
+ ##
757
+ # Close the connection to the memcached server targeted by this
758
+ # object. The server is not considered dead.
759
+
760
+ def close
761
+ @mutex.lock if @multithread
762
+ @sock.close if @sock && !@sock.closed?
763
+ @sock = nil
764
+ @retry = nil
765
+ @status = "NOT CONNECTED"
766
+ ensure
767
+ @mutex.unlock if @multithread
768
+ end
769
+
770
+ private
771
+
772
+ ##
773
+ # Mark the server as dead and close its socket.
774
+
775
+ def mark_dead(reason = "Unknown error")
776
+ @sock.close if @sock && !@sock.closed?
777
+ @sock = nil
778
+ @retry = Time.now + RETRY_DELAY
779
+
780
+ @status = sprintf "DEAD: %s, will retry at %s", reason, @retry
781
+ end
782
+
783
+ end
784
+
785
+ ##
786
+ # Base MemCache exception class.
787
+
788
+ class MemCacheError < RuntimeError; end
789
+
790
+ end
791
+