Class: InstantCache::Blob

Inherits:
Object
  • Object
show all
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

Counter

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

Class Method Summary collapse

Instance Method Summary collapse

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

#expiryObject

Memcache expiration (lifetime) for this entity. Defaults to zero.



354
355
356
# File 'lib/instantcache.rb', line 354

def expiry
  @expiry
end

#identityObject (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_usObject (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

#rawmodeObject (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_identityObject

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.

Raises:



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.

Raises:



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.

Returns:

  • (Boolean)


517
518
519
# File 'lib/instantcache.rb', line 517

def destroyed?
  return false
end

#getObject 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.

Raises:



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

#lockObject

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.

Raises:



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

#nameObject

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.

Raises:

  • (RuntimeError)


498
499
500
# File 'lib/instantcache.rb', line 498

def name
  raise RuntimeError.new('#name method must be defined in instance')
end

#resetObject

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.

Raises:



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.

Raises:



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.

Raises:



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

#unlockObject

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.

Raises:



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