Class: PEROBS::Cache

Inherits:
Object
  • Object
show all
Defined in:
lib/perobs/Cache.rb

Overview

The Cache provides two functions for the PEROBS Store. It keeps some amount of objects in memory to substantially reduce read access latencies. It also stores a list of objects that haven’t been synced to the permanent store yet to accelerate object writes.

Instance Method Summary collapse

Constructor Details

#initialize(bits = 16) ⇒ Cache

Create a new Cache object.

Parameters:

  • bits (Fixnum) (defaults to: 16)

    Number of bits for the cache index. This parameter heavilty affects the performance and memory consumption of the cache.



42
43
44
45
46
47
48
49
# File 'lib/perobs/Cache.rb', line 42

def initialize(bits = 16)
  @bits = bits
  # This mask is used to access the _bits_ least significant bits of the
  # object ID.
  @mask = 2 ** bits - 1
  # Initialize the read and write cache
  reset
end

Instance Method Details

#abort_transactionObject

Tell the cache to abort the currently active transaction. All modified objects will be restored from the storage back-end to their state before the transaction started.



181
182
183
184
185
186
# File 'lib/perobs/Cache.rb', line 181

def abort_transaction
  if @transaction_stack.empty?
    raise RuntimeError, 'No ongoing transaction to abort'
  end
  @transaction_stack.pop.each { |o| o._restore(@transaction_stack.length) }
end

#begin_transactionObject

Tell the cache to start a new transaction. If no other transaction is active, the write cached is flushed before the transaction is started.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/perobs/Cache.rb', line 141

def begin_transaction
  if @transaction_stack.empty?
    # This is the top-level transaction. Flush the write buffer to save
    # the current state of all objects.
    flush
  else
    @transaction_stack.last.each do |o|
      o._stash(@transaction_stack.length - 1)
    end
  end
  # Push a transaction buffer onto the transaction stack. This buffer will
  # hold a reference to all objects modified during this transaction.
  @transaction_stack.push(::Array.new)
end

#cache_read(obj) ⇒ Object

Add an PEROBS::Object to the read cache.

Parameters:



53
54
55
56
57
58
59
60
61
# File 'lib/perobs/Cache.rb', line 53

def cache_read(obj)
  # This is just a safety check. It can probably be disabled in the future
  # to increase performance.
  if obj.respond_to?(:is_poxreference?)
    # If this condition triggers, we have a bug in the library.
    raise RuntimeError, "POXReference objects should never be cached"
  end
  @reads[index(obj)] = obj
end

#cache_write(obj) ⇒ Object

Add a PEROBS::Object to the write cache.

Parameters:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/perobs/Cache.rb', line 65

def cache_write(obj)
  # This is just a safety check. It can probably be disabled in the future
  # to increase performance.
  if obj.respond_to?(:is_poxreference?)
    # If this condition triggers, we have a bug in the library.
    raise RuntimeError, "POXReference objects should never be cached"
  end
  if @transaction_stack.empty?
    idx = index(obj)
    if (old_obj = @writes[idx]) && old_obj._id != obj._id
      # There is another old object using this cache slot. Before we can
      # re-use the slot, we need to sync it to the permanent storage.
      old_obj._sync
    end
    @writes[idx] = obj
  else
    # When a transaction is active, we don't have a write cache. The read
    # cache is used to speed up access to recently used objects.
    cache_read(obj)
    # Push the reference of the modified object into the write buffer for
    # this transaction level.
    unless @transaction_stack.last.include?(obj)
      @transaction_stack.last << obj
    end
  end
end

#end_transactionObject

Tell the cache to end the currently active transaction. All write operations of the current transaction will be synced to the storage back-end.



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/perobs/Cache.rb', line 159

def end_transaction
  case @transaction_stack.length
  when 0
    raise RuntimeError, 'No ongoing transaction to end'
  when 1
    # All transactions completed successfully. Write all modified objects
    # into the backend storage.
    @transaction_stack.pop.each { |o| o._sync }
  else
    # A nested transaction completed successfully. We add the list of
    # modified objects to the list of the enclosing transaction.
    transactions = @transaction_stack.pop
    # Merge the two lists
    @transaction_stack.push(@transaction_stack.pop + transactions)
    # Ensure that each object is only included once in the list.
    @transaction_stack.last.uniq!
  end
end

#flushObject

Flush all pending writes to the persistant storage back-end.



127
128
129
130
# File 'lib/perobs/Cache.rb', line 127

def flush
  @writes.each { |w| w._sync if w }
  @writes = ::Array.new(2 ** @bits)
end

#in_transaction?true/false

Returns true if the Cache is currently handling a transaction, false otherwise.

Returns:

  • (true/false)


135
136
137
# File 'lib/perobs/Cache.rb', line 135

def in_transaction?
  !@transaction_stack.empty?
end

#inspectObject

Don’t include the cache buffers in output of other objects that reference Cache.



201
202
# File 'lib/perobs/Cache.rb', line 201

def inspect
end

#object_by_id(id) ⇒ Object

Return the PEROBS::Object with the specified ID or nil if not found.

Parameters:

  • id (Fixnum or Bignum)

    ID of the cached PEROBS::ObjectBase



111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/perobs/Cache.rb', line 111

def object_by_id(id)
  idx = id & @mask
  # The index is just a hash. We still need to check if the object IDs are
  # actually the same before we can return the object.
  if (obj = @writes[idx]) && obj._id == id
    # The object was in the write cache.
    return obj
  elsif (obj = @reads[idx]) && obj._id == id
    # The object was in the read cache.
    return obj
  end

  nil
end

#resetObject

Clear all cached entries. You must call flush before calling this method. Otherwise unwritten objects will be lost.



190
191
192
193
194
195
196
197
# File 'lib/perobs/Cache.rb', line 190

def reset
  # The read and write caches are Arrays. We use the _bits_ least
  # significant bits of the PEROBS::ObjectBase ID to select the index in
  # the read or write cache Arrays.
  @reads = ::Array.new(2 ** @bits)
  @writes = ::Array.new(2 ** @bits)
  @transaction_stack = []
end

#unwrite(obj) ⇒ Object

Remove an object from the write cache. This will prevent a modified object from being written to the back-end store.



94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/perobs/Cache.rb', line 94

def unwrite(obj)
  if @transaction_stack.empty?
    idx = index(obj)
    if (old_obj = @writes[idx]).nil? || old_obj._id != obj._id
      raise RuntimeError, "Object to unwrite is not in cache"
    end
    @writes[idx] = nil
  else
    unless @transaction_stack.last.include?(obj)
      raise RuntimeError, 'unwrite failed'
    end
    @transaction_stack.last.delete(obj)
  end
end