Module: InstantCache

Defined in:
lib/instantcache.rb,
lib/instantcache/exceptions.rb

Overview

Copyright 

++

Defined Under Namespace

Classes: Blob, ConnexionLost, Counter, CounterIntegerOnly, Destroyed, Exception, IncompatibleType, IncompleteException, LockInconsistency, NoCache, SharedOnly

Constant Summary collapse

Version =

The base Versionomy representation of the package version.

Versionomy.parse('0.1.1')
VERSION =

The package version-as-a-string.

Version.to_s.freeze
SHARED =

Label informing accessor declarations that the variable is to be shared. This changes how some things are done (like default memcached cell names).

:SHARED
PRIVATE =

Marks a variable as deliberately private and unshared. It can still be accessed through memcached calls if you know how, but it isn’t made easy – it’s supposed to be private, after all.

:PRIVATE
Setup =

String constant used to set up most of the background magic common to all of our types of cached variables.

TODO: Resolve what to do if the instance variable is zapped

One of the fortunate side-effects of all of the methods calling this first is that if the instance variable gets zapped somehow, the next access to it through of of our methods will create a new Blob or Counter object and put it into the instance variable before proceeding.

One of the UNfortunate side effects of that is that if the object that was lost was locked, it cannot be unlocked through the normal paths – only the blob object itself is supposed to lock and unlock itself. It can be worked around, but that’s for another day.

If we decide against instantiating a new object, the ConnexionLost exception is ready to be pressed into service.

"      def _initialise_%s\nunless (self.instance_variables.include?('@%s') \\\n        && @%s.kind_of?(InstantCache::Blob))\n  mvar = InstantCache::%s.new\n  cellname = self.class.name + ':'\n  cellname << self.object_id.to_s\n  cellname << ':@%s'\n  shared = %s\n  owner = ObjectSpace._id2ref(self.object_id)\n  mvar.instance_eval(%%Q{\n    def name\n      return '%s'\n    end\n    def shared?\n      return \#{shared.inspect}\n    end\n    def private?\n      return (! self.shared?)\n    end\n    def owner\n      return ObjectSpace._id2ref(\#{self.object_id})\n    end})\n  @%s = mvar\n  ObjectSpace.define_finalizer(owner, Proc.new { mvar.unlock })\n  unless (shared)\n    mvar.reset\n    finaliser = Proc.new {\n      InstantCache.cache.delete(mvar.name)\n      InstantCache.cache.delete(mvar.send(:lock_name))\n    }\n    ObjectSpace.define_finalizer(owner, finaliser)\n  end\n  return true\nend\nreturn false\n      end\n      private(:_initialise_%s)\n      def %s_lock\nself.__send__(:_initialise_%s)\nreturn @%s.lock\n      end\n      def %s_unlock\nself.__send__(:_initialise_%s)\nreturn @%s.unlock\n      end\n      def %s_expiry\nself.__send__(:_initialise_%s)\nreturn @%s.__send__(:expiry)\n      end\n      def %s_expiry=(val=0)\nself.__send__(:_initialise_%s)\nreturn @%s.__send__(:expiry=, val)\n      end\n      def %s_reset\nself.__send__(:_initialise_%s)\nreturn @%s.__send__(:reset)\n      end\n      def %s_destroy!\nself.__send__(:_initialise_%s)\nreturn @%s.__send__(:destr\n"             # :nodoc:
Reader =

String to define a read accessor for the given cache variable.

"      def %s\nself.__send__(:_initialise_%s)\nreturn @%s.\n"            # :nodoc:
Writer =

As above, except this is a storage (write) accessor, and is optional.

"      def %s=(*args)\nself.__send__(:_initialise_%s)\nreturn @%s.set(*ar\n"            # :nodoc:
EigenReader =

Actual code to create a read accessor for a cell.

Proc.new { |*args,&block| # :nodoc:
  shared = true
  if ([ :SHARED, :PRIVATE ].include?(args[0]))
    shared = (args.shift == :SHARED)
  end
  args.each do |ivar|
    ivar_s = ivar.to_s
    if (block)
      if (shared)
        name = block.call(ivar)
      else
        raise SharedOnly.new(ivar.to_sym.inspect)
      end
    end
    name ||= '#{cellname}'
    subslist = (([ ivar_s ] * 3) +
                [ 'Blob', ivar_s, shared.inspect, name] +
                ([ ivar_s ] * 20))
    class_eval(Setup % subslist)
    class_eval(Reader % subslist[7, 3])
  end
  nil
}
EigenAccessor =

Code for a write accessor.

Proc.new { |*args,&block| # :nodoc:
  shared = true
  if ([ :SHARED, :PRIVATE ].include?(args[0]))
    shared = (args.shift == :SHARED)
  end
  args.each do |ivar|
    ivar_s = ivar.to_s
    if (block)
      if (shared)
        name = block.call(ivar)
      else
        raise SharedOnly.new(ivar.to_sym.inspect)
      end
    end
    name ||= '#{cellname}'
    subslist = (([ ivar_s ] * 3) +
                [ 'Blob', ivar_s, shared.inspect, name] +
                ([ ivar_s ] * 20))
    class_eval(Setup % subslist)
    class_eval(Reader % subslist[7, 3])
    class_eval(Writer % subslist[7, 3])
  end
  nil
}
EigenCounter =

And the code for a counter (read and write access).

Proc.new { |*args,&block| # :nodoc:
  shared = true
  if ([ :SHARED, :PRIVATE ].include?(args[0]))
    shared = (args.shift == :SHARED)
  end
  args.each do |ivar|
    ivar_s = ivar.to_s
    if (block)
      if (shared)
        name = block.call(ivar)
      else
        raise SharedOnly.new(ivar.to_sym.inspect)
      end
    end
    name ||= '#{cellname}'
    subslist = (([ ivar_s ] * 3) +
                [ 'Counter', ivar_s, shared.inspect, name] +
                ([ ivar_s ] * 20))
    class_eval(Setup % subslist)
    subslist.delete_at(6)
    subslist.delete_at(5)
    subslist.delete_at(3)
    class_eval(Reader % subslist[7, 3])
    class_eval(Writer % subslist[7, 3])
    class_eval(Counter % subslist[0, 10])
  end
  nil
}

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.cache_objectObject

The memcached instance is currently a class-wide value.



101
102
103
# File 'lib/instantcache.rb', line 101

def cache_object
  @cache_object
end

Class Method Details

.cacheObject

Description

Wrapper for the @cache_object class variable.

:call-seq: InstantCache.cache.method

Arguments

None.

Exceptions

InstantCache::NoCache

Cache object unset or misset.

Raises:



116
117
118
119
120
# File 'lib/instantcache.rb', line 116

def cache                   # :nodoc
  mco = (InstantCache.cache_object ||= nil)
  return mco if (mco.kind_of?(MemCache))
  raise NoCache
end

.enwrap(target) ⇒ Object

Description

Add singleton wrapper methods to a copy of the cached value.

:call-seq: InstantCache.enwrap(cacheval) => nil

Arguments

cacheval

Variable containing value fetched from memcache.

Exceptions

InstantCache::Destroyed

Cache value instance has been destroyed and is no longer usable. The value in the cache is unaffected.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/instantcache.rb', line 137

def enwrap(target)
  #
  # Shamelessly cadged from delegator.rb
  #
  eigenklass = eval('class << target ; self ; end')
  preserved = ::Kernel.public_instance_methods(false)
  preserved -= [ 'to_s', 'to_a', 'inspect', '==', '=~', '===' ]
  swbd = {}
  target.instance_variable_set(:@_instantcache_method_map, swbd)
  target.instance_variable_set(:@_instantcache_datatype, target.class)
  for t in self.class.ancestors
    preserved |= t.public_instance_methods(false)
    preserved |= t.private_instance_methods(false)
    preserved |= t.protected_instance_methods(false)
  end
  preserved << 'singleton_method_added'
  target.methods.each do |method|
    next if (preserved.include?(method))
    swbd[method] = target.method(method.to_sym)
    target.instance_eval("      def \#{method}(*args, &block)\n        iniself = self.clone\n        result = @_instantcache_method_map['\#{method}'].call(*args, &block)\n        if (self != iniself)\n          #\n          # Store the changed entity\n          #\n          newklass = self.class\n          iniklass = iniself.instance_variable_get(:@_instantcache_datatype)\n          unless (self.kind_of?(iniklass))\n            begin\n              raise InstantCache::IncompatibleType.new(newklass.name,\n                                                       iniklass.name,\n                                                       'TBS')\n            rescue InstantCache::IncompatibleType\n              if ($@)\n                [email protected]_if { |s|\n                  %r\"\\A\#{Regexp.quote(__FILE__)}:\\d+:in `\" =~ s\n                }\n              end\n              raise\n            end\n          end\n          owner = self.instance_variable_get(:@_instantcache_owner)\n          owner.set(self)\n        end\n        return result\n      end\n    EOS\n  end\n  return nil\nend\n")

.included(base_klass) ⇒ Object

Description

This class method is invoked when the module is mixed into a class; the argument is the class object involved.

Arguments

base_klass

Class object of the class into which the module is being mixed.

Exceptions

None.



1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
# File 'lib/instantcache.rb', line 1238

def included(base_klass)
  base_eigenklass = base_klass.class_eval('class << self ; self ; end')
  base_eigenklass.__send__(:define_method,
                           :memcached_reader,
                           EigenReader)
  base_eigenklass.__send__(:define_method,
                           :memcached_accessor,
                           EigenAccessor)
  base_eigenklass.__send__(:define_method,
                           :memcached_counter,
                           EigenCounter)
  return nil
end

.unwrap(target) ⇒ Object

Description

Removes any singleton methods added by the #enwrap class method. If the argument doesn’t have any (e.g., isn’t a value that was previously fetched), this is a no-op.

:call-seq: InstantCache.unwrap(target) => nil

Arguments

target

Variable containing value previously fetched from memcache.

Exceptions

None.



206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/instantcache.rb', line 206

def unwrap(target)
  remap = target.instance_variable_get(:@_instantcache_method_map)
  return nil unless (remap.kind_of?(Hash))
  remap.keys.each do |method|
    begin
      eval("class << target ; remove_method(:#{method}) ; end")
    rescue
    end
  end
  target.instance_variable_set(:@_instantcache_method_map, nil)
  target.instance_variable_set(:@_instantcache_owner, nil)
  return nil
end

Instance Method Details

#nameObject

Description

This should be overridden by inheritors; it’s used to form the name of the memcached cell.

Arguments

None.

Exceptions

None.



1267
1268
1269
# File 'lib/instantcache.rb', line 1267

def name
  return "Unnamed-#{self.class.name}-object"
end