mack-caching 0.8.1 → 0.8.2

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