Ruby-MemCache 0.0.1

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.
@@ -0,0 +1,1266 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # A Ruby client library for memcached (memory cache daemon)
4
+ #
5
+ # == Synopsis
6
+ #
7
+ # require 'memcache'
8
+ #
9
+ # cache = MemCache::new '10.0.0.15:11211',
10
+ # '10.0.0.15:11212',
11
+ # '10.0.0.17:11211:3', # weighted
12
+ # :debug => true,
13
+ # :c_threshold => 100_000,
14
+ # :compression => false,
15
+ # :namespace => 'foo'
16
+ # cache.servers += [ "10.0.0.15:11211:5" ]
17
+ # cache.c_threshold = 10_000
18
+ # cache.compression = true
19
+ #
20
+ # # Cache simple values with simple String or Symbol keys
21
+ # cache["my_key"] = "Some value"
22
+ # cache[:other_key] = "Another value"
23
+ #
24
+ # # ...or more-complex values
25
+ # cache["object_key"] = { 'complex' => [ "object", 2, 4 ] }
26
+ #
27
+ # # ...or more-complex keys
28
+ # cache[ Time::now.to_a[1..7] ] ||= 0
29
+ #
30
+ # # ...or both
31
+ # cache[userObject] = { :attempts => 0, :edges => [], :nodes => [] }
32
+ #
33
+ # val = cache["my_key"] # => "Some value"
34
+ # val = cache["object_key"] # => {"complex" => ["object",2,4]}
35
+ # print val['complex'][2] # => 4
36
+ #
37
+ # == Notes
38
+ #
39
+ # * Symbols are stringified currently because that's the only way to guarantee
40
+ # that they hash to the same value across processes.
41
+ #
42
+ #
43
+ # == Known Bugs
44
+ #
45
+ # * If one or more memcacheds error when asked for 'map' or 'malloc' stats, it
46
+ # won't be possible to retrieve them from any of the other servers,
47
+ # either. This is due to the way that the client handles server error
48
+ # conditions, and needs rethinking.
49
+ #
50
+ #
51
+ # == Authors
52
+ #
53
+ # * Michael Granger <ged@FaerieMUD.org>
54
+ #
55
+ # Thanks to Martin Chase and Rick Bradley for peer review, bugfixes, and
56
+ # suggestions.
57
+ #
58
+ #
59
+ # == Copyright
60
+ #
61
+ # Copyright (c) 2003, 2004 The FaerieMUD Consortium. All rights reserved.
62
+ #
63
+ # This module is free software. You may use, modify, and/or redistribute this
64
+ # software under the same terms as Ruby.
65
+ #
66
+ #
67
+ # == Subversion Id
68
+ #
69
+ # $Id: memcache.rb 31 2004-11-13 17:32:54Z ged $
70
+ #
71
+
72
+ require 'io/reactor'
73
+ require 'socket'
74
+ require 'sync'
75
+ require 'timeout'
76
+ require 'zlib'
77
+ require 'uri'
78
+
79
+
80
+ ### A Ruby implementation of the 'memcached' client interface.
81
+ class MemCache
82
+ include Socket::Constants
83
+
84
+
85
+ ### Class constants
86
+
87
+ # SVN Revision
88
+ SVNRev = %q$Rev: 31 $
89
+
90
+ # SVN Id
91
+ SVNId = %q$Id: memcache.rb 31 2004-11-13 17:32:54Z ged $
92
+
93
+ # SVN URL
94
+ SVNURL = %q$URL: svn+ssh://svn.FaerieMUD.org/var/svn/RMemCache/trunk/lib/memcache.rb $
95
+
96
+ # Default compression threshold.
97
+ DefaultCThreshold = 10_000
98
+
99
+ # Default memcached port
100
+ DefaultPort = 11211
101
+
102
+ # Default 'weight' value assigned to a server.
103
+ DefaultServerWeight = 1
104
+
105
+ # Minimum percentage length compressed values have to be to be preferred
106
+ # over the uncompressed version.
107
+ MinCompressionRatio = 0.80
108
+
109
+ # Default constructor options
110
+ DefaultOptions = {
111
+ :debug => false,
112
+ :c_threshold => DefaultCThreshold,
113
+ :compression => true,
114
+ :namespace => nil,
115
+ :readonly => false,
116
+ :urlencode => true,
117
+ }
118
+
119
+ # Storage flags
120
+ F_SERIALIZED = 1
121
+ F_COMPRESSED = 2
122
+ F_ESCAPED = 4
123
+ F_NUMERIC = 8
124
+
125
+ # Line-ending
126
+ CRLF = "\r\n"
127
+
128
+ # Flags to use for the BasicSocket#send call. Note that Ruby's socket
129
+ # library doesn't define MSG_NOSIGNAL, but if it ever does it'll be used.
130
+ SendFlags = 0
131
+ SendFlags |= Socket.const_get( :MSG_NOSIGNAL ) if
132
+ Socket.const_defined?( :MSG_NOSIGNAL )
133
+
134
+ # Patterns for matching against server error replies
135
+ GENERAL_ERROR = /^ERROR\r\n/
136
+ CLIENT_ERROR = /^CLIENT_ERROR\s+([^\r\n]+)\r\n/
137
+ SERVER_ERROR = /^SERVER_ERROR\s+([^\r\n]+)\r\n/
138
+ ANY_ERROR = Regexp::union( GENERAL_ERROR, CLIENT_ERROR, SERVER_ERROR )
139
+
140
+ # Terminator regexps for the two styles of replies from memcached
141
+ LINE_TERMINATOR = Regexp::union( /\r\n$/, ANY_ERROR )
142
+ MULTILINE_TERMINATOR = Regexp::union( /^END\r\n$/, ANY_ERROR )
143
+
144
+ # Callables to convert various part of the server stats reply to appropriate
145
+ # object types.
146
+ StatConverters = {
147
+ :__default__ => lambda {|stat| Integer(stat) },
148
+ :version => lambda {|stat| stat }, # Already a String
149
+ :rusage_user => lambda {|stat|
150
+ seconds, microseconds = stat.split(/:/, 2)
151
+ Float(seconds) + (Float(microseconds) / 1_000_000)
152
+ },
153
+ :rusage_system => lambda {|stat|
154
+ seconds, microseconds = stat.split(/:/, 2)
155
+ Float(seconds) + (Float(microseconds) / 1_000_000)
156
+ }
157
+ }
158
+
159
+
160
+
161
+ #################################################################
162
+ ### I N S T A N C E M E T H O D S
163
+ #################################################################
164
+
165
+ ### Create a new memcache object that will distribute gets and sets between
166
+ ### the specified +servers+. You can also pass one or more options as hash
167
+ ### arguments. Valid options are:
168
+ ### [<b>:compression</b>]
169
+ ### Set the compression flag. See #use_compression? for more info.
170
+ ### [<b>:c_threshold</b>]
171
+ ### Set the compression threshold, in bytes. See #c_threshold for more
172
+ ### info.
173
+ ### [<b>:debug</b>]
174
+ ### Send debugging output to the object specified as a value if it
175
+ ### responds to #call, and to $deferr if set to anything else but +false+
176
+ ### or +nil+.
177
+ ### [<b>:namespace</b>]
178
+ ### If specified, all keys will have the given value prepended before
179
+ ### accessing the cache. Defaults to +nil+.
180
+ ### [<b>:urlencode</b>]
181
+ ### If this is set, all keys and values will be urlencoded. If this is not
182
+ ### set, keys and/or values with certain characters in them may generate
183
+ ### client errors when interacting with the cache, but the values used
184
+ ### will be more compatible with those set by other clients. Defaults to
185
+ ### +true+.
186
+ ### [<b>:readonly</b>]
187
+ ### If this is set, any attempt to write to the cache will generate an
188
+ ### exception. Defaults to +false+.
189
+ ### If a +block+ is given, it is used as the default hash function for
190
+ ### determining which server the key (given as an argument to the block) is
191
+ ### stored/fetched from.
192
+ def initialize( *servers, &block )
193
+ opts = servers.pop if servers.last.is_a?( Hash )
194
+ opts = DefaultOptions.merge( opts || {} )
195
+
196
+ @debug = opts[:debug]
197
+
198
+ @c_threshold = opts[:c_threshold]
199
+ @compression = opts[:compression]
200
+ @namespace = opts[:namespace]
201
+ @readonly = opts[:readonly]
202
+ @urlencode = opts[:urlencode]
203
+
204
+ @buckets = nil
205
+ @hashfunc = block || lambda {|val| val.hash}
206
+ @mutex = Sync::new
207
+
208
+ @reactor = IO::Reactor::new
209
+
210
+ # Stats is an auto-vivifying hash -- an access to a key that hasn't yet
211
+ # been created generates a new stats subhash
212
+ @stats = Hash::new {|hsh,k|
213
+ hsh[k] = {:count => 0, :utime => 0.0, :stime => 0.0}
214
+ }
215
+ @stats_callback = nil
216
+
217
+ self.servers = servers
218
+ end
219
+
220
+
221
+ ### Return a human-readable version of the cache object.
222
+ def inspect
223
+ "<MemCache: %d servers/%s buckets: ns: %p, debug: %p, cmp: %p, ro: %p>" % [
224
+ @servers.nitems,
225
+ @buckets.nil? ? "?" : @buckets.nitems,
226
+ @namespace,
227
+ @debug,
228
+ @compression,
229
+ @readonly,
230
+ ]
231
+ end
232
+
233
+
234
+ ######
235
+ public
236
+ ######
237
+
238
+ # The compression threshold setting, in bytes. Values larger than this
239
+ # threshold will be compressed by #[]= (and #set) and decompressed by #[]
240
+ # (and #get).
241
+ attr_accessor :c_threshold
242
+ alias_method :compression_threshold, :c_threshold
243
+
244
+ # Turn compression on or off temporarily.
245
+ attr_accessor :compression
246
+
247
+ # Debugging flag -- when set to +true+, debugging output will be send to
248
+ # $deferr. If set to an object which supports either #<< or #call, debugging
249
+ # output will be sent to it via this method instead (#call being
250
+ # preferred). If set to +false+ or +nil+, no debugging will be generated.
251
+ attr_accessor :debug
252
+
253
+ # The function (a Method or Proc object) which will be used to hash keys for
254
+ # determining where values are stored.
255
+ attr_accessor :hashfunc
256
+
257
+ # The Array of MemCache::Server objects that represent the memcached
258
+ # instances the client will use.
259
+ attr_reader :servers
260
+
261
+ # The namespace that will be prepended to all keys set/fetched from the
262
+ # cache.
263
+ attr_accessor :namespace
264
+
265
+ # Hash of counts of cache operations, keyed by operation (e.g., +:delete+,
266
+ # +:flush_all+, +:set+, +:add+, etc.). Each value of the hash is another
267
+ # hash with statistics for the corresponding operation:
268
+ # {
269
+ # :stime => <total system time of all calls>,
270
+ # :utime => <total user time> of all calls,
271
+ # :count => <number of calls>,
272
+ # }
273
+ attr_reader :stats
274
+
275
+ # Hash of system/user time-tuples for each op
276
+ attr_reader :times
277
+
278
+ # Settable statistics callback -- setting this to an object that responds to
279
+ # #call will cause it to be called once for each operation with the
280
+ # operation type (as a Symbol), and Struct::Tms objects created immediately
281
+ # before and after the operation.
282
+ attr_accessor :stats_callback
283
+
284
+ # The Sync mutex object for the cache
285
+ attr_reader :mutex
286
+
287
+
288
+ ### Returns +true+ if the cache was created read-only.
289
+ def readonly?
290
+ @readonly
291
+ end
292
+
293
+
294
+ ### Set the servers the memcache will distribute gets and sets
295
+ ### between. Arguments can be either Strings of the form
296
+ ### <tt>"hostname:port"</tt> (or "hostname:port:weight"), or
297
+ ### MemCache::Server objects.
298
+ def servers=( servers )
299
+ @mutex.synchronize( Sync::EX ) {
300
+ @servers = servers.collect {|svr|
301
+ self.debug_msg( "Transforming svr = %p", svr )
302
+
303
+ case svr
304
+ when String
305
+ host, port, weight = svr.split( /:/, 3 )
306
+ weight ||= DefaultServerWeight
307
+ port ||= DefaultPort
308
+ Server::new( host, port.to_i, weight )
309
+
310
+ when Array
311
+ host, port = svr[0].split(/:/, 2)
312
+ weight = svr[1] || DefaultServerWeight
313
+ port ||= DefaultPort
314
+ Server::new( host, port.to_i, weight )
315
+
316
+ when Server
317
+ svr
318
+
319
+ else
320
+ raise TypeError, "cannot convert %s to MemCache::Server" %
321
+ svr.class.name
322
+ end
323
+ }
324
+
325
+ @buckets = nil
326
+ }
327
+
328
+ return @servers # (ignored)
329
+ end
330
+
331
+
332
+ ### Returns +true+ if there is at least one active server for the receiver.
333
+ def active?
334
+ not @servers.empty?
335
+ end
336
+
337
+
338
+ ### Fetch and return the values associated with the given +keys+ from the
339
+ ### cache. Returns +nil+ for any value that wasn't in the cache.
340
+ def get( *keys )
341
+ raise MemCacheError, "no active servers" unless self.active?
342
+ hash = nil
343
+
344
+ @mutex.synchronize( Sync::SH ) {
345
+ hash = self.fetch( :get, *keys )
346
+ }
347
+
348
+ return *(hash.values_at( *keys ))
349
+ end
350
+ alias_method :[], :get
351
+
352
+
353
+ ### Fetch and return the values associated the the given +keys+ from the
354
+ ### cache as a Hash object. Returns +nil+ for any value that wasn't in the
355
+ ### cache.
356
+ def get_hash( *keys )
357
+ raise MemCacheError, "no active servers" unless self.active?
358
+ return @mutex.synchronize( Sync::SH ) {
359
+ self.fetch( :get_hash, *keys )
360
+ }
361
+ end
362
+
363
+
364
+ ### Fetch, delete, and return the given +keys+ atomically from the cache.
365
+ #def take( *keys )
366
+ # raise MemCacheError, "no active servers" unless self.active?
367
+ # raise MemCacheError, "readonly cache" if self.readonly?
368
+ #
369
+ # hash = @mutex.synchronize( Sync::EX ) {
370
+ # self.fetch( :take, *keys )
371
+ # }
372
+ #
373
+ # return hash[*keys]
374
+ #end
375
+
376
+
377
+ ### Unconditionally set the entry in the cache under the given +key+ to
378
+ ### +value+, returning +true+ on success. The optional +exptime+ argument
379
+ ### specifies an expiration time for the tuple, in seconds relative to the
380
+ ### present if it's less than 60*60*24*30 (30 days), or as an absolute Unix
381
+ ### time (E.g., Time#to_i) if greater. If +exptime+ is +0+, the entry will
382
+ ### never expire.
383
+ def set( key, val, exptime=0 )
384
+ raise MemCacheError, "no active servers" unless self.active?
385
+ raise MemCacheError, "readonly cache" if self.readonly?
386
+ rval = nil
387
+
388
+ @mutex.synchronize( Sync::EX ) {
389
+ rval = self.store( :set, key, val, exptime )
390
+ }
391
+
392
+ return rval
393
+ end
394
+
395
+
396
+ ### Multi-set method; unconditionally set each key/value pair in
397
+ ### +pairs+. The call to set each value is done synchronously, but until
398
+ ### memcached supports a multi-set operation this is only a little more
399
+ ### efficient than calling #set for each pair yourself.
400
+ def set_many( pairs )
401
+ raise MemCacheError, "no active servers" unless self.active?
402
+ raise MemCacheError, "readonly cache" if self.readonly?
403
+ raise MemCacheError,
404
+ "expected an object that responds to the #each_pair message" unless
405
+ pairs.respond_to?( :each_pair )
406
+
407
+ rvals = []
408
+
409
+ # Just iterate over the pairs, setting them one-by-one until memcached
410
+ # supports multi-set.
411
+ @mutex.synchronize( Sync::EX ) {
412
+ pairs.each_pair do |key, val|
413
+ rvals << self.store( :set, key, val, 0 )
414
+ end
415
+ }
416
+
417
+ return rvals
418
+ end
419
+
420
+
421
+ ### Index assignment method. Supports slice-setting, e.g.:
422
+ ### cache[ :foo, :bar ] = 12, "darkwood"
423
+ ### This uses #set_many internally if there is more than one key, or #set if
424
+ ### there is only one.
425
+ def []=( *args )
426
+ raise MemCacheError, "no active servers" unless self.active?
427
+ raise MemCacheError, "readonly cache" if self.readonly?
428
+
429
+ # Use #set if there's only one pair
430
+ if args.length <= 2
431
+ self.set( *args )
432
+ else
433
+ # Args from a slice-style call like
434
+ # cache[ :foo, :bar ] = 1, 2
435
+ # will be passed in like:
436
+ # ( :foo, :bar, [1, 2] )
437
+ # so just shift the value part off, transpose them into a Hash and
438
+ # pass them on to #set_many.
439
+ vals = args.pop
440
+ vals = [vals] unless vals.is_a?( Array ) # Handle [:a,:b] = 1
441
+ pairs = Hash[ *([ args, vals ].transpose) ]
442
+ self.set_many( pairs )
443
+ end
444
+
445
+ # It doesn't matter what this returns, as Ruby ignores it for some
446
+ # reason.
447
+ return nil
448
+ end
449
+
450
+
451
+ ### Like #set, but only stores the tuple if it doesn't already exist.
452
+ def add( key, val, exptime=0 )
453
+ raise MemCacheError, "no active servers" unless self.active?
454
+ raise MemCacheError, "readonly cache" if self.readonly?
455
+
456
+ @mutex.synchronize( Sync::EX ) {
457
+ self.store( :add, key, val, exptime )
458
+ }
459
+ end
460
+
461
+
462
+ ### Like #set, but only stores the tuple if it already exists.
463
+ def replace( key, val, exptime=0 )
464
+ raise MemCacheError, "no active servers" unless self.active?
465
+ raise MemCacheError, "readonly cache" if self.readonly?
466
+
467
+ @mutex.synchronize( Sync::EX ) {
468
+ self.store( :replace, key, val, exptime )
469
+ }
470
+ end
471
+
472
+
473
+ ### Atomically increment the value associated with +key+ by +val+. Returns
474
+ ### +nil+ if the value doesn't exist in the cache, or the new value after
475
+ ### incrementing if it does. +val+ should be zero or greater. Overflow on
476
+ ### the server is not checked. Beware of values approaching 2**32.
477
+ def incr( key, val=1 )
478
+ raise MemCacheError, "no active servers" unless self.active?
479
+ raise MemCacheError, "readonly cache" if self.readonly?
480
+
481
+ @mutex.synchronize( Sync::EX ) {
482
+ self.incrdecr( :incr, key, val )
483
+ }
484
+ end
485
+
486
+
487
+ ### Like #incr, but decrements. Unlike #incr, underflow is checked, and new
488
+ ### values are capped at 0. If server value is 1, a decrement of 2 returns
489
+ ### 0, not -1.
490
+ def decr( key, val=1 )
491
+ raise MemCacheError, "no active servers" unless self.active?
492
+ raise MemCacheError, "readonly cache" if self.readonly?
493
+
494
+ @mutex.synchronize( Sync::EX ) {
495
+ self.incrdecr( :decr, key, val )
496
+ }
497
+ end
498
+
499
+
500
+ ### Delete the entry with the specified key, optionally at the specified
501
+ ### +time+.
502
+ def delete( key, time=nil )
503
+ raise MemCacheError, "no active servers" unless self.active?
504
+ raise MemCacheError, "readonly cache" if self.readonly?
505
+ svr = nil
506
+
507
+ res = @mutex.synchronize( Sync::EX ) {
508
+ svr = self.get_server( key )
509
+ cachekey = self.make_cache_key( key )
510
+
511
+ self.add_stat( :delete ) do
512
+ cmd = "delete %s%s" % [ cachekey, time ? " #{time.to_i}" : "" ]
513
+ self.send( svr => cmd )
514
+ end
515
+ }
516
+
517
+ res && res[svr].rstrip == "DELETED"
518
+ end
519
+
520
+
521
+ ### Mark all entries on all servers as expired.
522
+ def flush_all
523
+ raise MemCacheError, "no active servers" unless self.active?
524
+ raise MemCacheError, "readonly cache" if self.readonly?
525
+
526
+ res = @mutex.synchronize( Sync::EX ) {
527
+
528
+ # Build commandset for servers that are alive
529
+ servers = @servers.select {|svr| svr.alive? }
530
+ cmds = self.make_command_map( "flush_all", servers )
531
+
532
+ # Send them in parallel
533
+ self.add_stat( :flush_all ) {
534
+ self.send( cmds )
535
+ }
536
+ }
537
+
538
+ !res.find {|svr,st| st.rstrip != 'OK'}
539
+ end
540
+ alias_method :clear, :flush_all
541
+
542
+
543
+ ### Return a hash of statistics hashes for each of the specified +servers+.
544
+ def server_stats( servers=@servers )
545
+
546
+ # Build commandset for servers that are alive
547
+ asvrs = servers.select {|svr| svr.alive?}
548
+ cmds = self.make_command_map( "stats", asvrs )
549
+
550
+ # Send them in parallel
551
+ return self.add_stat( :server_stats ) do
552
+ self.send( cmds ) do |svr,reply|
553
+ self.parse_stats( reply )
554
+ end
555
+ end
556
+ end
557
+
558
+
559
+ ### Reset statistics on the given +servers+.
560
+ def server_reset_stats( servers=@servers )
561
+
562
+ # Build commandset for servers that are alive
563
+ asvrs = servers.select {|svr| svr.alive? }
564
+ cmds = self.make_command_map( "stats reset", asvrs )
565
+
566
+ # Send them in parallel
567
+ return self.add_stat( :server_reset_stats ) do
568
+ self.send( cmds ) do |svr,reply|
569
+ reply.rstrip == "RESET"
570
+ end
571
+ end
572
+ end
573
+
574
+
575
+ ### Return memory maps from the specified +servers+ (not supported on all
576
+ ### platforms)
577
+ def server_map_stats( servers=@servers )
578
+
579
+ # Build commandset for servers that are alive
580
+ asvrs = servers.select {|svr| svr.alive? }
581
+ cmds = self.make_command_map( "stats maps", asvrs )
582
+
583
+ # Send them in parallel
584
+ return self.add_stat( :server_map_stats ) do
585
+ self.send( cmds )
586
+ end
587
+ rescue MemCache::ServerError => err
588
+ self.debug_msg "%p doesn't support 'stats maps'" % err.server
589
+ return {}
590
+ end
591
+
592
+
593
+ ### Return malloc stats from the specified +servers+ (not supported on all
594
+ ### platforms)
595
+ def server_malloc_stats( servers=@servers )
596
+
597
+ # Build commandset for servers that are alive
598
+ asvrs = servers.select {|svr| svr.alive? }
599
+ cmds = self.make_command_map( "stats malloc", asvrs )
600
+
601
+ # Send them in parallel
602
+ return self.add_stat( :server_malloc_stats ) do
603
+ self.send( cmds ) do |svr,reply|
604
+ self.parse_stats( reply )
605
+ end
606
+ end
607
+ rescue MemCache::InternalError
608
+ self.debug_msg( "One or more servers doesn't support 'stats malloc'" )
609
+ return {}
610
+ end
611
+
612
+
613
+ ### Return slab stats from the specified +servers+
614
+ def server_slab_stats( servers=@servers )
615
+
616
+ # Build commandset for servers that are alive
617
+ asvrs = servers.select {|svr| svr.alive? }
618
+ cmds = self.make_command_map( "stats slabs", asvrs )
619
+
620
+ # Send them in parallel
621
+ return self.add_stat( :server_slab_stats ) do
622
+ self.send( cmds ) do |svr,reply|
623
+ ### :TODO: I could parse the results from this further to split
624
+ ### out the individual slabs into their own sub-hashes, but this
625
+ ### will work for now.
626
+ self.parse_stats( reply )
627
+ end
628
+ end
629
+ end
630
+
631
+
632
+ ### Return item stats from the specified +servers+
633
+ def server_item_stats( servers=@servers )
634
+
635
+ # Build commandset for servers that are alive
636
+ asvrs = servers.select {|svr| svr.alive? }
637
+ cmds = self.make_command_map( "stats items", asvrs )
638
+
639
+ # Send them in parallel
640
+ return self.add_stat( :server_stats_items ) do
641
+ self.send( cmds ) do |svr,reply|
642
+ self.parse_stats( reply )
643
+ end
644
+ end
645
+ end
646
+
647
+
648
+ ### Return item size stats from the specified +servers+
649
+ def server_size_stats( servers=@servers )
650
+
651
+ # Build commandset for servers that are alive
652
+ asvrs = servers.select {|svr| svr.alive? }
653
+ cmds = self.make_command_map( "stats sizes", asvrs )
654
+
655
+ # Send them in parallel
656
+ return self.add_stat( :server_stats_sizes ) do
657
+ self.send( cmds ) do |svr,reply|
658
+ reply.sub( /#{CRLF}END#{CRLF}/, '' ).split( /#{CRLF}/ )
659
+ end
660
+ end
661
+ end
662
+
663
+
664
+
665
+ #########
666
+ protected
667
+ #########
668
+
669
+ ### Create a hash mapping the specified command to each of the given
670
+ ### +servers+.
671
+ def make_command_map( command, servers=@servers )
672
+ Hash[ *([servers, [command]*servers.nitems].transpose.flatten) ]
673
+ end
674
+
675
+
676
+ ### Parse raw statistics lines from a memcached 'stats' +reply+ and return a
677
+ ### Hash.
678
+ def parse_stats( reply )
679
+
680
+ # Trim off the footer
681
+ reply.sub!( /#{CRLF}END#{CRLF}/, '' )
682
+
683
+ # Make a hash out of the other values
684
+ pairs = reply.split( /#{CRLF}/ ).collect {|line|
685
+ stat, name, val = line.split(/\s+/, 3)
686
+ name = name.to_sym
687
+
688
+ if StatConverters.key?( name )
689
+ val = StatConverters[ name ].call( val )
690
+ else
691
+ val = StatConverters[ :__default__ ].call( val )
692
+ end
693
+
694
+ [name,val]
695
+ }
696
+
697
+ return Hash[ *(pairs.flatten) ]
698
+ end
699
+
700
+
701
+ ### Get the server corresponding to the given +key+.
702
+ def get_server( key )
703
+ @mutex.synchronize( Sync::SH ) {
704
+ return @servers.first if @servers.length == 1
705
+
706
+ # If the key is an integer, it's assumed to be a precomputed hash
707
+ # key so don't bother hashing it. Otherwise use the hashing function
708
+ # to come up with a hash of the key to determine which server to
709
+ # talk to
710
+ hkey = nil
711
+ if key.is_a?( Integer )
712
+ hkey = key
713
+ else
714
+ hkey = @hashfunc.call( key )
715
+ end
716
+
717
+ # Set up buckets if they haven't been already
718
+ unless @buckets
719
+ @mutex.synchronize( Sync::EX ) {
720
+ # Check again after switching to an exclusive lock
721
+ unless @buckets
722
+ @buckets = []
723
+ @servers.each do |svr|
724
+ self.debug_msg( "Adding %d buckets for %p", svr.weight, svr )
725
+ svr.weight.times { @buckets.push(svr) }
726
+ end
727
+ end
728
+ }
729
+ end
730
+
731
+ # Fetch a server for the given key, retrying if that server is
732
+ # offline
733
+ 20.times do |tries|
734
+ svr = @buckets[ (hkey + tries) % @buckets.nitems ]
735
+ break if svr.alive?
736
+ svr = nil
737
+ end
738
+
739
+ raise MemCacheError, "No servers available" if svr.nil?
740
+ return svr
741
+ }
742
+ end
743
+
744
+
745
+ ### Store the specified +value+ to the cache associated with the specified
746
+ ### +key+ and expiration time +exptime+.
747
+ def store( type, key, val, exptime )
748
+ return self.delete( key ) if val.nil?
749
+ svr = self.get_server( key )
750
+ cachekey = self.make_cache_key( key )
751
+ res = nil
752
+
753
+ self.add_stat( type ) {
754
+ # Prep the value for storage
755
+ sval, flags = self.prep_value( val )
756
+
757
+ # Form the command
758
+ cmd = []
759
+ cmd << "%s %s %d %d %d" %
760
+ [ type, cachekey, flags, exptime, sval.length ]
761
+ cmd << sval
762
+ self.debug_msg( "Storing with: %p", cmd )
763
+
764
+ # Send the command and read the reply
765
+ res = self.send( svr => cmd )
766
+ }
767
+
768
+ # Check for an appropriate server response
769
+ return (res && res[svr] && res[svr].rstrip == "STORED")
770
+ end
771
+
772
+
773
+ ### Fetch the values corresponding to the given +keys+ from the cache and
774
+ ### return them as a Hash.
775
+ def fetch( type, *keys )
776
+
777
+ # Make a hash to hold servers => commands for the keys to be fetched,
778
+ # and one to match cache keys to user keys.
779
+ map = Hash::new {|hsh,key| hsh[key] = 'get'}
780
+ cachekeys = {}
781
+
782
+ res = {}
783
+ self.add_stat( type ) {
784
+
785
+ # Map the key's server to the command to fetch its value
786
+ keys.each do |key|
787
+ svr = self.get_server( key )
788
+
789
+ ckey = self.make_cache_key( key )
790
+ cachekeys[ ckey ] = key
791
+ map[ svr ] << " " + ckey
792
+ end
793
+
794
+ # Send the commands and map the results hash into the return hash
795
+ self.send( map, true ) do |svr, reply|
796
+
797
+ # Iterate over the replies, stripping first the 'VALUE
798
+ # <cachekey> <flags> <len>' line with a regexp and then the data
799
+ # line by length as specified by the VALUE line.
800
+ while reply.sub!( /^VALUE (\S+) (\d+) (\d+)\r\n/, '' )
801
+ ckey, flags, len = $1, $2.to_i, $3.to_i
802
+
803
+ # Restore compressed and thawed values that require it.
804
+ data = reply.slice!( 0, len + 2 ) # + CRLF
805
+ rval = self.restore( data[0,len], flags )
806
+
807
+ res[ cachekeys[ckey] ] = rval
808
+ end
809
+
810
+ unless reply == "END" + CRLF
811
+ raise MemCacheError, "Malformed reply fetched from %p: %p" %
812
+ [ svr, rval ]
813
+ end
814
+ end
815
+ }
816
+
817
+ return res
818
+ end
819
+
820
+
821
+ ### Increment/decrement the value associated with +key+ on the server by
822
+ ### +val+.
823
+ def incrdecr( type, key, val )
824
+ svr = self.get_server( key )
825
+ cachekey = self.make_cache_key( key )
826
+
827
+ # Form the command, send it, and read the reply
828
+ res = self.add_stat( type ) {
829
+ cmd = "%s %s %d" % [ type, cachekey, val ]
830
+ self.send( svr => cmd )
831
+ }
832
+
833
+ # De-stringify the number if it is one and return it as an Integer, or
834
+ # nil if it isn't a number.
835
+ if /^(\d+)/.match( res[svr] )
836
+ return Integer( $1 )
837
+ else
838
+ return nil
839
+ end
840
+ end
841
+
842
+
843
+ ### Prepare the specified value +val+ for insertion into the cache,
844
+ ### serializing and compressing as necessary/configured.
845
+ def prep_value( val )
846
+ sval = nil
847
+ flags = 0
848
+
849
+ # Serialize if something other than a String, Numeric
850
+ case val
851
+ when String
852
+ sval = val.dup
853
+ when Numeric
854
+ sval = val.to_s
855
+ flags |= F_NUMERIC
856
+ else
857
+ self.debug_msg "Serializing %p" % val
858
+ sval = Marshal::dump( val )
859
+ flags |= F_SERIALIZED
860
+ end
861
+
862
+ # Compress if compression is enabled, the value exceeds the
863
+ # compression threshold, and the compressed value is smaller than
864
+ # the uncompressed version.
865
+ if @compress && sval.length > @c_threshold
866
+ zipped = Zlib::ZStream::Deflate::deflate( sval )
867
+
868
+ if zipped.length < (sval.length * MinCompressionRatio)
869
+ self.debug_msg "Using compressed value (%d/%d)" %
870
+ [ zipped.length, sval.length ]
871
+ sval = zipped
872
+ flags |= F_COMPRESSED
873
+ end
874
+ end
875
+
876
+ # Urlencode unless told not to
877
+ unless !@urlencode
878
+ sval = URI::escape( sval )
879
+ flags |= F_ESCAPED
880
+ end
881
+
882
+ return sval, flags
883
+ end
884
+
885
+
886
+ ### Restore the specified value +val+ from the form inserted into the cache,
887
+ ### given the specified +flags+.
888
+ def restore( val, flags=0 )
889
+ self.debug_msg( "Restoring value %p (flags: %d)", val, flags )
890
+ rval = val.dup
891
+
892
+ # De-urlencode
893
+ if (flags & F_ESCAPED).nonzero?
894
+ rval = URI::unescape( rval )
895
+ end
896
+
897
+ # Decompress
898
+ if (flags & F_COMPRESSED).nonzero?
899
+ rval = Zlib::ZStream::Inflate::inflate( rval )
900
+ end
901
+
902
+ # Unserialize
903
+ if (flags & F_SERIALIZED).nonzero?
904
+ rval = Marshal::load( rval )
905
+ end
906
+
907
+ if (flags & F_NUMERIC).nonzero?
908
+ if /\./.match( rval )
909
+ rval = Float( rval )
910
+ else
911
+ rval = Integer( rval )
912
+ end
913
+ end
914
+
915
+ return rval
916
+ end
917
+
918
+
919
+ ### Statistics wrapper: increment the execution count and processor times
920
+ ### for the given operation +type+ for the specified +server+.
921
+ def add_stat( type )
922
+ raise LocalJumpError, "no block given" unless block_given?
923
+
924
+ # Time the block
925
+ starttime = Process::times
926
+ res = yield
927
+ endtime = Process::times
928
+
929
+ # Add time/call stats callback
930
+ @stats[type][:count] += 1
931
+ @stats[type][:utime] += endtime.utime - starttime.utime
932
+ @stats[type][:stime] += endtime.stime - starttime.stime
933
+ @stats_callback.call( type, starttime, endtime ) if @stats_callback
934
+
935
+ return res
936
+ end
937
+
938
+
939
+ ### Write a message (formed +sprintf+-style with +fmt+ and +args+) to the
940
+ ### debugging callback in @debug, to $stderr if @debug doesn't appear to be
941
+ ### a callable object but is still +true+. If @debug is +nil+ or +false+, do
942
+ ### nothing.
943
+ def debug_msg( fmt, *args )
944
+ return unless @debug
945
+
946
+ if @debug.respond_to?( :call )
947
+ @debug.call( fmt % args )
948
+ elsif @debug.respond_to?( :<< )
949
+ @debug << "#{fmt}\n" % args
950
+ else
951
+ $stderr.puts( fmt % args )
952
+ end
953
+ end
954
+
955
+
956
+ ### Create a key for the cache from any object. Strings are used as-is,
957
+ ### Symbols are stringified, and other values use their #hash method.
958
+ def make_cache_key( key )
959
+ ck = @namespace ? "#@namespace:" : ""
960
+
961
+ case key
962
+ when String, Symbol
963
+ ck += key.to_s
964
+ else
965
+ ck += "%s" % key.hash
966
+ end
967
+
968
+ ck = URI::escape( ck ) unless !@urlencode
969
+
970
+ self.debug_msg( "Cache key for %p: %p", key, ck )
971
+ return ck
972
+ end
973
+
974
+
975
+ ### Socket IO Methods
976
+
977
+ ### Given +pairs+ of MemCache::Server objects and Strings or Arrays of
978
+ ### commands for each server, do multiplexed IO between all of them, reading
979
+ ### single-line responses.
980
+ def send( pairs, multiline=false )
981
+ raise TypeError, "type mismatch: #{pairs.class.name} given" unless
982
+ pairs.is_a?( Hash )
983
+ buffers = {}
984
+ rval = {}
985
+
986
+ # Fetch the Method object for the IO handler
987
+ handler = self.method( :handle_line_io )
988
+
989
+ # Set up the buffers and reactor for the exchange
990
+ pairs.each do |server,cmds|
991
+ unless server.alive?
992
+ rval[server] = nil
993
+ pairs.delete( server )
994
+ next
995
+ end
996
+
997
+ # Handle either Arrayish or Stringish commandsets
998
+ wbuf = cmds.respond_to?( :join ) ? cmds.join( CRLF ) : cmds.to_s
999
+ self.debug_msg( "Created command %p for %p", wbuf, server )
1000
+ wbuf += CRLF
1001
+
1002
+ # Make a buffer tuple (read/write) for the server
1003
+ buffers[server] = { :rbuf => '', :wbuf => wbuf }
1004
+
1005
+ # Register the server's socket with the reactor
1006
+ @reactor.register( server.socket, :write, :read, server,
1007
+ buffers[server], multiline, &handler )
1008
+ end
1009
+
1010
+ # Do all the IO at once
1011
+ self.debug_msg( "Reactor starting for %d IOs", @reactor.handles.length )
1012
+ @reactor.poll until @reactor.empty?
1013
+ self.debug_msg( "Reactor finished." )
1014
+
1015
+ # Build the return value, delegating the processing to a block if one
1016
+ # was given.
1017
+ pairs.each {|server,cmds|
1018
+
1019
+ # Handle protocol errors if they happen. I have no idea if this is
1020
+ # desirable/correct behavior: none of the other clients react to
1021
+ # CLIENT_ERROR or SERVER_ERROR at all; in fact, I think they'd all
1022
+ # hang on one like this one did before I added them to the
1023
+ # terminator pattern in #handle_line_io. So this may change in the
1024
+ # future if it ends up being better to just ignore errors, try to
1025
+ # cache/fetch what we can, and hope returning nil will suffice in
1026
+ # the face of error conditions
1027
+ self.handle_protocol_error( buffers[server][:rbuf], server ) if
1028
+ ANY_ERROR.match( buffers[server][:rbuf] )
1029
+
1030
+ # If the caller is doing processing on the reply, yield each buffer
1031
+ # in turn. Otherwise, just use the raw buffer as the return value
1032
+ if block_given?
1033
+ self.debug_msg( "Yielding value/s %p for %p",
1034
+ buffers[server][:rbuf], server )
1035
+ rval[server] = yield( server, buffers[server][:rbuf] )
1036
+ else
1037
+ rval[server] = buffers[server][:rbuf]
1038
+ end
1039
+ }
1040
+
1041
+ return rval
1042
+ end
1043
+
1044
+
1045
+ ### Handle an IO event +ev+ on the given +sock+ for the specified +server+,
1046
+ ### expecting single-line syntax (i.e., ends with CRLF).
1047
+ def handle_line_io( sock, ev, server, buffers, multiline=false )
1048
+ self.debug_msg( "Line IO (ml=%p) event for %p: %s: %p - %p",
1049
+ multiline, sock, ev, server, buffers )
1050
+
1051
+ # Set the terminator pattern based on whether multiline is turned on or
1052
+ # not.
1053
+ terminator = multiline ? MULTILINE_TERMINATOR : LINE_TERMINATOR
1054
+
1055
+ # Handle the event
1056
+ case ev
1057
+ when :read
1058
+ len = buffers[:rbuf].length
1059
+ buffers[:rbuf] << sock.sysread( 256 )
1060
+ self.debug_msg "Read %d bytes." % [ buffers[:rbuf].length - len ]
1061
+
1062
+ # If we've read the reply, then we're done with this socket
1063
+ # completely.
1064
+ if terminator.match( buffers[:rbuf] )
1065
+ self.debug_msg "Done with read for %p: %p", sock, buffers[:rbuf]
1066
+ @reactor.remove( sock )
1067
+ end
1068
+
1069
+ when :write
1070
+ res = sock.send( buffers[:wbuf], SendFlags )
1071
+ self.debug_msg( "Wrote %d bytes.", res )
1072
+ buffers[:wbuf].slice!( 0, res ) unless res.zero?
1073
+
1074
+ # If the write buffer's done, then we don't care about writability
1075
+ # anymore, so clear that event.
1076
+ if buffers[:wbuf].empty?
1077
+ self.debug_msg "Done with write for %p" % sock
1078
+ @reactor.disableEvents( sock, :write )
1079
+ end
1080
+
1081
+ when :err
1082
+ so_error = sock.getsockopt( SOL_SOCKET, SO_ERROR )
1083
+ self.debug_msg "Socket error on %p: %s" % [ sock, so_error ]
1084
+ @reactor.remove( sock )
1085
+ server.mark_dead( so_error )
1086
+
1087
+ else
1088
+ raise ArgumentError, "Unhandled reactor event type: #{ev}"
1089
+ end
1090
+ rescue EOFError, IOError => err
1091
+ @reactor.remove( sock )
1092
+ server.mark_dead( err.message )
1093
+ end
1094
+
1095
+
1096
+ ### Handle error messages defined in the memcached protocol. The +buffer+
1097
+ ### argument will be parsed for the error type, and, if appropriate, the
1098
+ ### error message. The +server+ argument is only used in the case of
1099
+ ### +SERVER_ERROR+, in which case the raised exception will contain that
1100
+ ### object. The +depth+ argument is used to specify the call depth from
1101
+ ### which the exception's stacktrace should be gathered.
1102
+ def handle_protocol_error( buffer, server, depth=4 )
1103
+ case buffer
1104
+ when CLIENT_ERROR
1105
+ raise ClientError, $1, caller(depth)
1106
+
1107
+ when SERVER_ERROR
1108
+ raise ServerError::new( server ), $1, caller(depth)
1109
+
1110
+ else
1111
+ raise InternalError, "Unknown internal error", caller(depth)
1112
+ end
1113
+ end
1114
+
1115
+
1116
+
1117
+ #####################################################################
1118
+ ### I N T E R I O R C L A S S E S
1119
+ #####################################################################
1120
+
1121
+ ### A Multiton datatype to represent a potential memcached server
1122
+ ### connection.
1123
+ class Server
1124
+
1125
+ # Default timeout for connections to memcached servers.
1126
+ ConnectTimeout = 0.25
1127
+
1128
+
1129
+ #############################################################
1130
+ ### I N S T A N C E M E T H O D S
1131
+ #############################################################
1132
+
1133
+ ### Create a new MemCache::Server object for the memcached instance
1134
+ ### listening on the given +host+ and +port+, weighted with the given
1135
+ ### +weight+.
1136
+ def initialize( host, port=11211, weight=DefaultServerWeight )
1137
+ if host.nil? || host.empty?
1138
+ raise ArgumentError, "Illegal host %p" % host
1139
+ elsif port.nil? || port.to_i.zero?
1140
+ raise ArgumentError, "Illegal port %p" % port
1141
+ end
1142
+
1143
+ @host = host
1144
+ @port = port
1145
+ @weight = weight
1146
+
1147
+ @sock = nil
1148
+ @retry = nil
1149
+ @status = "not yet connected"
1150
+ end
1151
+
1152
+
1153
+ ######
1154
+ public
1155
+ ######
1156
+
1157
+ # The host the memcached server is running on
1158
+ attr_reader :host
1159
+
1160
+ # The port the memcached is listening on
1161
+ attr_reader :port
1162
+
1163
+ # The weight given to the server
1164
+ attr_reader :weight
1165
+
1166
+ # The Time of next connection retry if the object is dead.
1167
+ attr_reader :retry
1168
+
1169
+ # A text status string describing the state of the server.
1170
+ attr_reader :status
1171
+
1172
+
1173
+ ### Return a string representation of the server object.
1174
+ def inspect
1175
+ return "<MemCache::Server: %s:%d [%d] (%s)>" % [
1176
+ @host,
1177
+ @port,
1178
+ @weight,
1179
+ @status,
1180
+ ]
1181
+ end
1182
+
1183
+
1184
+ ### Test the server for aliveness, returning +true+ if the object was
1185
+ ### able to connect. This will cause the socket connection to be opened
1186
+ ### if it isn't already.
1187
+ def alive?
1188
+ return !self.socket.nil?
1189
+ end
1190
+
1191
+
1192
+ ### Try to connect to the memcached targeted by this object. Returns the
1193
+ ### connected socket object on success; sets @dead and returns +nil+ on
1194
+ ### any failure.
1195
+ def socket
1196
+
1197
+ # Connect if not already connected
1198
+ unless @sock || (!@sock.nil? && @sock.closed?)
1199
+
1200
+ # If the host was dead, don't retry for a while
1201
+ if @retry
1202
+ return nil if @retry > Time::now
1203
+ end
1204
+
1205
+ # Attempt to connect,
1206
+ begin
1207
+ @sock = timeout( ConnectTimeout ) {
1208
+ TCPSocket::new( @host, @port )
1209
+ }
1210
+ @status = "connected"
1211
+ rescue SystemCallError, IOError, TimeoutError => err
1212
+ self.mark_dead( err.message )
1213
+ end
1214
+ end
1215
+
1216
+ return @sock
1217
+ end
1218
+
1219
+
1220
+ ### Mark the server as dead for 30 seconds and close its socket. The
1221
+ ### specified +reason+ will be used to construct an appropriate status
1222
+ ### message.
1223
+ def mark_dead( reason="Unknown error" )
1224
+ @sock.close if @sock && !@sock.closed?
1225
+ @sock = nil
1226
+ @retry = Time::now + ( 30 + rand(10) )
1227
+ @status = "DEAD: %s: Will retry at %s" %
1228
+ [ reason, @retry ]
1229
+ end
1230
+
1231
+
1232
+ end # class Server
1233
+
1234
+
1235
+ #################################################################
1236
+ ### E X C E P T I O N C L A S S E S
1237
+ #################################################################
1238
+
1239
+ ### Base MemCache exception class
1240
+ class MemCacheError < ::Exception
1241
+ end
1242
+
1243
+ ### MemCache internal error class -- instances of this class mean that there
1244
+ ### is some internal error either in the memcache client lib or the
1245
+ ### memcached server it's talking to.
1246
+ class InternalError < MemCacheError
1247
+ end
1248
+
1249
+ ### MemCache client error class -- this is raised if a "CLIENT_ERROR
1250
+ ### <error>\r\n" is seen in the dialog with a server.
1251
+ class ClientError < InternalError
1252
+ end
1253
+
1254
+ ### MemCache server error class -- this is raised if a "SERVER_ERROR
1255
+ ### <error>\r\n" is seen in the dialog with a server.
1256
+ class ServerError < InternalError
1257
+ def initialize( svr )
1258
+ @server = svr
1259
+ end
1260
+
1261
+ attr_reader :server
1262
+ end
1263
+
1264
+
1265
+ end # class memcache
1266
+