instantcache 0.1.0a1

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,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