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 (Integer) (defaults to: 16)

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



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

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.



220
221
222
223
224
225
226
227
228
# File 'lib/perobs/Cache.rb', line 220

def abort_transaction
  if @transaction_stack.empty?
    PEROBS.log.fatal 'No ongoing transaction to abort'
  end
  @transaction_stack.pop.each do |id|
    @transaction_objects[id]._restore(@transaction_stack.length)
  end
  @transaction_thread = nil
end

#begin_transactionObject

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



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/perobs/Cache.rb', line 166

def begin_transaction
  if @transaction_stack.empty?
    if @transaction_thread
      PEROBS.log.fatal 'transaction_thread must be nil'
    end
    @transaction_thread = Thread.current
    # The new transaction is the top-level transaction. Flush the write
    # buffer to save the current state of all objects.
    flush
  else
    # Nested transactions are currently only supported within the same
    # thread. If we are in another thread, raise TransactionInOtherThread
    # to pause the calling thread for a bit.
    if @transaction_thread != Thread.current
      raise TransactionInOtherThread
    end
    # Save a copy of all objects that were modified during the enclosing
    # transaction.
    @transaction_stack.last.each do |id|
      @transaction_objects[id]._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:



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

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.
    PEROBS.log.fatal "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:



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
91
92
93
94
# File 'lib/perobs/Cache.rb', line 66

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.
  #  PEROBS.log.fatal "POXReference objects should never be cached"
  #end

  if @transaction_stack.empty?
    # We are not in transaction mode.
    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._id)
      @transaction_stack.last << obj._id
      @transaction_objects[obj._id] = 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.



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/perobs/Cache.rb', line 196

def end_transaction
  case @transaction_stack.length
  when 0
    PEROBS.log.fatal 'No ongoing transaction to end'
  when 1
    # All transactions completed successfully. Write all modified objects
    # into the backend storage.
    @transaction_stack.pop.each { |id| @transaction_objects[id]._sync }
    @transaction_objects = ::Hash.new
    @transaction_thread = nil
  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 ID is only included once in the list.
    @transaction_stack.last.uniq!
  end
end

#evict(id) ⇒ True/False

Evict the object with the given ID from the cache.

Parameters:

  • id (Integer)

    ID of the cached PEROBS::ObjectBase

Returns:

  • (True/False)

    True if object was stored in the cache. False otherwise.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/perobs/Cache.rb', line 100

def evict(id)
  unless @transaction_stack.empty?
    PEROBS.log.fatal "You cannot evict entries during a transaction."
  end

  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 is in the write cache.
    @writes[idx] = nil
    return true
  elsif (obj = @reads[idx]) && obj._id == id
    # The object is in the read cache.
    @reads[idx] = nil
    return true
  end

  false
end

#flushObject

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



152
153
154
155
# File 'lib/perobs/Cache.rb', line 152

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)


160
161
162
# File 'lib/perobs/Cache.rb', line 160

def in_transaction?
  !@transaction_stack.empty?
end

#inspectObject

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



245
246
# File 'lib/perobs/Cache.rb', line 245

def inspect
end

#object_by_id(id) ⇒ Object

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

Parameters:

  • id (Integer)

    ID of the cached PEROBS::ObjectBase



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/perobs/Cache.rb', line 123

def object_by_id(id)
  idx = id & @mask

  if @transaction_stack.empty?
    # 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
    end
  else
    # During transactions, the read cache is used to provide fast access
    # to modified objects. But it does not store all modified objects
    # since there can be hash collisions. So we also have to check all
    # transaction objects first.
    if (obj = @transaction_objects[id])
      return obj
    end
  end

  if (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.



232
233
234
235
236
237
238
239
240
241
# File 'lib/perobs/Cache.rb', line 232

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 = ::Array.new
  @transaction_thread = nil
  @transaction_objects = ::Hash.new
end