Class CacheManager
In: lib/storage/cache.rb
Parent: Object
CacheStats
CacheManager
CacheEntry
dot/f_42.png

This class manages the cache. It is initialized by the Store and front ends the database behind the Store.

Methods

delete   get   hash   inspect   keys   makenoswap   makeswap   mark   new   put   stats   sync   sync_chain  

Public Class methods

[Source]

# File lib/storage/cache.rb, line 112
  def initialize(db)
    @cwidth = options['cache_width']
    @cdepth = options['cache_depth']

    @db =  db
    @st = CacheStats.new

    @cache = Array.new(@cwidth) {Array.new}
    @cwidth.times do |i|
      @cdepth.times do |j|
        @cache[i] << CacheEntry.new
        @st.inc(:cache_size)
      end
    end

  end

Public Instance methods

delete an object

[Source]

# File lib/storage/cache.rb, line 254
  def delete(oid)
    return nil if oid.nil?
    @st.inc(:deletes)
    hv = hash(oid)

    # mark dead in cache

    @cache[hv].each do |ce|
      next if ce.oid != oid
      ce.mark_dead
      break
    end
    # delete it from the database

    @db.delete(oid.to_s)
  end

gets an object

oid
- integer object id to retrieve
return
- a reference to the object or nil if none exists

[Source]

# File lib/storage/cache.rb, line 151
  def get(oid)
    return nil if oid.nil?
    @st.inc(:reads)
    hv = hash(oid)

    # search the cache

    @cache[hv].each do |ce|
      if ce.oid == oid
        @st.inc(:cache_read_hits)
        # need to try putting hot hit to the head of the list?

        return ce.obj
      end
    end

    # cache miss - search the database

    if @db.has_key? oid.to_s
      ret = Utility.decode(@db[oid.to_s])
    else
      @st.inc(:database_read_fails)
      return nil
    end
    @st.inc(:database_reads)

    # get and remove the last entry off this list

    ch = @cache[hv].pop
    # if its dirty we write it to the database

    if ch.dirty? && !ch.dead?
      @db[ch.oid.to_s] = Utility.encode(ch.obj)
      @st.inc(:database_writes)
      if ch.noswap?  # here we have a problem we can't use this

        # first push it back onto the list

        @cache[hv].unshift ch
        # get ourselves a brand new

        ch = CacheEntry.new
        # problem solved

        # the depth of any list chains will be cache_depth + # noswap entries

      end
    end

    # assign our new object to the cache entry

    ch.obj = ret
    ch.oid = oid
    ch.clean!
    # push it to the head of the list

    @cache[hv].unshift ch

    ret
  end

Our simple hash algoritm. Our database keys are sequentially assigned integers, so… I don‘t know what would be better.

[Source]

# File lib/storage/cache.rb, line 144
  def hash(oid)
    oid % @cwidth
  end

report on the cache map see cmd_dumpcache

[Source]

# File lib/storage/cache.rb, line 130
  def inspect
    str = "cache width:#{@cwidth} cache depth:#{@cdepth}\n"
    @cwidth.times do |i|
      @cache[i].each_with_index do |ce,j|
        next if ce.dead?
        str << "cache map [#{i}][#{j}] => "
        str << "oid #{ce.oid} object_id #{ce.obj.object_id} dirty? #{ce.dirty?}  noswap? #{ce.noswap?}\n"
      end
    end
    str
  end

This is provided to traverse the entire object chain. This thrashes the cache violently. See Store#each. One needs to question the design of routines that use Store#each above.

[Source]

# File lib/storage/cache.rb, line 359
  def keys
    kys = []
    @cwidth.times do |i|
      @cache[i].each {|ce| kys << ce.oid if !ce.oid.nil? }
    end
    kys
  end

Mark an object in the cache as nonswappable

oid
is the object id
return
undefined

[Source]

# File lib/storage/cache.rb, line 319
  def makenoswap(oid)
    return nil if oid.nil?
    @st.inc(:cache_noswap)
    hv = hash(oid)

    @cache[hv].each do |ce|
      next if ce.oid != oid
      ce.noswap!
      return
    end

    # This would indicate we've tried marking it noswap before we did a put.

    # A logic error

    @st.inc(:cache_noswap_misses)
    log.debug "Marking object nonswappable before put - #{oid}"
  end

Mark an object in the cache as swappable

oid
is the object id
return
undefined

[Source]

# File lib/storage/cache.rb, line 339
  def makeswap(oid)
    return nil if oid.nil?
    @st.inc(:cache_swap)
    hv = hash(oid)

    @cache[hv].each do |ce|
      next if ce.oid != oid
      ce.swap!
      return
    end

    # This would indicate we've tried marking it swap before we did a put.

    # A logic error

    @st.inc(:cache_swap_misses)
    log.debug "Marking object swappable before put - #{oid}"
  end

deliberately mark an object in the cache as dirty see properties.

[Source]

# File lib/storage/cache.rb, line 271
  def mark(oid)
    return nil if oid.nil?
    @st.inc(:cache_marks)
    hv = hash(oid)

    @cache[hv].each do |ce|
      next if ce.oid != oid
      ce.dirty!
      return
    end

    # this would indicate we've tried marking it dirty before we did a put.

    @st.inc(:cache_mark_misses)
    log.debug "Marking object dirty before put - #{oid}"
  end

puts an object

obj
- The integer object id to retrieve
return
- undefined

[Source]

# File lib/storage/cache.rb, line 203
  def put(obj)
    return nil if obj.nil?
    @st.inc(:writes)
    hv = hash(obj.id)

    # search the cache

    @cache[hv].each do |ce|
      next if ce.oid != obj.id
      @st.inc(:cache_write_hits) if ce.dirty?
      if obj.object_id != ce.obj.object_id  # be safe

        # Be extra careful here.  It's possible we could have two objects

        # with the same object id but with different Ruby object_ids.

        # Most likely this is a bug, but we should handle it here..

        ce.obj = obj
        log.warn "Duplicate object id's in cache!"
        log.warn "insert - #{obj.inspect}"
        log.warn "cache - #{obj.inspect}"
      end
      # need to try putting hot hit to the head of the list?

      ce.dirty!
      return
    end

    # get and remove the last entry off this list

    ch = @cache[hv].pop
    # if its dirty we write it to the database

    if ch.dirty? && !ch.dead?
      # errors possible - check in store module

      @db[ch.oid.to_s] = Utility.encode(ch.obj)
      @st.inc(:database_writes)
      if ch.noswap?  # here we have a problem we can't use this

        # first push it back onto the list

        @cache[hv].unshift ch
        # get ourselves a brand new

        ch = CacheEntry.new
        # problem solved

        # the depth of any list chains will be cache_depth + # noswap entries

      end
    end

    # assign our new object to the cache entry

    ch.obj = obj
    ch.oid = obj.id
    ch.dirty!
    # push it to the head of the list

    @cache[hv].unshift ch

    return
  end

produce a report - see cmd_stats

[Source]

# File lib/storage/cache.rb, line 368
  def stats
    str =  "----------* Cache Statistics *----------\n"
    @st.each do |k,v|
      str << sprintf("%25-25s : %d\n",k.to_s.gsub(/_/,' '),v)
    end
    str << "----------*                  *----------\n"
  end

syncronize the entire cache with the database called by save.

[Source]

# File lib/storage/cache.rb, line 289
  def sync
    @st.inc(:cache_syncs)
    # search the list chains for dirty objects and write them out.

    @cwidth.times do |i|
      @cache[i].each do |ce|
        if ce.dirty?
          @db[ce.oid.to_s] = Utility.encode(ce.obj)
          @st.inc(:database_writes)
          ce.clean!
        end
      end
    end
  end

syncronize a specific list chain not yet called but included for possible performance enhancement

[Source]

# File lib/storage/cache.rb, line 305
  def sync_chain(i)
    @st.inc(:chain_syncs)
    @cache[i].each do |ce|
      if ce.dirty?
        @db[ce.oid.to_s] = Utility.encode(ce.obj)
        @st.inc(:database_writes)
        ce.clean!
      end
    end
  end

[Validate]