Class: InstantCache::Blob
- Inherits:
-
Object
- Object
- InstantCache::Blob
- Defined in:
- lib/instantcache.rb
Overview
The ‘Blob’ class is used to store data of arbitrary and opaque format in the cache. This is used for just about all cases except integer counters, which have their own class.
Direct Known Subclasses
Constant Summary collapse
- RESET_VALUE =
When a cached value of this type is reset or cleared, exactly what value is used to do so? This is overridden in subclasses as needed.
nil
Instance Attribute Summary collapse
-
#expiry ⇒ Object
Memcache expiration (lifetime) for this entity.
-
#identity ⇒ Object
readonly
When we lock a cell in the shared cache, we do so by creating another cell with a related name, in which we store info about ourself so that problems can be traced back to the correct thread/process/system.
-
#locked_by_us ⇒ Object
readonly
When we lock a memcached cell, not only do we hang our identity on a interlock cell, but we record the fact locally.
-
#rawmode ⇒ Object
readonly
not.
Class Method Summary collapse
-
.memcached_accessor(*args, &block) ⇒ Object
Description Access method declarator for a read/write memcache-backed variable.
-
.memcached_reader(*args, &block) ⇒ Object
Description Access method declarator for a read-only memcache-backed variable.
Instance Method Summary collapse
-
#create_identity ⇒ Object
Description Create a string that should uniquely identify this instance and a way to locate it.
-
#destroy! ⇒ Object
Description Marks this instance as destroyed – that is to say, any connexion it has to any cached value is severed.
-
#destroyed? ⇒ Boolean
Description Returns true or false according to whether this variable instance has been irrevocably disconnected from any value in the cache.
-
#get ⇒ Object
(also: #read)
Description Fetch the value out of memcached.
-
#initialize(inival = nil) ⇒ Blob
constructor
Description Constructor for a normal (i.e., non-counter) variable stored in the cache.
-
#lock ⇒ Object
Description Try to obtain an interlock on the memcached cell.
-
#name ⇒ Object
Description The name of the variable declared with #memcached_accessor and friends does not necessarily equate to the name of the cell in the cache.
-
#reset ⇒ Object
Description Reset the cache value to its default (typically zero or nil).
-
#set(val_p) ⇒ Object
(also: #write)
Description Store a value for the cell into the cache.
-
#to_s(*args) ⇒ Object
Description Return the string representaton of the value, not this instance.
-
#unlock ⇒ Object
Description If we have the cell locked, unlock it by deleting the interlock cell (allowing someone else’s #lock(#add) to work).
Constructor Details
#initialize(inival = nil) ⇒ Blob
Description
Constructor for a normal (i.e., non-counter) variable stored in the cache. This is not intended to be invoked directly except by Those Who Know What They’re Doing; rather, cached variables should be declared with the accessor methods Blob#memcached_accessor (for read/write access) and Blob#memcached_reader (for read-only).
:call-seq: new([val])
Arguments
- val
-
Value to be loaded into the cache cell. N.B.: If the cell in question is shared, this will overwrite the current value if any!
Exceptions
None.
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/instantcache.rb', line 399 def initialize(inival=nil) # # This method is defined in the Blob class, for which raw mode # is a no-no. However, to allow simple subclassing, we only set # @rawmode if a potential subclass' #initialize hasn't done so. # Thus subclasses can get most of the setup work done with a # simple invocation of #super. # @rawmode ||= false @expiry = 0 @locked_by_us = false # # Fill in our identity for purposes of lock ownership. # @identity = self.create_identity # # If we were given an initial value, go ahead and store it. # <b>N.B.:</b> If the cell in question is shared, this # <b>will</b> overwrite the current value if any! # self.set(inival) unless(inival.nil?) end |
Instance Attribute Details
#expiry ⇒ Object
Memcache expiration (lifetime) for this entity. Defaults to zero.
354 355 356 |
# File 'lib/instantcache.rb', line 354 def expiry @expiry end |
#identity ⇒ Object (readonly)
When we lock a cell in the shared cache, we do so by creating another cell with a related name, in which we store info about ourself so that problems can be traced back to the correct thread/process/system. That identity is stored here.
371 372 373 |
# File 'lib/instantcache.rb', line 371 def identity @identity end |
#locked_by_us ⇒ Object (readonly)
When we lock a memcached cell, not only do we hang our identity on a interlock cell, but we record the fact locally.
377 378 379 |
# File 'lib/instantcache.rb', line 377 def locked_by_us @locked_by_us end |
#rawmode ⇒ Object (readonly)
not. Counters require memcache raw mode in order for increment/decrement to work; non-raw values are marshalled before storage and hence not atomically accessible in a short instruction stream.
363 364 365 |
# File 'lib/instantcache.rb', line 363 def rawmode @rawmode end |
Class Method Details
.memcached_accessor(*args, &block) ⇒ Object
Description
Access method declarator for a read/write memcache-backed variable.
This declarator sets up several methods relating to the variable. If the name passed is :ivar, these methods are created for it:
- ivar
-
Normal read accessor (e.g.,
obj.ivar). (See Blob#set) - ivar=
-
Normal write accessor (e.g.,
obj.ivar = 17). (See Blob#get) - ivar_reset
-
Resets the cache variable to the default ‘uninitialised’ value. (See Blob#reset)
- ivar_expiry
-
Returns the current cache lifetime (default 0). (See Blob#expiry)
- ivar_expiry=
-
Sets the cache lifetime. (See Blob#expiry=)
- ivar_lock
-
Tries to get an exclusive lock on the variable. (See Blob#lock)
- ivar_unlock
-
Unlocks the variable if locked. (See Blob#unlock)
- ivar_destroyed?
-
Returns true if variable is disconnected from the cache and unusable. (See Blob#destroyed?)
- ivar_destroy!
-
Disconnects the variable from the cache and makes it unusable. (See Blob#destroy!)
:call-seq: memcached_accessor(symbol[,…]) memcached_accessor(symbol[,…]) { |symbol| … }
Arguments
- symbol
-
As with other Ruby accessor declarations, the argument list consists of one or more variable names represented as symbols (e.g.,
:variablename). - block
-
If a block is supplied, its return value must be a string, which will be used as the name of the memcached cell backing the variable. The argument to the block is the name of the variable as passed to the accessor declaration.
Exceptions
None.
– This will be overridden later, but we need to declare something for the rdoc generation to work. ++
286 |
# File 'lib/instantcache.rb', line 286 def memcached_accessor(*args, &block) ; end |
.memcached_reader(*args, &block) ⇒ Object
Description
Access method declarator for a read-only memcache-backed variable.
This declarator sets up several methods relating to the variable. If the name passed is :ivar, these methods are created for it:
- ivar
-
Normal read accessor (e.g.,
obj.ivar). (See Blob#get) - ivar_reset
-
Resets the cache variable to the default ‘uninitialised’ value. (See Blob#reset)
- ivar_expiry
-
Returns the current cache lifetime (default 0). (See Blob#expiry)
- ivar_expiry=
-
Sets the cache lifetime. (See Blob#expiry=)
- ivar_lock
-
Tries to get an exclusive lock on the variable. (See Blob#lock)
- ivar_unlock
-
Unlocks the variable if locked. (See Blob#unlock)
- ivar_destroyed?
-
Returns true if variable is disconnected from the cache and unusable. (See Blob#destroyed?)
- ivar_destroy!
-
Disconnects the variable from the cache and makes it unusable. (See Blob#destroy!)
:call-seq: memcached_reader(symbol[,…]) memcached_reader(symbol[,…]) { |symbol| … }
Arguments
- symbol
-
As with other Ruby accessor declarations, the argument list consists of one or more variable names represented as symbols (e.g.,
:variablename). - block
-
If a block is supplied, its return value must be a string, which will be used as the name of the memcached cell backing the variable. The argument to the block is the name of the variable as passed to the accessor declaration.
Exceptions
None.
– This will be overridden later, but we need to declare something for the rdoc generation to work. ++
340 |
# File 'lib/instantcache.rb', line 340 def memcached_reader(*args, &block) ; end |
Instance Method Details
#create_identity ⇒ Object
Description
Create a string that should uniquely identify this instance and a way to locate it. This is stored in the interlock cell when we obtain exclusive access to the main cached cell, so that we can be tracked down in case of hangs or other problems.
This method can be overridden at need.
Arguments
None.
Exceptions
InstantCache::Destroyed-
Cache value instance has been destroyed and is no longer usable. The value in the cache is unaffected.
439 440 441 442 443 444 445 446 447 448 449 |
# File 'lib/instantcache.rb', line 439 def create_identity raise Destroyed.new(self.name) if (self.destroyed?) idfmt = 'host[%s]:pid[%d]:thread[%d]:%s[%d]' idargs = [] idargs << `hostname`.chomp.strip idargs << $$ idargs << Thread.current.object_id idargs << self.class.name.sub(%r!^.*::!, '') idargs << self.object_id return idfmt % idargs end |
#destroy! ⇒ Object
Description
Marks this instance as destroyed – that is to say, any connexion it has to any cached value is severed. Any outstanding lock on the cache entry is released. This instance will no longer be usable.
Arguments
None.
Exceptions
InstantCache::Destroyed-
Cache value instance has been destroyed and is no longer usable. The value in the cache is unaffected.
536 537 538 539 540 541 |
# File 'lib/instantcache.rb', line 536 def destroy! raise Destroyed.new(self.name) if (self.destroyed?) self.unlock self.instance_eval('def destroyed? ; return true ; end') return nil end |
#destroyed? ⇒ Boolean
Description
Returns true or false according to whether this variable instance has been irrevocably disconnected from any value in the cache.
When the instance is destroyed, this method is redefined to return true.
Arguments
None.
Exceptions
None.
517 518 519 |
# File 'lib/instantcache.rb', line 517 def destroyed? return false end |
#get ⇒ Object Also known as: read
Description
Fetch the value out of memcached. Before being returned to the called, the value is annotated with singleton methods intended to keep the cache updated with any changes made to the value we’re returning.
Arguments
None.
Exceptions
InstantCache::Destroyed-
Cache value instance has been destroyed and is no longer usable. The value in the cache is unaffected.
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 |
# File 'lib/instantcache.rb', line 675 def get raise Destroyed.new(self.name) if (self.destroyed?) # # TODO: Another instance of poor-man's-cache-location; see #reset # value = InstantCache.cache.get(self.name, self.rawmode) begin # # Make a copy of the thing we fetched out of the cache. # value.clone # # Add a note to it about who we are (so that requests can be # appropriately directed). # value.instance_variable_set(:@_instantcache_owner, self) # # Add the singleton annotations. # InstantCache.enwrap(value) rescue # # If the value was something we couldn't clone, like a Fixnum, # it's inherently immutable and we don't need to add no # steenkin' singleton methods to it. That's our position # ayup. # end return value end |
#lock ⇒ Object
Description
Try to obtain an interlock on the memcached cell. If successful, returns true – else, the cell is locked by someone else and we should proceed accordingly.
N.B.: This makes use of the memcached convention that #add is a no-op if the cell already exists; we use that to try to create the interlock cell.
The return value is wither true if we obtained (or already held) an exclusive lock, or false if we failed and/or someone else has it locked exclusively.
:call-seq: lock => Boolean
Arguments
None.
Exceptions
InstantCache::Destroyed-
Cache value instance has been destroyed and is no longer usable. The value in the cache is unaffected.
588 589 590 591 592 593 594 595 596 597 |
# File 'lib/instantcache.rb', line 588 def lock raise Destroyed.new(self.name) if (self.destroyed?) return true if (@locked_by_us) # # TODO: Another instance of poor-man's-cache-location; see #reset # sts = InstantCache.cache.add(self.lock_name, @identity) @locked_by_us = (sts.to_s =~ %r!^STORED!) ? true : false return @locked_by_us end |
#name ⇒ Object
Description
The name of the variable declared with #memcached_accessor and friends does not necessarily equate to the name of the cell in the cache. This method is responsible for creating the latter; the name it returns is also used to identify the interlock cell.
This method must be overridden by subclassing; there is no default name syntax for the memcache cells. (This is done automatically by the memcached_xxx accessor declarations.)
Arguments
None.
Exceptions
RuntimeError-
This method has not been overridden as required.
498 499 500 |
# File 'lib/instantcache.rb', line 498 def name raise RuntimeError.new('#name method must be defined in instance') end |
#reset ⇒ Object
Description
Reset the cache value to its default (typically zero or nil).
:call-seq: reset => default reset value
Arguments
None.
Exceptions
InstantCache::Destroyed-
Cache value instance has been destroyed and is no longer usable. The value in the cache is unaffected.
466 467 468 469 470 471 472 473 474 475 476 477 |
# File 'lib/instantcache.rb', line 466 def reset raise Destroyed.new(self.name) if (self.destroyed?) rval = nil if (self.class.constants.include?('RESET_VALUE')) rval = self.class.const_get('RESET_VALUE') end # # TODO: This can mess with subclassing; need better way to find the cache # InstantCache.cache.set(self.name, rval, self.expiry, self.rawmode) return rval end |
#set(val_p) ⇒ Object Also known as: write
Description
Store a value for the cell into the cache. We need to remove any singleton annotation methods before storing because the memcache gem can’t handle them (actually, Marshal#dump, which memcache uses, cannot handle them).
N.B.: We don’t remove any annotations from the original value; it might be altered again, in which case we’d want to update the cache again. This can lead to some odd situations; see the bug list.
Arguments
- val_p
-
The new value to be stored.
Exceptions
InstantCache::Destroyed-
Cache value instance has been destroyed and is no longer usable. The value in the cache is unaffected.
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 |
# File 'lib/instantcache.rb', line 727 def set(val_p) raise Destroyed.new(self.name) if (self.destroyed?) begin val = val_p.clone rescue TypeError => e val = val_p end # InstantCache.unwrap(val) # # TODO: Another instance of poor-man's-cache-location; see #reset # # We use both memcache#add and memcache#set for completeness. # InstantCache.cache.add(self.name, val, self.expiry, self.rawmode) InstantCache.cache.set(self.name, val, self.expiry, self.rawmode) # # Return the value as fetched through our accessor; this ensures # the proper annotation. # return self.get end |
#to_s(*args) ⇒ Object
Description
Return the string representaton of the value, not this instance. This is part of our ‘try to be transparent’ sensitivity training.
Arguments
Any appropriate to the #to_s method of the underlying data’s class.
Exceptions
InstantCache::Destroyed-
Cache value instance has been destroyed and is no longer usable. The value in the cache is unaffected.
765 766 767 768 |
# File 'lib/instantcache.rb', line 765 def to_s(*args) raise Destroyed.new(self.name) if (self.destroyed?) return self.get.__send__(:to_s, *args) end |
#unlock ⇒ Object
Description
If we have the cell locked, unlock it by deleting the interlock cell (allowing someone else’s #lock(#add) to work).
This method returns true if we held the lock and have released it, or false if we didn’t own the lock or the cell isn’t locked at all.
:call-seq: unlock => Boolean
Arguments
None.
Exceptions
InstantCache::Destroyed-
Cache value instance has been destroyed and is no longer usable. The value in the cache is unaffected.
InstantCache::LockInconsistency-
The state of the lock on the cell as stored in memcache differs from our local understanding of things. Specifically, we show it as locked by us, but the cache disagrees.
629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 |
# File 'lib/instantcache.rb', line 629 def unlock raise Destroyed.new(self.name) if (self.destroyed?) # # TODO: Another instance of poor-man's-cache-location; see #reset # sts = InstantCache.cache.get(self.lock_name) || false if (@locked_by_us && (sts != @identity)) # # If we show we have the lock, but the lock cell doesn't exist # (or isn't us), that's definitely an inconsistency. # e = LockInconsistency.new(self.lock_name, @identity, sts.inspect) raise e end return false unless (@locked_by_us) @locked_by_us = false # # TODO: Another instance of poor-man's-cache-location; see #reset # sts = InstantCache.cache.delete(self.lock_name) if (sts !~ %r!^DELETED!) e = LockInconsistency.new(self.lock_name, '/DELETED/', sts.inspect) raise e end return true end |