perobs 3.0.1 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|