instantcache 0.1.0a1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1254 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # = instantcache.rb - InstantCache module
4
+ #
5
+ # Author:: Ken Coar
6
+ # Copyright:: Copyright © 2011 Ken Coar
7
+ # License:: Apache Licence 2.0
8
+ #
9
+ # == Synopsis
10
+ #
11
+ # require 'rubygems'
12
+ # require 'memcache'
13
+ # require 'instantcache'
14
+ # InstantCache.cache_object = MemCache.new('127.0.0.1:11211')
15
+ #
16
+ # class Foo
17
+ # include InstantCache
18
+ # memcached_accessor(:bar)
19
+ # end
20
+ #
21
+ # f = Foo.new
22
+ # f.bar
23
+ # => nil
24
+ # f.bar = f
25
+ # => #<Foo:0xb7438c64 @bar=#<InstantCache::Blob:0xc74f882>>
26
+ # f.bar = %w( one two three )
27
+ # => ["one","two","three"]
28
+ # f.bar << :four
29
+ # => ["one","two","three",:four]
30
+ # f.bar[1,1]
31
+ # => "two"
32
+ # f.bar_destroy!
33
+ # => nil
34
+ # f.bar
35
+ # InstantCache::Destroyed: attempt to access destroyed variable
36
+ #
37
+ # == Description
38
+ #
39
+ # InstantCache provides accessor declarations, and the necessary
40
+ # underpinnings, to allow you to share instance variables across a
41
+ # memcached cluster.
42
+ #
43
+ #--
44
+ # Copyright © 2011 Ken Coar
45
+ #
46
+ # Licensed under the Apache License, Version 2.0 (the "License");
47
+ # you may not use this file except in compliance with the License.
48
+ # You may obtain a copy of the License at
49
+ #
50
+ # http://www.apache.org/licenses/LICENSE-2.0
51
+ #
52
+ # Unless required by applicable law or agreed to in writing, software
53
+ # distributed under the License is distributed on an "AS IS" BASIS,
54
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
55
+ # See the License for the specific language governing permissions and
56
+ # limitations under the License.
57
+ #++
58
+
59
+ require 'thread'
60
+ require 'memcache'
61
+ require 'versionomy'
62
+ require 'instantcache/exceptions'
63
+
64
+ require 'ruby-debug'
65
+ Debugger.start
66
+
67
+ #
68
+ # Provide accessors that actually store the 'instance variables'
69
+ # in memcached.
70
+ #
71
+ module InstantCache
72
+
73
+ #
74
+ # The base Versionomy representation of the package version.
75
+ #
76
+ Version = Versionomy.parse('0.1.0a1')
77
+
78
+ #
79
+ # The package version-as-a-string.
80
+ #
81
+ VERSION = Version.to_s.freeze
82
+
83
+ #
84
+ # Label informing accessor declarations that the variable is to be
85
+ # shared. This changes how some things are done (like default
86
+ # memcached cell names).
87
+ #
88
+ SHARED = :SHARED
89
+ #
90
+ # Marks a variable as deliberately private and unshared. It can
91
+ # still be accessed through memcached calls if you know how, but it
92
+ # isn't made easy -- it's supposed to be private, after all.
93
+ #
94
+ PRIVATE = :PRIVATE
95
+
96
+ class << self
97
+
98
+ #
99
+ # The memcached instance is currently a class-wide value.
100
+ #
101
+ attr_accessor(:cache_object)
102
+
103
+ #
104
+ # === Description
105
+ # :call-seq:
106
+ # InstantCache.enwrap(<i>cacheval</i>) => nil
107
+ #
108
+ # === Arguments
109
+ # [<i>cacheval</i>] Variable containing value fetched from memcache.
110
+ #
111
+ # === Exceptions
112
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
113
+ # destroyed and is no longer usable.
114
+ # The value in the cache is unaffected.
115
+ #
116
+ def enwrap(target)
117
+ #
118
+ # Shamelessly cadged from delegator.rb
119
+ #
120
+ eigenklass = eval('class << target ; self ; end')
121
+ preserved = ::Kernel.public_instance_methods(false)
122
+ preserved -= [ 'to_s', 'to_a', 'inspect', '==', '=~', '===' ]
123
+ swbd = {}
124
+ target.instance_variable_set(:@_instantcache_method_map, swbd)
125
+ target.instance_variable_set(:@_instantcache_datatype, target.class)
126
+ for t in self.class.ancestors
127
+ preserved |= t.public_instance_methods(false)
128
+ preserved |= t.private_instance_methods(false)
129
+ preserved |= t.protected_instance_methods(false)
130
+ end
131
+ preserved << 'singleton_method_added'
132
+ target.methods.each do |method|
133
+ next if (preserved.include?(method))
134
+ swbd[method] = target.method(method.to_sym)
135
+ target.instance_eval(<<-EOS)
136
+ def #{method}(*args, &block)
137
+ iniself = self.clone
138
+ result = @_instantcache_method_map['#{method}'].call(*args, &block)
139
+ if (self != iniself)
140
+ #
141
+ # Store the changed entity
142
+ #
143
+ newklass = self.class
144
+ iniklass = iniself.instance_variable_get(:@_instantcache_datatype)
145
+ unless (self.kind_of?(iniklass))
146
+ begin
147
+ raise InstantCache::IncompatibleType.new(newklass.name,
148
+ iniklass.name,
149
+ 'TBS')
150
+ rescue InstantCache::IncompatibleType
151
+ if ($@)
152
+ $@.delete_if { |s|
153
+ %r"\A#{Regexp.quote(__FILE__)}:\d+:in `" =~ s
154
+ }
155
+ end
156
+ raise
157
+ end
158
+ end
159
+ owner = self.instance_variable_get(:@_instantcache_owner)
160
+ owner.set(self)
161
+ end
162
+ return result
163
+ end
164
+ EOS
165
+ end
166
+ return nil
167
+ end # End of def enwrap
168
+
169
+ #
170
+ # === Description
171
+ # Removes any singleton methods added by the #enwrap class method.
172
+ # If the argument doesn't have any (<i>e.g.</i>, isn't a value that
173
+ # was previously fetched), this is a no-op.
174
+ #
175
+ # :call-seq:
176
+ # InstantCache.unwrap(<i>target</i>) => nil
177
+ #
178
+ # === Arguments
179
+ # [<i>target</i>] Variable containing value previously fetched
180
+ # from memcache.
181
+ #
182
+ # === Exceptions
183
+ # <i>None.</i>
184
+ #
185
+ def unwrap(target)
186
+ remap = target.instance_variable_get(:@_instantcache_method_map)
187
+ return nil unless (remap.kind_of?(Hash))
188
+ remap.keys.each do |method|
189
+ begin
190
+ eval("class << target ; remove_method(:#{method}) ; end")
191
+ rescue
192
+ end
193
+ end
194
+ target.instance_variable_set(:@_instantcache_method_map, nil)
195
+ target.instance_variable_set(:@_instantcache_owner, nil)
196
+ return nil
197
+ end # End of def unwrap
198
+
199
+ end # End of module InstantCache eigenclass
200
+
201
+ #
202
+ # The 'Blob' class is used to store data of arbitrary and opaque
203
+ # format in the cache. This is used for just about all cases except
204
+ # integer counters, which have their own class.
205
+ #
206
+ class Blob
207
+
208
+ class << self
209
+ #
210
+ # === Description
211
+ # Access method declarator for a read/write memcache-backed
212
+ # variable.
213
+ #
214
+ # This declarator sets up several methods relating to the
215
+ # variable. If the name passed is <b><tt>:ivar</tt></b>, these
216
+ # methods are created for it:
217
+ #
218
+ # [<i>ivar</i>] Normal read accessor
219
+ # (<i>e.g.</i>, <tt>obj.ivar</tt>).
220
+ # (See Blob#set)
221
+ # [<i>ivar</i>=] Normal write accessor
222
+ # (<i>e.g.</i>, <tt>obj.ivar = 17</tt>).
223
+ # (See Blob#get)
224
+ # [<i>ivar</i>_reset] Resets the cache variable to the default
225
+ # 'uninitialised' value.
226
+ # (See Blob#reset)
227
+ # [<i>ivar</i>_expiry] Returns the current cache lifetime
228
+ # (default 0).
229
+ # (See Blob#expiry)
230
+ # [<i>ivar</i>_expiry=] Sets the cache lifetime.
231
+ # (See Blob#expiry=)
232
+ # [<i>ivar</i>_lock] Tries to get an exclusive lock on the
233
+ # variable.
234
+ # (See Blob#lock)
235
+ # [<i>ivar</i>_unlock] Unlocks the variable if locked.
236
+ # (See Blob#unlock)
237
+ # [<i>ivar</i>_destroyed?] Returns true if variable is disconnected
238
+ # from the cache and unusable.
239
+ # (See Blob#destroyed?)
240
+ # [<i>ivar</i>_destroy!] Disconnects the variable from the cache
241
+ # and makes it unusable.
242
+ # (See Blob#destroy!)
243
+ #
244
+ # :call-seq:
245
+ # memcached_accessor(<i>symbol</i>[,...])
246
+ # memcached_accessor(<i>symbol</i>[,...]) { |symbol| ... }
247
+ #
248
+ # === Arguments
249
+ # [<i>symbol</i>] As with other Ruby accessor declarations, the argument
250
+ # list consists of one or more variable names represented
251
+ # as symbols (<i>e.g.</i>, <tt>:variablename</tt>).
252
+ # [<i>{block}</i>] If a block is supplied, its return value must
253
+ # be a string, which will be used as the name of
254
+ # the memcached cell backing the variable. The
255
+ # argument to the block is the name of the
256
+ # variable as passed to the accessor declaration.
257
+ #
258
+ # === Exceptions
259
+ # <i>None.</i>
260
+ #
261
+ #--
262
+ # This will be overridden later, but we need to declare
263
+ # *something* for the rdoc generation to work.
264
+ #++
265
+ def memcached_accessor(*args, &block) ; end
266
+
267
+ #
268
+ # === Description
269
+ # Access method declarator for a read-only memcache-backed variable.
270
+ #
271
+ # This declarator sets up several methods relating to the
272
+ # variable. If the name passed is <b><tt>:ivar</tt></b>, these
273
+ # methods are created for it:
274
+ #
275
+ # [<i>ivar</i>] Normal read accessor
276
+ # (<i>e.g.</i>, <tt>obj.ivar</tt>).
277
+ # (See Blob#get)
278
+ # [<i>ivar</i>_reset] Resets the cache variable to the default
279
+ # 'uninitialised' value.
280
+ # (See Blob#reset)
281
+ # [<i>ivar</i>_expiry] Returns the current cache lifetime
282
+ # (default 0).
283
+ # (See Blob#expiry)
284
+ # [<i>ivar</i>_expiry=] Sets the cache lifetime.
285
+ # (See Blob#expiry=)
286
+ # [<i>ivar</i>_lock] Tries to get an exclusive lock on the
287
+ # variable.
288
+ # (See Blob#lock)
289
+ # [<i>ivar</i>_unlock] Unlocks the variable if locked.
290
+ # (See Blob#unlock)
291
+ # [<i>ivar</i>_destroyed?] Returns true if variable is disconnected
292
+ # from the cache and unusable.
293
+ # (See Blob#destroyed?)
294
+ # [<i>ivar</i>_destroy!] Disconnects the variable from the cache
295
+ # and makes it unusable.
296
+ # (See Blob#destroy!)
297
+ #
298
+ # :call-seq:
299
+ # memcached_reader(<i>symbol</i>[,...])
300
+ # memcached_reader(<i>symbol</i>[,...]) { |symbol| ... }
301
+ #
302
+ # === Arguments
303
+ # [<i>symbol</i>] As with other Ruby accessor declarations, the argument
304
+ # list consists of one or more variable names represented
305
+ # as symbols (<i>e.g.</i>, <tt>:variablename</tt>).
306
+ # [<i>{block}</i>] If a block is supplied, its return value must
307
+ # be a string, which will be used as the name of
308
+ # the memcached cell backing the variable. The
309
+ # argument to the block is the name of the
310
+ # variable as passed to the accessor declaration.
311
+ #
312
+ # === Exceptions
313
+ # <i>None.</i>
314
+ #
315
+ #--
316
+ # This will be overridden later, but we need to declare
317
+ # *something* for the rdoc generation to work.
318
+ #++
319
+ def memcached_reader(*args, &block) ; end
320
+
321
+ end # End of class Blob eigenclass
322
+
323
+ #
324
+ # When a cached value of this type is reset or cleared,
325
+ # exactly what value is used to do so? This is overridden in
326
+ # subclasses as needed.
327
+ #
328
+ RESET_VALUE = nil
329
+
330
+ #
331
+ # Memcache expiration (lifetime) for this entity. Defaults to zero.
332
+ #
333
+ attr_accessor(:expiry)
334
+
335
+ #
336
+ # @rawmode is used to signal whether the object is a counter or
337
+ # not. Counters require memcache raw mode in order for
338
+ # increment/decrement to work; non-raw values are marshalled
339
+ # before storage and hence not atomically accessible in a short
340
+ # instruction stream.
341
+ #
342
+ attr_reader(:rawmode)
343
+
344
+ #
345
+ # When we lock a cell in the shared cache, we do so by creating
346
+ # another cell with a related name, in which we store info about
347
+ # ourself so that problems can be traced back to the correct
348
+ # thread/process/system. That identity is stored here.
349
+ #
350
+ attr_reader(:identity)
351
+
352
+ #
353
+ # When we lock a memcached cell, not only do we hang our identity
354
+ # on a interlock cell, but we record the fact locally.
355
+ #
356
+ attr_reader(:locked_by_us)
357
+
358
+ #
359
+ # === Description
360
+ # Constructor for a normal (<i>i.e.</i>, non-counter) variable
361
+ # stored in the cache. This is not intended to be invoked
362
+ # directly except by Those Who Know What They're Doing; rather,
363
+ # cached variables should be declared with the accessor methods
364
+ # <tt>Blob#memcached_accessor</tt> (for read/write access) and
365
+ # <tt>Blob#memcached_reader</tt> (for read-only).
366
+ #
367
+ # :call-seq:
368
+ # new(<i>[val]</i>)
369
+ #
370
+ # === Arguments
371
+ # [<i>val</i>] Value to be loaded into the cache cell.
372
+ # <b>N.B.:</b> If the cell in question is shared, this
373
+ # <b>will</b> overwrite the current value if any!
374
+ #
375
+ # === Exceptions
376
+ # <i>None.</i>
377
+ #
378
+ def initialize(inival=nil)
379
+ #
380
+ # This method is defined in the Blob class, for which raw mode
381
+ # is a no-no. However, to allow simple subclassing, we only set
382
+ # @rawmode if a potential subclass' #initialize hasn't done so.
383
+ # Thus subclasses can get most of the setup work done with a
384
+ # simple invocation of #super.
385
+ #
386
+ @rawmode ||= false
387
+ @expiry = 0
388
+ @locked_by_us = false
389
+ #
390
+ # Fill in our identity for purposes of lock ownership.
391
+ #
392
+ @identity = self.create_identity
393
+ #
394
+ # If we were given an initial value, go ahead and store it.
395
+ # <b>N.B.:</b> If the cell in question is shared, this
396
+ # <b>will</b> overwrite the current value if any!
397
+ #
398
+ self.set(inival) unless(inival.nil?)
399
+ end
400
+
401
+ #
402
+ # === Description
403
+ # Create a string that should uniquely identify this instance and
404
+ # a way to locate it. This is stored in the interlock cell when
405
+ # we obtain exclusive access to the main cached cell, so that we
406
+ # can be tracked down in case of hangs or other problems.
407
+ #
408
+ # This method can be overridden at need.
409
+ #
410
+ # === Arguments
411
+ # <i>None.</i>
412
+ #
413
+ # === Exceptions
414
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
415
+ # destroyed and is no longer usable.
416
+ # The value in the cache is unaffected.
417
+ #
418
+ def create_identity
419
+ raise Destroyed.new(self.name) if (self.destroyed?)
420
+ idfmt = 'host[%s]:pid[%d]:thread[%d]:%s[%d]'
421
+ idargs = []
422
+ idargs << `hostname`.chomp.strip
423
+ idargs << $$
424
+ idargs << Thread.current.object_id
425
+ idargs << self.class.name.sub(%r!^.*::!, '')
426
+ idargs << self.object_id
427
+ return idfmt % idargs
428
+ end
429
+
430
+ #
431
+ # === Description
432
+ # Reset the cache value to its default (typically zero or nil).
433
+ #
434
+ # :call-seq:
435
+ # reset => <i>default reset value</i>
436
+ #
437
+ # === Arguments
438
+ # <i>None.</i>
439
+ #
440
+ # === Exceptions
441
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
442
+ # destroyed and is no longer usable.
443
+ # The value in the cache is unaffected.
444
+ #
445
+ def reset
446
+ raise Destroyed.new(self.name) if (self.destroyed?)
447
+ rval = nil
448
+ if (self.class.constants.include?('RESET_VALUE'))
449
+ rval = self.class.const_get('RESET_VALUE')
450
+ end
451
+ #
452
+ # TODO: This can mess with subclassing; need better way to find the cache
453
+ #
454
+ InstantCache.cache_object.set(self.name, rval, self.expiry, self.rawmode)
455
+ return rval
456
+ end
457
+
458
+ #
459
+ # === Description
460
+ # The name of the variable declared with #memcached_accessor and
461
+ # friends does <i>not</i> necessarily equate to the name of the
462
+ # cell in the cache. This method is responsible for creating the
463
+ # latter; the name it returns is also used to identify the
464
+ # interlock cell.
465
+ #
466
+ # This method <b>must</b> be overridden by subclassing; there is
467
+ # no default name syntax for the memcache cells. (This is done
468
+ # automatically by the <tt>memcached_<i>xxx</i></tt> accessor
469
+ # declarations.)
470
+ #
471
+ # === Arguments
472
+ # <i>None.</i>
473
+ #
474
+ # === Exceptions
475
+ # [<tt>RuntimeError</tt>] This method has not been overridden as required.
476
+ #
477
+ def name
478
+ raise RuntimeError.new('#name method must be defined in instance')
479
+ end
480
+
481
+ #
482
+ # === Description
483
+ # Returns true or false according to whether this variable
484
+ # instance has been irrevocably disconnected from any value in the
485
+ # cache.
486
+ #
487
+ # When the instance is destroyed, this method is redefined to
488
+ # return <i>true</i>.
489
+ #
490
+ # === Arguments
491
+ # <i>None.</i>
492
+ #
493
+ # === Exceptions
494
+ # <i>None.</i>
495
+ #
496
+ def destroyed?
497
+ return false
498
+ end
499
+
500
+ #
501
+ # === Description
502
+ # Marks this instance as <b>destroyed</b> -- that is to say, any
503
+ # connexion it has to any cached value is severed. Any
504
+ # outstanding lock on the cache entry is released. This instance
505
+ # will no longer be usable.
506
+ #
507
+ # === Arguments
508
+ # <i>None.</i>
509
+ #
510
+ # === Exceptions
511
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
512
+ # destroyed and is no longer usable.
513
+ # The value in the cache is unaffected.
514
+ #
515
+ def destroy!
516
+ raise Destroyed.new(self.name) if (self.destroyed?)
517
+ self.unlock
518
+ self.instance_eval('def destroyed? ; return true ; end')
519
+ return nil
520
+ end
521
+
522
+ #:stopdoc:
523
+ # Not-for-public-consumption methods.
524
+
525
+ #
526
+ # === Description
527
+ # Create the name of the cached interlock cell based upon the name
528
+ # of the main value cell.
529
+ #
530
+ # === Arguments
531
+ # <i>None.</i>
532
+ #
533
+ # === Exceptions
534
+ # <i>None.</i>
535
+ #
536
+ def lock_name # :nodoc:
537
+ return self.name + '-lock'
538
+ end
539
+ protected(:lock_name)
540
+ #:startdoc:
541
+
542
+ #
543
+ # === Description
544
+ # Try to obtain an interlock on the memcached cell. If successful,
545
+ # returns true -- else, the cell is locked by someone else and
546
+ # we should proceed accordingly.
547
+ #
548
+ # <b>N.B.:</b> This makes use of the memcached convention that #add
549
+ # is a no-op if the cell already exists; we use that to try to
550
+ # create the interlock cell.
551
+ #
552
+ # The return value is wither <b><tt>true</tt></b> if we obtained
553
+ # (or already held) an exclusive lock, or <b><tt>false</tt></b> if
554
+ # we failed and/or someone else has it locked exclusively.
555
+ #
556
+ # :call-seq:
557
+ # lock => <i>Boolean</i>
558
+ #
559
+ # === Arguments
560
+ # <i>None.</i>
561
+ #
562
+ # === Exceptions
563
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
564
+ # destroyed and is no longer usable.
565
+ # The value in the cache is unaffected.
566
+ #
567
+ def lock
568
+ raise Destroyed.new(self.name) if (self.destroyed?)
569
+ return true if (@locked_by_us)
570
+ #
571
+ # TODO: Another instance of poor-man's-cache-location; see #reset
572
+ #
573
+ sts = InstantCache.cache_object.add(self.lock_name, @identity)
574
+ @locked_by_us = (sts.to_s =~ %r!^STORED!) ? true : false
575
+ return @locked_by_us
576
+ end
577
+
578
+ #
579
+ # === Description
580
+ # If we have the cell locked, unlock it by deleting the
581
+ # interlock cell (allowing someone else's #lock(#add) to work).
582
+ #
583
+ # This method returns <tt>true</tt> if we held the lock and have
584
+ # released it, or false if we didn't own the lock or the cell
585
+ # isn't locked at all.
586
+ #
587
+ # :call-seq:
588
+ # unlock => <i>Boolean</i>
589
+ #
590
+ # === Arguments
591
+ # <i>None.</i>
592
+ #
593
+ # === Exceptions
594
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
595
+ # destroyed and is no longer usable.
596
+ # The value in the cache is unaffected.
597
+ # [<tt>InstantCache::LockInconsistency</tt>] The state of the lock
598
+ # on the cell as stored
599
+ # in memcache differs
600
+ # from our local
601
+ # understanding of
602
+ # things.
603
+ # Specifically, we show
604
+ # it as locked by us,
605
+ # but the cache
606
+ # disagrees.
607
+ #
608
+ def unlock
609
+ raise Destroyed.new(self.name) if (self.destroyed?)
610
+ #
611
+ # TODO: Another instance of poor-man's-cache-location; see #reset
612
+ #
613
+ sts = InstantCache.cache_object.get(self.lock_name) || false
614
+ if (@locked_by_us && (sts != @identity))
615
+ #
616
+ # If we show we have the lock, but the lock cell doesn't exist
617
+ # (or isn't us), that's definitely an inconsistency.
618
+ #
619
+ e = LockInconsistency.new(self.lock_name,
620
+ @identity,
621
+ sts.inspect)
622
+ raise e
623
+ end
624
+ return false unless (@locked_by_us)
625
+ @locked_by_us = false
626
+ #
627
+ # TODO: Another instance of poor-man's-cache-location; see #reset
628
+ #
629
+ sts = InstantCache.cache_object.delete(self.lock_name)
630
+ if (sts !~ %r!^DELETED!)
631
+ e = LockInconsistency.new(self.lock_name,
632
+ '/DELETED/',
633
+ sts.inspect)
634
+ raise e
635
+ end
636
+ return true
637
+ end
638
+
639
+ #
640
+ # === Description
641
+ # Fetch the value out of memcached. Before being returned to the
642
+ # called, the value is annotated with singleton methods intended
643
+ # to keep the cache updated with any changes made to the value
644
+ # we're returning.
645
+ #
646
+ # === Arguments
647
+ # <i>None.</i>
648
+ #
649
+ # === Exceptions
650
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
651
+ # destroyed and is no longer usable.
652
+ # The value in the cache is unaffected.
653
+ #
654
+ def get
655
+ raise Destroyed.new(self.name) if (self.destroyed?)
656
+ #
657
+ # TODO: Another instance of poor-man's-cache-location; see #reset
658
+ #
659
+ value = InstantCache.cache_object.get(self.name, self.rawmode)
660
+ begin
661
+ #
662
+ # Make a copy of the thing we fetched out of the cache.
663
+ #
664
+ value.clone
665
+ #
666
+ # Add a note to it about who we are (so that requests can be
667
+ # appropriately directed).
668
+ #
669
+ value.instance_variable_set(:@_instantcache_owner, self)
670
+ #
671
+ # Add the singleton annotations.
672
+ #
673
+ InstantCache.enwrap(value)
674
+ rescue
675
+ #
676
+ # If the value was something we couldn't clone, like a Fixnum,
677
+ # it's inherently immutable and we don't need to add no
678
+ # steenkin' singleton methods to it. That's our position
679
+ # ayup.
680
+ #
681
+ end
682
+ return value
683
+ end
684
+ alias_method(:read, :get)
685
+
686
+ #
687
+ # === Description
688
+ # Store a value for the cell into the cache. We need to remove
689
+ # any singleton annotation methods before storing because the
690
+ # memcache gem can't handle them (actually, Marshal#dump, which
691
+ # memcache uses, cannot handle them).
692
+ #
693
+ # <i>N.B.:</i> We <b>don't</b> remove any annotations from the
694
+ # original value; it might be altered again, in which case we'd
695
+ # want to update the cache again. This can lead to some odd
696
+ # situations; see the bug list.
697
+ #
698
+ # === Arguments
699
+ # [<i>val_p</i>] The new value to be stored.
700
+ #
701
+ # === Exceptions
702
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
703
+ # destroyed and is no longer usable.
704
+ # The value in the cache is unaffected.
705
+ #
706
+ def set(val_p)
707
+ raise Destroyed.new(self.name) if (self.destroyed?)
708
+ begin
709
+ val = val_p.clone
710
+ rescue TypeError => e
711
+ val = val_p
712
+ end
713
+ #
714
+ InstantCache.unwrap(val)
715
+ #
716
+ # TODO: Another instance of poor-man's-cache-location; see #reset
717
+ #
718
+ # We use both memcache#add and memcache#set for completeness.
719
+ #
720
+ InstantCache.cache_object.add(self.name, val, self.expiry, self.rawmode)
721
+ InstantCache.cache_object.set(self.name, val, self.expiry, self.rawmode)
722
+ #
723
+ # Return the value as fetched through our accessor; this ensures
724
+ # the proper annotation.
725
+ #
726
+ return self.get
727
+ end
728
+ alias_method(:write, :set)
729
+
730
+ #
731
+ # === Description
732
+ # Return the string representaton of the value, not this instance.
733
+ # This is part of our 'try to be transparent' sensitivity
734
+ # training.
735
+ #
736
+ # === Arguments
737
+ # Any appropriate to the #to_s method of the underlying data's class.
738
+ #
739
+ # === Exceptions
740
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
741
+ # destroyed and is no longer usable.
742
+ # The value in the cache is unaffected.
743
+ #
744
+ def to_s(*args)
745
+ raise Destroyed.new(self.name) if (self.destroyed?)
746
+ return self.get.__send__(:to_s, *args)
747
+ end
748
+
749
+ =begin
750
+ #
751
+ # === Description
752
+ # :call-seq:
753
+ # === Arguments
754
+ # === Exceptions
755
+ # <i>None.</i>
756
+ #
757
+ def method_missing(meth, *args)
758
+ methsym = meth.to_sym
759
+ return self.__send__(methsym, *args) if (self.respond_to?(methsym))
760
+ curval = self.get
761
+ lastval = curval.clone
762
+ opresult = curval.__send__(methsym, *args)
763
+ if (curval != lastval)
764
+ self.set(curval)
765
+ end
766
+ return opresult
767
+ end
768
+ =end
769
+
770
+ end # End of class Blob
771
+
772
+ #
773
+ # Class for integer-only memcache cells, capable of atomic
774
+ # increment/decrement. Basically the same as Blob, except with a
775
+ # default reset value of zero and rawmode forced to true.
776
+ #
777
+ # However, counters have some additional features:
778
+ # * They may only be set to integer values;
779
+ # * <i><tt>varname</tt></i><tt>_increment</tt> and
780
+ # <i><tt>varname</tt></i><tt>_decrement</tt> methods, which provide access
781
+ # to the corresponding underlying memcache atomic integer
782
+ # operations;
783
+ # * Decrementing stops at zero, and will not result in negative
784
+ # numbers (a feature of memcache).
785
+ #
786
+ class Counter < Blob
787
+
788
+ class << self
789
+
790
+ #
791
+ # === Description
792
+ # Access method declarator for an interlocked shared integer
793
+ # memcache-backed variable.
794
+ #
795
+ # This declarator sets up several methods relating to the
796
+ # variable. If the name passed is <b><tt>:ivar</tt></b>, these
797
+ # methods are created for it:
798
+ #
799
+ # [<i>ivar</i>] Normal read accessor
800
+ # (<i>e.g.</i>, <tt>obj.ivar</tt>).
801
+ # (See Blob#get)
802
+ # [<i>ivar</i>_reset] Resets the cache variable to the default
803
+ # 'uninitialised' value.
804
+ # (See Blob#reset)
805
+ # [<i>ivar</i>_expiry] Returns the current cache lifetime
806
+ # (default 0).
807
+ # (See Blob#expiry)
808
+ # [<i>ivar</i>_expiry=] Sets the cache lifetime.
809
+ # (See Blob#expiry=)
810
+ # [<i>ivar</i>_lock] Tries to get an exclusive lock on the
811
+ # variable.
812
+ # (See Blob#lock)
813
+ # [<i>ivar</i>_unlock] Unlocks the variable if locked.
814
+ # (See Blob#unlock)
815
+ # [<i>ivar</i>_destroyed?] Returns true if variable is disconnected
816
+ # from the cache and unusable.
817
+ # (See Blob#destroyed?)
818
+ # [<i>ivar</i>_destroy!] Disconnects the variable from the cache
819
+ # and makes it unusable.
820
+ # (See Blob#destroy!)
821
+ #
822
+ # (These are the same methods created for a
823
+ # Blob::memcached_accessor declaration.)
824
+ #
825
+ # In addition, the following counter-specific methods are created:
826
+ #
827
+ # [<i>ivar</i>_increment] Adds the specified value to the cache
828
+ # variable.
829
+ # (See Counter#increment)
830
+ # [<i>ivar</i>_incr] Alias for
831
+ # <i><tt>ivar</tt></i><tt>_increment</tt>.
832
+ # [<i>ivar</i>_decrement] Subtracts from the cache variable.
833
+ # (See Counter#decrement)
834
+ # [<i>ivar</i>_decr] Alias for
835
+ # <i><tt>ivar</tt></i><tt>_decrement</tt>.
836
+ #
837
+ # :call-seq:
838
+ # memcached_counter(<i>symbol</i>[,...])
839
+ # memcached_counter(<i>symbol</i>[,...]) { |symbol| ... }
840
+ #
841
+ # === Arguments
842
+ # [<i>symbol</i>] As with other Ruby accessor declarations, the argument
843
+ # list consists of one or more variable names represented
844
+ # as symbols (<i>e.g.</i>, <tt>:variablename</tt>).
845
+ # [<i>{block}</i>] If a block is supplied, its return value must
846
+ # be a string, which will be used as the name of
847
+ # the memcached cell backing the variable. The
848
+ # argument to the block is the name of the
849
+ # variable as passed to the accessor declaration.
850
+ #
851
+ # === Exceptions
852
+ # <i>None.</i>
853
+ #
854
+ #--
855
+ # This will be overridden later, but we need to declare
856
+ # *something* for the rdoc generation to work.
857
+ #++
858
+ def memcached_counter(*args, &block) ; end
859
+
860
+ end # End of class Counter eigenclass
861
+ #
862
+ # When a cached 'counter' value is reset or cleared, that means
863
+ # 'zero'.
864
+ #
865
+ RESET_VALUE = 0
866
+
867
+ #
868
+ # === Description
869
+ # As with Blob#initialize, this is not intended to be called
870
+ # directly. Rather, instances are declared with the
871
+ # #memcached_counter class method.
872
+ #
873
+ # :call-seq:
874
+ # memcached_counter(<i>symbol</i>[,...])
875
+ # memcached_counter(<i>symbol</i>[,...]) { |symbol| ... }
876
+ #
877
+ # === Arguments
878
+ # [<i>symbol</i>] As with other Ruby accessor declarations, the argument
879
+ # list consists of one or more variable names represented
880
+ # as symbols (<i>e.g.</i>, <tt>:variablename</tt>).
881
+ # :call-seq:
882
+ # [<i>{block}</i>] If a block is supplied, its return value must
883
+ # be a string, which will be used as the name of
884
+ # the memcached cell backing the variable. The
885
+ # argument to the block is the name of the
886
+ # variable as passed to the accessor declaration.
887
+ #
888
+ # === Exceptions
889
+ # <i>None.</i>
890
+ #
891
+ def initialize(inival=nil)
892
+ @rawmode = true
893
+ return super
894
+ end
895
+
896
+ #
897
+ # === Description
898
+ # Fetch the cached value through the superclass, and convert it to
899
+ # integer. (Raw values get stored as strings, since they're
900
+ # unmarshalled.)
901
+ #
902
+ # === Arguments
903
+ # <i>None.</i>
904
+ #
905
+ # === Exceptions
906
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
907
+ # destroyed and is no longer usable.
908
+ # The value in the cache is unaffected.
909
+ # (From Blob#get)
910
+ #
911
+ def get
912
+ return super.to_i
913
+ end
914
+
915
+ #
916
+ # === Description
917
+ # Store a value as an integer, and return the value as stored.
918
+ # See Blob#set for the significance of this operation.
919
+ #
920
+ # === Arguments
921
+ # [<i>val</i>] New value to be stored in the cached cell.
922
+ #
923
+ # === Exceptions
924
+ # [<tt>InstantCache::CounterIntegerOnly</tt>] The supplied value
925
+ # was not an integer,
926
+ # and was not stored.
927
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
928
+ # destroyed and is no longer usable.
929
+ # The value in the cache is unaffected.
930
+ #
931
+ def set(val)
932
+ unless (val.kind_of?(Integer))
933
+ raise CounterIntegerOnly.new(self.ivar_name.inspect)
934
+ end
935
+ return super(val.to_i).to_i
936
+ end
937
+
938
+ #
939
+ # === Description
940
+ # Increment a memcached cell. This <b>only</b> works in raw mode,
941
+ # which is why it's in this class rather than Blob, but it's
942
+ # implicitly atomic according to memcache semantics.
943
+ #
944
+ # :call-seq:
945
+ # increment[(<i>by_amount</i>)] => <i>Integer</i>
946
+ #
947
+ # === Arguments
948
+ # [<i>by_amount</i>] An integer amount by which to increase the
949
+ # value of the variable; default is 1.
950
+ #
951
+ # === Exceptions
952
+ # [<tt>InstantCache::CounterIntegerOnly</tt>] The supplied value
953
+ # was not an integer,
954
+ # and the cache was
955
+ # not changed.
956
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
957
+ # destroyed and is no longer usable.
958
+ # The value in the cache is unaffected.
959
+ #
960
+ def increment(amt=1)
961
+ raise Destroyed.new(self.name) if (self.destroyed?)
962
+ unless (amt.kind_of?(Integer))
963
+ raise CounterIntegerOnly.new(self.ivar_name.inspect)
964
+ end
965
+ #
966
+ # TODO: Another instance of poor-man's-cache-location; see #reset
967
+ #
968
+ return InstantCache.cache_object.incr(self.name, amt)
969
+ end
970
+ alias_method(:incr, :increment)
971
+
972
+ #
973
+ # === Description
974
+ # Decrement a memcached cell. This <b>only</b> works in raw mode,
975
+ # which is why it's in this class rather than Blob, but it's
976
+ # implicitly atomic according to memcache semantics.
977
+ #
978
+ # :call-seq:
979
+ # decrement[(<i>by_amount</i>)] => <i>Integer</i>
980
+ #
981
+ # === Arguments
982
+ # [<i>by_amount</i>] An integer amount by which to reduce the
983
+ # value of the variable; default is 1.
984
+ #
985
+ # === Exceptions
986
+ # [<tt>InstantCache::CounterIntegerOnly</tt>] The supplied value
987
+ # was not an integer,
988
+ # and the cache was
989
+ # not changed.
990
+ # [<tt>InstantCache::Destroyed</tt>] Cache value instance has been
991
+ # destroyed and is no longer usable.
992
+ # The value in the cache is unaffected.
993
+ #
994
+ def decrement(amt=1)
995
+ raise Destroyed.new(self.name) if (self.destroyed?)
996
+ unless (amt.kind_of?(Integer))
997
+ raise CounterIntegerOnly.new(self.ivar_name.inspect)
998
+ end
999
+ #
1000
+ # TODO: Another instance of poor-man's-cache-location; see #reset
1001
+ #
1002
+ return InstantCache.cache_object.decr(self.name, amt)
1003
+ end
1004
+ alias_method(:decr, :decrement)
1005
+
1006
+ end # End of class Counter < Blob
1007
+
1008
+ # :stopdoc:
1009
+ #
1010
+ # Back into the eigenclass to define the magic stuff that makes this
1011
+ # all work behind the scenes.
1012
+ #
1013
+ # TODO: All the '%' formatting and evaluations are really rather hokey..
1014
+ #
1015
+ class << self
1016
+
1017
+ #
1018
+ # String constant used to set up most of the background magic
1019
+ # common to all of our types of cached variables.
1020
+ #
1021
+ Setup =<<-'EOT' # :nodoc:
1022
+ def _initialise_%s
1023
+ unless (self.instance_variables.include?('@%s') \
1024
+ && @%s.kind_of?(InstantCache::Blob))
1025
+ mvar = InstantCache::%s.new
1026
+ cellname = self.class.name + ':'
1027
+ cellname << self.object_id.to_s
1028
+ cellname << ':@%s'
1029
+ shared = %s
1030
+ owner = ObjectSpace._id2ref(self.object_id)
1031
+ mvar.instance_eval(%%Q{
1032
+ def name
1033
+ return '%s'
1034
+ end
1035
+ def shared?
1036
+ return #{shared.inspect}
1037
+ end
1038
+ def private?
1039
+ return (! self.shared?)
1040
+ end
1041
+ def owner
1042
+ return ObjectSpace._id2ref(#{self.object_id})
1043
+ end})
1044
+ @%s = mvar
1045
+ ObjectSpace.define_finalizer(owner, Proc.new { mvar.unlock })
1046
+ unless (shared)
1047
+ mvar.reset
1048
+ finaliser = Proc.new {
1049
+ InstantCache.cache_object.delete(mvar.name)
1050
+ InstantCache.cache_object.delete(mvar.send(:lock_name))
1051
+ }
1052
+ ObjectSpace.define_finalizer(owner, finaliser)
1053
+ end
1054
+ return true
1055
+ end
1056
+ return false
1057
+ end
1058
+ private(:_initialise_%s)
1059
+ def %s_lock
1060
+ self.__send__(:_initialise_%s)
1061
+ return @%s.lock
1062
+ end
1063
+ def %s_unlock
1064
+ self.__send__(:_initialise_%s)
1065
+ return @%s.unlock
1066
+ end
1067
+ def %s_expiry
1068
+ self.__send__(:_initialise_%s)
1069
+ return @%s.__send__(:expiry)
1070
+ end
1071
+ def %s_expiry=(val=0)
1072
+ self.__send__(:_initialise_%s)
1073
+ return @%s.__send__(:expiry=, val)
1074
+ end
1075
+ def %s_reset
1076
+ self.__send__(:_initialise_%s)
1077
+ return @%s.__send__(:reset)
1078
+ end
1079
+ def %s_destroy!
1080
+ self.__send__(:_initialise_%s)
1081
+ return @%s.__send__(:destroy!)
1082
+ end
1083
+ EOT
1084
+
1085
+ #
1086
+ # String to define a read accessor for the given cache variable.
1087
+ #
1088
+ Reader =<<-'EOT' # :nodoc:
1089
+ def %s
1090
+ self.__send__(:_initialise_%s)
1091
+ return @%s.get
1092
+ end
1093
+ EOT
1094
+
1095
+ #
1096
+ # As above, except this is a storage (write) accessor, and is
1097
+ # optional.
1098
+ #
1099
+ Writer =<<-'EOT' # :nodoc:
1100
+ def %s=(*args)
1101
+ self.__send__(:_initialise_%s)
1102
+ return @%s.set(*args)
1103
+ end
1104
+ EOT
1105
+
1106
+ #
1107
+ # Canned string for declaring an integer counter cell.
1108
+ #
1109
+ Counter =<<-'EOT' # :nodoc:
1110
+ def %s_increment(amt=1)
1111
+ self.__send__(:_initialise_%s)
1112
+ return @%s.increment(amt)
1113
+ end
1114
+ alias_method(:%s_incr, :%s_increment)
1115
+ def %s_decrement(amt=1)
1116
+ self.__send__(:_initialise_%s)
1117
+ return @%s.decrement(amt)
1118
+ end
1119
+ alias_method(:%s_decr, :%s_decrement)
1120
+ EOT
1121
+
1122
+ #
1123
+ # Actual code to create a read accessor for a cell.
1124
+ #
1125
+ EigenReader = Proc.new { |*args,&block| # :nodoc:
1126
+ shared = true
1127
+ if ([ :SHARED, :PRIVATE ].include?(args[0]))
1128
+ shared = (args.shift == :SHARED)
1129
+ end
1130
+ args.each do |ivar|
1131
+ ivar_s = ivar.to_s
1132
+ if (block)
1133
+ if (shared)
1134
+ name = block.call(ivar)
1135
+ else
1136
+ raise SharedOnly.new(ivar.to_sym.inspect)
1137
+ end
1138
+ end
1139
+ name ||= '#{cellname}'
1140
+ subslist = (([ ivar_s ] * 3) +
1141
+ [ 'Blob', ivar_s, shared.inspect, name] +
1142
+ ([ ivar_s ] * 20))
1143
+ class_eval(Setup % subslist)
1144
+ class_eval(Reader % subslist[7, 3])
1145
+ end
1146
+ nil
1147
+ } # End of Proc EigenReader
1148
+
1149
+ #
1150
+ # Code for a write accessor.
1151
+ #
1152
+ EigenAccessor = Proc.new { |*args,&block| # :nodoc:
1153
+ shared = true
1154
+ if ([ :SHARED, :PRIVATE ].include?(args[0]))
1155
+ shared = (args.shift == :SHARED)
1156
+ end
1157
+ args.each do |ivar|
1158
+ ivar_s = ivar.to_s
1159
+ if (block)
1160
+ if (shared)
1161
+ name = block.call(ivar)
1162
+ else
1163
+ raise SharedOnly.new(ivar.to_sym.inspect)
1164
+ end
1165
+ end
1166
+ name ||= '#{cellname}'
1167
+ subslist = (([ ivar_s ] * 3) +
1168
+ [ 'Blob', ivar_s, shared.inspect, name] +
1169
+ ([ ivar_s ] * 20))
1170
+ class_eval(Setup % subslist)
1171
+ class_eval(Reader % subslist[7, 3])
1172
+ class_eval(Writer % subslist[7, 3])
1173
+ end
1174
+ nil
1175
+ } # End of Proc EigenAccessor
1176
+
1177
+ #
1178
+ # And the code for a counter (read and write access).
1179
+ #
1180
+ EigenCounter = Proc.new { |*args,&block| # :nodoc:
1181
+ shared = true
1182
+ if ([ :SHARED, :PRIVATE ].include?(args[0]))
1183
+ shared = (args.shift == :SHARED)
1184
+ end
1185
+ args.each do |ivar|
1186
+ ivar_s = ivar.to_s
1187
+ if (block)
1188
+ if (shared)
1189
+ name = block.call(ivar)
1190
+ else
1191
+ raise SharedOnly.new(ivar.to_sym.inspect)
1192
+ end
1193
+ end
1194
+ name ||= '#{cellname}'
1195
+ subslist = (([ ivar_s ] * 3) +
1196
+ [ 'Counter', ivar_s, shared.inspect, name] +
1197
+ ([ ivar_s ] * 20))
1198
+ class_eval(Setup % subslist)
1199
+ subslist.delete_at(6)
1200
+ subslist.delete_at(5)
1201
+ subslist.delete_at(3)
1202
+ class_eval(Reader % subslist[7, 3])
1203
+ class_eval(Writer % subslist[7, 3])
1204
+ class_eval(Counter % subslist[0, 10])
1205
+ end
1206
+ nil
1207
+ } # End of Proc EigenCounter
1208
+
1209
+ #
1210
+ # === Description
1211
+ # This class method is invoked when the module is mixed into a
1212
+ # class; the argument is the class object involved.
1213
+ #
1214
+ # === Arguments
1215
+ # [<i>base_klass</i>] Class object of the class into which the
1216
+ # module is being mixed.
1217
+ #
1218
+ # === Exceptions
1219
+ # <i>None.</i>
1220
+ #
1221
+ def included(base_klass)
1222
+ base_eigenklass = base_klass.class_eval('class << self ; self ; end')
1223
+ base_eigenklass.__send__(:define_method,
1224
+ :memcached_reader,
1225
+ EigenReader)
1226
+ base_eigenklass.__send__(:define_method,
1227
+ :memcached_accessor,
1228
+ EigenAccessor)
1229
+ base_eigenklass.__send__(:define_method,
1230
+ :memcached_counter,
1231
+ EigenCounter)
1232
+ return nil
1233
+ end # End of def included
1234
+
1235
+ end # End of module InstantCache eigenclass
1236
+
1237
+ # :startdoc:
1238
+
1239
+ #
1240
+ # === Description
1241
+ # This should be overridden by inheritors; it's used to form the name
1242
+ # of the memcached cell.
1243
+ #
1244
+ # === Arguments
1245
+ # <i>None.</i>
1246
+ #
1247
+ # === Exceptions
1248
+ # <i>None.</i>
1249
+ #
1250
+ def name
1251
+ return "Unnamed-#{self.class.name}-object"
1252
+ end # End of def name
1253
+
1254
+ end # End of module InstantCache