perobs 3.0.1 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +19 -18
- data/lib/perobs.rb +2 -0
- data/lib/perobs/Array.rb +68 -21
- data/lib/perobs/BTree.rb +110 -54
- data/lib/perobs/BTreeBlob.rb +14 -13
- data/lib/perobs/BTreeDB.rb +11 -10
- data/lib/perobs/BTreeNode.rb +551 -197
- data/lib/perobs/BTreeNodeCache.rb +10 -8
- data/lib/perobs/BTreeNodeLink.rb +11 -1
- data/lib/perobs/BigArray.rb +285 -0
- data/lib/perobs/BigArrayNode.rb +1002 -0
- data/lib/perobs/BigHash.rb +246 -0
- data/lib/perobs/BigTree.rb +197 -0
- data/lib/perobs/BigTreeNode.rb +873 -0
- data/lib/perobs/Cache.rb +47 -22
- data/lib/perobs/ClassMap.rb +2 -2
- data/lib/perobs/ConsoleProgressMeter.rb +61 -0
- data/lib/perobs/DataBase.rb +4 -3
- data/lib/perobs/DynamoDB.rb +62 -20
- data/lib/perobs/EquiBlobsFile.rb +174 -59
- data/lib/perobs/FNV_Hash_1a_64.rb +54 -0
- data/lib/perobs/FlatFile.rb +536 -242
- data/lib/perobs/FlatFileBlobHeader.rb +120 -84
- data/lib/perobs/FlatFileDB.rb +58 -27
- data/lib/perobs/FuzzyStringMatcher.rb +175 -0
- data/lib/perobs/Hash.rb +129 -35
- data/lib/perobs/IDList.rb +144 -0
- data/lib/perobs/IDListPage.rb +107 -0
- data/lib/perobs/IDListPageFile.rb +180 -0
- data/lib/perobs/IDListPageRecord.rb +142 -0
- data/lib/perobs/LockFile.rb +3 -0
- data/lib/perobs/Object.rb +28 -20
- data/lib/perobs/ObjectBase.rb +53 -10
- data/lib/perobs/PersistentObjectCache.rb +142 -0
- data/lib/perobs/PersistentObjectCacheLine.rb +99 -0
- data/lib/perobs/ProgressMeter.rb +97 -0
- data/lib/perobs/SpaceManager.rb +273 -0
- data/lib/perobs/SpaceTree.rb +63 -47
- data/lib/perobs/SpaceTreeNode.rb +134 -115
- data/lib/perobs/SpaceTreeNodeLink.rb +1 -1
- data/lib/perobs/StackFile.rb +1 -1
- data/lib/perobs/Store.rb +180 -70
- data/lib/perobs/version.rb +1 -1
- data/perobs.gemspec +4 -4
- data/test/Array_spec.rb +48 -39
- data/test/BTreeDB_spec.rb +2 -2
- data/test/BTree_spec.rb +50 -1
- data/test/BigArray_spec.rb +261 -0
- data/test/BigHash_spec.rb +152 -0
- data/test/BigTreeNode_spec.rb +153 -0
- data/test/BigTree_spec.rb +259 -0
- data/test/EquiBlobsFile_spec.rb +105 -5
- data/test/FNV_Hash_1a_64_spec.rb +59 -0
- data/test/FlatFileDB_spec.rb +199 -15
- data/test/FuzzyStringMatcher_spec.rb +261 -0
- data/test/Hash_spec.rb +27 -16
- data/test/IDList_spec.rb +77 -0
- data/test/LegacyDBs/LegacyDB.rb +155 -0
- data/test/LegacyDBs/version_3/class_map.json +1 -0
- data/test/LegacyDBs/version_3/config.json +1 -0
- data/test/LegacyDBs/version_3/database.blobs +0 -0
- data/test/LegacyDBs/version_3/database_spaces.blobs +0 -0
- data/test/LegacyDBs/version_3/index.blobs +0 -0
- data/test/LegacyDBs/version_3/version +1 -0
- data/test/LockFile_spec.rb +9 -6
- data/test/Object_spec.rb +5 -5
- data/test/SpaceManager_spec.rb +176 -0
- data/test/SpaceTree_spec.rb +27 -9
- data/test/Store_spec.rb +353 -206
- data/test/perobs_spec.rb +7 -3
- data/test/spec_helper.rb +9 -4
- metadata +59 -16
- data/lib/perobs/SpaceTreeNodeCache.rb +0 -76
- data/lib/perobs/TreeDB.rb +0 -277
data/lib/perobs/Object.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
#
|
3
3
|
# = Object.rb -- Persistent Ruby Object Store
|
4
4
|
#
|
5
|
-
# Copyright (c) 2015, 2016 by Chris Schlaeger <chris@taskjuggler.org>
|
5
|
+
# Copyright (c) 2015, 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
|
6
6
|
#
|
7
7
|
# MIT License
|
8
8
|
#
|
@@ -49,9 +49,11 @@ module PEROBS
|
|
49
49
|
attr_reader :attributes
|
50
50
|
|
51
51
|
# This method can be used to define instance variable for
|
52
|
-
# PEROBS::Object derived classes.
|
52
|
+
# PEROBS::Object derived classes. Persistent attributes always have
|
53
|
+
# getter and setter methods defined. So it's essentially equivalent to
|
54
|
+
# attr_accessor but additionally declares an attribute as persistent.
|
53
55
|
# @param attributes [Symbol] Name of the instance variable
|
54
|
-
def
|
56
|
+
def attr_persist(*attributes)
|
55
57
|
attributes.each do |attr_name|
|
56
58
|
unless attr_name.is_a?(Symbol)
|
57
59
|
PEROBS.log.fatal "name must be a symbol but is a " +
|
@@ -73,6 +75,9 @@ module PEROBS
|
|
73
75
|
end
|
74
76
|
end
|
75
77
|
|
78
|
+
# This is the deprecated name for the attr_persist method
|
79
|
+
alias po_attr attr_persist
|
80
|
+
|
76
81
|
end
|
77
82
|
|
78
83
|
attr_reader :attributes
|
@@ -108,7 +113,7 @@ module PEROBS
|
|
108
113
|
# object was saved with an earlier version of the program that did not yet
|
109
114
|
# have the instance variable. If you want to assign another PEROBS object
|
110
115
|
# to the variable you should use the block variant to avoid unnecessary
|
111
|
-
# creation of PEROBS
|
116
|
+
# creation of PEROBS objects that later need to be collected again.
|
112
117
|
def attr_init(attr, val = nil, &block)
|
113
118
|
if _all_attributes.include?(attr)
|
114
119
|
unless instance_variable_defined?('@' + attr.to_s)
|
@@ -140,7 +145,7 @@ module PEROBS
|
|
140
145
|
|
141
146
|
# Return a list of all object IDs that the attributes of this instance are
|
142
147
|
# referencing.
|
143
|
-
# @return [Array of
|
148
|
+
# @return [Array of Integer] IDs of referenced objects
|
144
149
|
def _referenced_object_ids
|
145
150
|
ids = []
|
146
151
|
_all_attributes.each do |attr|
|
@@ -152,7 +157,7 @@ module PEROBS
|
|
152
157
|
|
153
158
|
# This method should only be used during store repair operations. It will
|
154
159
|
# delete all references to the given object ID.
|
155
|
-
# @param id [
|
160
|
+
# @param id [Integer] targeted object ID
|
156
161
|
def _delete_reference_to_id(id)
|
157
162
|
_all_attributes.each do |attr|
|
158
163
|
ivar = ('@' + attr.to_s).to_sym
|
@@ -207,17 +212,7 @@ module PEROBS
|
|
207
212
|
end
|
208
213
|
|
209
214
|
def _set(attr, val)
|
210
|
-
|
211
|
-
# References to other PEROBS::Objects must be handled somewhat
|
212
|
-
# special.
|
213
|
-
if @store != val.store
|
214
|
-
PEROBS.log.fatal 'The referenced object is not part of this store'
|
215
|
-
end
|
216
|
-
elsif val.is_a?(ObjectBase)
|
217
|
-
PEROBS.log.fatal 'A PEROBS::ObjectBase object escaped! ' +
|
218
|
-
'Have you used self() instead of myself() to get the reference ' +
|
219
|
-
'of the PEROBS object that you are trying to assign here?'
|
220
|
-
end
|
215
|
+
_check_assignment_value(val)
|
221
216
|
instance_variable_set(('@' + attr.to_s).to_sym, val)
|
222
217
|
# Let the store know that we have a modified object. If we restored the
|
223
218
|
# object from the DB, we don't mark it as modified.
|
@@ -231,13 +226,26 @@ module PEROBS
|
|
231
226
|
end
|
232
227
|
|
233
228
|
def _all_attributes
|
229
|
+
# Collect all persistent attributes from this class and all
|
230
|
+
# super classes into a single Array.
|
231
|
+
attributes = []
|
232
|
+
klass = self.class
|
233
|
+
while klass && klass.respond_to?(:attributes)
|
234
|
+
if (attrs = klass.attributes)
|
235
|
+
attributes += attrs
|
236
|
+
end
|
237
|
+
klass = klass.superclass
|
238
|
+
end
|
239
|
+
|
234
240
|
# PEROBS objects that don't have persistent attributes declared don't
|
235
241
|
# really make sense.
|
236
|
-
|
242
|
+
if attributes.empty?
|
237
243
|
PEROBS.log.fatal "No persistent attributes have been declared for " +
|
238
|
-
"class #{self.class}. Use '
|
244
|
+
"class #{self.class} or any parent class. Use 'attr_persist' " +
|
245
|
+
"to declare them."
|
239
246
|
end
|
240
|
-
|
247
|
+
|
248
|
+
attributes
|
241
249
|
end
|
242
250
|
|
243
251
|
end
|
data/lib/perobs/ObjectBase.rb
CHANGED
@@ -86,6 +86,10 @@ module PEROBS
|
|
86
86
|
_referenced_object == obj
|
87
87
|
end
|
88
88
|
|
89
|
+
def eql?(obj)
|
90
|
+
_referenced_object._id == obj._id
|
91
|
+
end
|
92
|
+
|
89
93
|
# BasicObject provides a equal?() method that prevents method_missing from
|
90
94
|
# being called. So we have to pass the call manually to the referenced
|
91
95
|
# object.
|
@@ -98,6 +102,13 @@ module PEROBS
|
|
98
102
|
end
|
99
103
|
end
|
100
104
|
|
105
|
+
# To allow POXReference objects to be used as Hash keys we need to
|
106
|
+
# implement this function. Conveniently, we can just use the PEROBS object
|
107
|
+
# ID since that is unique.
|
108
|
+
def hash
|
109
|
+
@id
|
110
|
+
end
|
111
|
+
|
101
112
|
# Shortcut to access the _id() method of the referenced object.
|
102
113
|
def _id
|
103
114
|
@id
|
@@ -114,6 +125,20 @@ module PEROBS
|
|
114
125
|
# common to all classes of persistent objects.
|
115
126
|
class ObjectBase
|
116
127
|
|
128
|
+
# This is a list of the native Ruby classes that are supported for
|
129
|
+
# instance variable assignements in addition to other PEROBS objects.
|
130
|
+
if RUBY_VERSION < '2.2'
|
131
|
+
NATIVE_CLASSES = [
|
132
|
+
NilClass, Integer, Bignum, Fixnum, Float, String, Time,
|
133
|
+
TrueClass, FalseClass
|
134
|
+
]
|
135
|
+
else
|
136
|
+
NATIVE_CLASSES = [
|
137
|
+
NilClass, Integer, Float, String, Time,
|
138
|
+
TrueClass, FalseClass
|
139
|
+
]
|
140
|
+
end
|
141
|
+
|
117
142
|
attr_reader :_id, :store, :myself
|
118
143
|
|
119
144
|
# New PEROBS objects must always be created by calling # Store.new().
|
@@ -133,7 +158,8 @@ module PEROBS
|
|
133
158
|
@store = p.store
|
134
159
|
@_id = p.id
|
135
160
|
@store._register_in_memory(self, @_id)
|
136
|
-
ObjectSpace.define_finalizer(
|
161
|
+
ObjectSpace.define_finalizer(
|
162
|
+
self, ObjectBase._finalize(@store, @_id, object_id))
|
137
163
|
@_stash_map = nil
|
138
164
|
# Allocate a proxy object for this object. User code should only operate
|
139
165
|
# on this proxy, never on self.
|
@@ -144,8 +170,8 @@ module PEROBS
|
|
144
170
|
# is done this way to prevent the Proc object hanging on to a reference to
|
145
171
|
# self which would prevent the object from being collected. This internal
|
146
172
|
# method is not intended for users to call.
|
147
|
-
def ObjectBase._finalize(store, id)
|
148
|
-
proc { store._collect(id) }
|
173
|
+
def ObjectBase._finalize(store, id, ruby_object_id)
|
174
|
+
proc { store._collect(id, ruby_object_id) }
|
149
175
|
end
|
150
176
|
|
151
177
|
# Library internal method to transfer the Object to a new store.
|
@@ -158,7 +184,8 @@ module PEROBS
|
|
158
184
|
# Register the object as in-memory object with the new store.
|
159
185
|
@store._register_in_memory(self, @_id)
|
160
186
|
# Register the finalizer for the new store.
|
161
|
-
ObjectSpace.define_finalizer(
|
187
|
+
ObjectSpace.define_finalizer(
|
188
|
+
self, ObjectBase._finalize(@store, @_id, object_id))
|
162
189
|
@myself = POXReference.new(@store, @_id)
|
163
190
|
end
|
164
191
|
|
@@ -190,6 +217,25 @@ module PEROBS
|
|
190
217
|
@store.db.put_object(db_obj, @_id)
|
191
218
|
end
|
192
219
|
|
220
|
+
#
|
221
|
+
def _check_assignment_value(val)
|
222
|
+
if val.respond_to?(:is_poxreference?)
|
223
|
+
# References to other PEROBS::Objects must be handled somewhat
|
224
|
+
# special.
|
225
|
+
if @store != val.store
|
226
|
+
PEROBS.log.fatal 'The referenced object is not part of this store'
|
227
|
+
end
|
228
|
+
elsif val.is_a?(ObjectBase)
|
229
|
+
PEROBS.log.fatal 'A PEROBS::ObjectBase object escaped! ' +
|
230
|
+
'Have you used self() instead of myself() to get the reference ' +
|
231
|
+
'of the PEROBS object that you are trying to assign here?'
|
232
|
+
elsif !NATIVE_CLASSES.include?(val.class)
|
233
|
+
PEROBS.log.fatal "Assigning objects of class #{val.class} is not " +
|
234
|
+
"supported. Only PEROBS objects or one of the following classes " +
|
235
|
+
"are supported: #{NATIVE_CLASSES.join(', ')}"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
193
239
|
# Read an raw object with the specified ID from the backing store and
|
194
240
|
# instantiate a new object of the specific type.
|
195
241
|
def ObjectBase.read(store, id)
|
@@ -207,19 +253,16 @@ module PEROBS
|
|
207
253
|
end
|
208
254
|
|
209
255
|
# Restore the object state from the storage back-end.
|
210
|
-
# @param level [
|
256
|
+
# @param level [Integer] the transaction nesting level
|
211
257
|
def _restore(level)
|
212
258
|
# Find the most recently stored state of this object. This could be on
|
213
259
|
# any previous stash level or in the regular object DB. If the object
|
214
|
-
# was created during the transaction, there is
|
260
|
+
# was created during the transaction, there is no previous state to
|
215
261
|
# restore to.
|
216
262
|
data = nil
|
217
263
|
if @_stash_map
|
218
264
|
(level - 1).downto(0) do |lvl|
|
219
|
-
if @_stash_map[lvl]
|
220
|
-
data = @_stash_map[lvl]
|
221
|
-
break
|
222
|
-
end
|
265
|
+
break if (data = @_stash_map[lvl])
|
223
266
|
end
|
224
267
|
end
|
225
268
|
if data
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = PersistentObjectCache.rb -- Persistent Ruby Object Store
|
4
|
+
#
|
5
|
+
# Copyright (c) 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
|
6
|
+
#
|
7
|
+
# MIT License
|
8
|
+
#
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
# a copy of this software and associated documentation files (the
|
11
|
+
# "Software"), to deal in the Software without restriction, including
|
12
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
# the following conditions:
|
16
|
+
#
|
17
|
+
# The above copyright notice and this permission notice shall be
|
18
|
+
# included in all copies or substantial portions of the Software.
|
19
|
+
#
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
|
+
|
28
|
+
require 'perobs/PersistentObjectCacheLine'
|
29
|
+
|
30
|
+
module PEROBS
|
31
|
+
|
32
|
+
class PersistentObjectCache
|
33
|
+
|
34
|
+
# This cache class manages the presence of objects that primarily live in
|
35
|
+
# a backing store but temporarily exist in memory as well. To work with
|
36
|
+
# these objects, direct references must be only very short lived. Indirect
|
37
|
+
# references can be done via a unique ID that the object must provide. Due
|
38
|
+
# to the indirect references the Ruby garbage collector can collect these
|
39
|
+
# objects. To reduce the read and write latencies of the backing store
|
40
|
+
# this class keeps a subset of the objects in memory which prevents them
|
41
|
+
# from being collected. All references to the objects must be resolved via
|
42
|
+
# the get() method to prevent duplicate instances in memory of the same
|
43
|
+
# in-store object. The cache uses a least-recently-used (LRU) scheme to
|
44
|
+
# cache objects.
|
45
|
+
# @param size [Integer] Minimum number of objects to be cached at a time
|
46
|
+
# @param flush_delay [Integer] Determines how often non-forced flushes are
|
47
|
+
# ignored in a row before the flush is really done. If flush_delay
|
48
|
+
# is smaller than 0 non-forced flushed will always be ignored.
|
49
|
+
# @param klass [Class] The class of the objects to be cached. Objects must
|
50
|
+
# provide a uid() method that returns a unique ID for every object.
|
51
|
+
# @param collection [] The object collection the objects belong to. It
|
52
|
+
# must provide a ::load method.
|
53
|
+
def initialize(size, flush_delay, klass, collection)
|
54
|
+
@size = size
|
55
|
+
@klass = klass
|
56
|
+
@collection = collection
|
57
|
+
@flush_delay = @flush_counter = flush_delay
|
58
|
+
@flush_times = 0
|
59
|
+
|
60
|
+
clear
|
61
|
+
end
|
62
|
+
|
63
|
+
# Insert an object into the cache.
|
64
|
+
# @param object [Object] Object to cache
|
65
|
+
# @param modified [Boolean] True if the object was modified, false otherwise
|
66
|
+
def insert(object, modified = true)
|
67
|
+
unless object.is_a?(@klass)
|
68
|
+
raise ArgumentError, "You can insert only #{@klass} objects in this " +
|
69
|
+
"cache. You have tried to insert a #{object.class} instead."
|
70
|
+
end
|
71
|
+
|
72
|
+
if modified
|
73
|
+
@modified_entries[object.uid] = object
|
74
|
+
else
|
75
|
+
@unmodified_entries[object.uid % @size] = object
|
76
|
+
end
|
77
|
+
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
|
81
|
+
# Retrieve a object reference from the cache.
|
82
|
+
# @param uid [Integer] uid of the object to retrieve.
|
83
|
+
# @param ref [Object] optional reference to be used by the load method
|
84
|
+
def get(uid, ref = nil)
|
85
|
+
# First check if it's a modified object.
|
86
|
+
if (object = @modified_entries[uid])
|
87
|
+
return object
|
88
|
+
end
|
89
|
+
|
90
|
+
# Then check the unmodified object list.
|
91
|
+
if (object = @unmodified_entries[uid % @size]) && object.uid == uid
|
92
|
+
return object
|
93
|
+
end
|
94
|
+
|
95
|
+
# If we don't have it in memory we need to load it.
|
96
|
+
@klass::load(@collection, uid, ref)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Remove a object from the cache.
|
100
|
+
# @param uid [Integer] unique ID of object to remove.
|
101
|
+
def delete(uid)
|
102
|
+
@modified_entries.delete(uid)
|
103
|
+
|
104
|
+
index = uid % @size
|
105
|
+
if (object = @unmodified_entries[index]) && object.uid == uid
|
106
|
+
@unmodified_entries[index] = nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Write all excess modified objects into the backing store. If now is true
|
111
|
+
# all modified objects will be written.
|
112
|
+
# @param now [Boolean]
|
113
|
+
def flush(now = false)
|
114
|
+
if now || (@flush_delay >= 0 && (@flush_counter -= 1) <= 0)
|
115
|
+
@modified_entries.each do |id, object|
|
116
|
+
object.save
|
117
|
+
# Add the object to the unmodified object cache. We might still need
|
118
|
+
# it again soon.
|
119
|
+
@unmodified_entries[object.uid % @size] = object
|
120
|
+
end
|
121
|
+
@modified_entries = ::Hash.new
|
122
|
+
@flush_counter = @flush_delay
|
123
|
+
end
|
124
|
+
@flush_times += 1
|
125
|
+
end
|
126
|
+
|
127
|
+
# Remove all entries from the cache.
|
128
|
+
def clear
|
129
|
+
# This Array stores all unmodified entries. It has a fixed size and uses
|
130
|
+
# a % operation to compute the index from the object ID.
|
131
|
+
@unmodified_entries = ::Array.new(@size)
|
132
|
+
|
133
|
+
# This Hash stores all modified entries. It can grow and shrink as
|
134
|
+
# needed. A flush operation writes all modified objects into the backing
|
135
|
+
# store.
|
136
|
+
@modified_entries = ::Hash.new
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = PersistentObjectCacheLine.rb -- Persistent Ruby Object Store
|
4
|
+
#
|
5
|
+
# Copyright (c) 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
|
6
|
+
#
|
7
|
+
# MIT License
|
8
|
+
#
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
# a copy of this software and associated documentation files (the
|
11
|
+
# "Software"), to deal in the Software without restriction, including
|
12
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
# the following conditions:
|
16
|
+
#
|
17
|
+
# The above copyright notice and this permission notice shall be
|
18
|
+
# included in all copies or substantial portions of the Software.
|
19
|
+
#
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
|
+
|
28
|
+
module PEROBS
|
29
|
+
|
30
|
+
class PersistentObjectCacheLine
|
31
|
+
|
32
|
+
# Utility class to store persistent objects and their
|
33
|
+
# modified/not-modified state.
|
34
|
+
class Entry < Struct.new(:obj, :modified)
|
35
|
+
end
|
36
|
+
|
37
|
+
# This defines the minimum size of the cache line. If it is too large, the
|
38
|
+
# time to find an entry will grow too much. If it is too small the number
|
39
|
+
# of cache lines will be too large and create more store overhead. By
|
40
|
+
# running benchmarks it turned out that 8 is a pretty good compromise.
|
41
|
+
WATERMARK = 8
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@entries = []
|
45
|
+
end
|
46
|
+
|
47
|
+
def insert(object, modified)
|
48
|
+
if (index = @entries.find_index{ |e| e.obj.uid == object.uid })
|
49
|
+
# We have found and removed an existing entry for this particular
|
50
|
+
# object. If the modified flag is set, ensure that the entry has it
|
51
|
+
# set as well.
|
52
|
+
entry = @entries.delete_at(index)
|
53
|
+
entry.modified = true if modified && !entry.modified
|
54
|
+
else
|
55
|
+
# There is no existing entry for this object. Create a new one.
|
56
|
+
entry = Entry.new(object, modified)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Insert the entry at the beginning of the line.
|
60
|
+
@entries.unshift(entry)
|
61
|
+
end
|
62
|
+
|
63
|
+
def get(uid)
|
64
|
+
if (index = @entries.find_index{ |e| e.obj.uid == uid })
|
65
|
+
if index > 0
|
66
|
+
# Move the entry to the front.
|
67
|
+
@entries.unshift(@entries.delete_at(index))
|
68
|
+
end
|
69
|
+
@entries.first
|
70
|
+
else
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Delete the entry that matches the given UID
|
76
|
+
# @param uid [Integer]
|
77
|
+
def delete(uid)
|
78
|
+
@entries.delete_if { |e| e.obj.uid == uid }
|
79
|
+
end
|
80
|
+
|
81
|
+
# Save all modified entries and delete all but the most recently added.
|
82
|
+
def flush(now)
|
83
|
+
if now || @entries.length > WATERMARK
|
84
|
+
@entries.each do |e|
|
85
|
+
if e.modified
|
86
|
+
e.obj.save
|
87
|
+
e.modified = false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Delete all but the first WATERMARK entry.
|
92
|
+
@entries = @entries[0..WATERMARK - 1] if @entries.length > WATERMARK
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|