mack-caching 0.8.1 → 0.8.2

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.
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
+