mperham-memcache-client 1.6.0

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