perobs 3.0.2 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 985e62db6b84f509a3b390ce11ac96f4215de5ea
4
- data.tar.gz: f6747e42300df5c9b55c1a5bbc6adf9822165b7b
3
+ metadata.gz: a988f009b1b97bc8da2b2bcfeb65399284a07032
4
+ data.tar.gz: 4346d5cdbec5b4154741f0a41d3f526985803912
5
5
  SHA512:
6
- metadata.gz: 7ed1499555741de10e6810f62041ffc879a738f488f5ce09860bddae21ed12cf999a8d4303361227a3798f3f389bd6cb9584e71e809b26d0f82fa7cdc6a28544
7
- data.tar.gz: 2c56287162c7ae376ecdc091e55655fd37e4985e22914a4e21d74f28eb0c3e054ca543fb7092485ea09605b8a0ad4cc33f13dc96fa19ffcdf5137921c5b145ae
6
+ metadata.gz: 639673c10faa5082c742258ee9848f5e951c6d1ec7fc9f03ec7066260ea5042b6c8b90020df6fa506b8be54cc5fd9793a0939f80ef1da726c27832c8a2eb8c85
7
+ data.tar.gz: 92e63145a1fc9b6e76dbdf6f4cdebfb5a8935bfcbd93a9634e22c12f874b945b656a17fe93aa03a7a3189a00371d2e2abd8d411862ad0ad51b0c364f7b7dc027
data/README.md CHANGED
@@ -30,7 +30,7 @@ classes:
30
30
 
31
31
  When you derive your own class from PEROBS::Object you need to
32
32
  specify which instance variables should be persistent. By using
33
- po_attr you can provide a list of symbols that describe the instance
33
+ 'attr_persist' you can provide a list of symbols that declare the instance
34
34
  variables to persist. This will also create getter and setter methods
35
35
  for these instance varables. You can set default values in the
36
36
  constructor . The constructor of PEROBS::ObjectBase derived objects
@@ -61,13 +61,14 @@ almost every Ruby data type. YAML is much slower than JSON and Marshal
61
61
  is not guaranteed to be compatible between Ruby versions.
62
62
 
63
63
  Once you have created a store you can assign objects to it. All
64
- persistent objects must be created with Store.new(). This is
65
- necessary as you will only deal with proxy objects in your code.
66
- Except for the member methods, you will never deal with the objects
67
- directly. Instead Store.new() returns a POXReference object that acts
68
- as a transparent proxy. This proxy is needed as your code never knows
69
- if the actual object is really loaded into the memory or not. PEROBS
70
- will handle this transparently for you.
64
+ persistent objects must be created with Store.new(). The Store object
65
+ is available via the @store instance variable provided by the parent
66
+ class. This is necessary as you will only deal with proxy objects in
67
+ your code. Except for the member methods, you will never deal with
68
+ the objects directly. Instead Store.new() returns a POXReference
69
+ object that acts as a transparent proxy. This proxy is needed as your
70
+ code never knows if the actual object is really loaded into the memory
71
+ or not. PEROBS will handle this transparently for you.
71
72
 
72
73
  A build-in cache keeps access latencies to recently used objects low
73
74
  and lazily flushes modified objects into the persistend back-end when
@@ -92,19 +93,19 @@ require 'perobs'
92
93
 
93
94
  class Person < PEROBS::Object
94
95
 
95
- po_attr :name, :mother, :father, :kids, :spouse, :status
96
+ attr_persist :name, :mother, :father, :kids, :spouse, :status
96
97
 
97
98
  def initialize(p, name)
98
99
  super(p)
99
- attr_init(:name, name)
100
- attr_init(:kids, store.new(PEROBS::Array))
101
- attr_init(:status, :single)
100
+ self.name = name
101
+ self.kids = @store.new(PEROBS::Array)
102
+ self.status = :single
102
103
  end
103
104
 
104
105
  def restore
105
106
  # Use block version of attr_init() to avoid creating unneded
106
107
  # objects. The block is only called when @father doesn't exist yet.
107
- attr_init(:father) do { store.new(Person, 'Dad') }
108
+ attr_init(:father) do { @store.new(Person, 'Dad') }
108
109
  end
109
110
 
110
111
  def merry(spouse)
@@ -136,7 +137,7 @@ contains the 3 Person objects.
136
137
  ### Accessing persistent instance variables
137
138
 
138
139
  All instance variables that should be persisted must be declared with
139
- 'po_attr'. This will create the instance variable, a getter and setter
140
+ 'attr_persist'. This will create the instance variable, a getter and setter
140
141
  method for it. These getter and setter methods are the recommended way
141
142
  to access instance variables both from ouside of the instances as well
142
143
  as from within. To access the setter or getter method from within an
@@ -166,8 +167,8 @@ object to another object.
166
167
  ### Caveats and known issues
167
168
 
168
169
  PEROBS is currently not thread-safe. You cannot simultaneously access
169
- the database from multiple application. You must provide your own
170
- locking mechanism to prevent this from happening.
170
+ the database from multiple application. The library uses locks to
171
+ ensure that only one Store object is accessing the database at a time.
171
172
 
172
173
  ## Installation
173
174
 
@@ -187,7 +188,7 @@ Or install it yourself as:
187
188
 
188
189
  ## Copyright and License
189
190
 
190
- Copyright (c) 2015, 2016 by Chris Schlaeger <chris@taskjuggler.org>
191
+ Copyright (c) 2015, 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
191
192
 
192
193
  PEROBS and all accompanying files are licensed under this MIT License
193
194
 
data/lib/perobs/BTree.rb CHANGED
@@ -27,7 +27,7 @@
27
27
 
28
28
  require 'perobs/LockFile'
29
29
  require 'perobs/EquiBlobsFile'
30
- require 'perobs/BTreeNodeCache'
30
+ require 'perobs/PersistentObjectCache'
31
31
  require 'perobs/BTreeNode'
32
32
 
33
33
  module PEROBS
@@ -40,7 +40,7 @@ module PEROBS
40
40
  # have N + 1 references to child nodes instead.
41
41
  class BTree
42
42
 
43
- attr_reader :order, :nodes
43
+ attr_reader :order, :nodes, :node_cache
44
44
 
45
45
  # Create a new BTree object.
46
46
  # @param dir [String] Directory to store the tree file
@@ -64,7 +64,7 @@ module PEROBS
64
64
  # This EquiBlobsFile contains the nodes of the BTree.
65
65
  @nodes = EquiBlobsFile.new(@dir, @name,
66
66
  BTreeNode::node_bytes(@order))
67
- @node_cache = BTreeNodeCache.new
67
+ @node_cache = PersistentObjectCache.new(512, BTreeNode, self)
68
68
 
69
69
  # This BTree implementation uses a write cache to improve write
70
70
  # performance of multiple successive read/write operations. This also
@@ -89,8 +89,10 @@ module PEROBS
89
89
 
90
90
  @node_cache.clear
91
91
  @nodes.open
92
- set_root(new_node(nil, @nodes.total_entries == 0 ?
93
- nil : @nodes.first_entry))
92
+ node = @nodes.total_entries == 0 ?
93
+ BTreeNode::create(self) :
94
+ BTreeNode::load(self, @nodes.first_entry)
95
+ set_root(node)
94
96
  end
95
97
 
96
98
  # Close the tree file.
@@ -104,7 +106,7 @@ module PEROBS
104
106
  def clear
105
107
  @node_cache.clear
106
108
  @nodes.clear
107
- set_root(new_node(nil))
109
+ set_root(BTreeNode::create(self))
108
110
  end
109
111
 
110
112
  # Erase the backing store of the BTree. This method should only be called
@@ -112,6 +114,7 @@ module PEROBS
112
114
  # all stored data from the BTree.
113
115
  def erase
114
116
  @nodes.erase
117
+ @root = nil
115
118
  @dirty_flag.forced_unlock
116
119
  end
117
120
 
@@ -132,7 +135,6 @@ module PEROBS
132
135
  def set_root(node)
133
136
  @root = node
134
137
  @nodes.first_entry = node.node_address
135
- @node_cache.set_root(node)
136
138
  end
137
139
 
138
140
  # Insert a new value into the tree using the key as a unique index. If the
@@ -178,17 +180,6 @@ module PEROBS
178
180
  @root.each(&block)
179
181
  end
180
182
 
181
- # Mark the given node as being modified. This will cause the dirty_flag
182
- # lock to be taken and the @is_dirty flag to be set.
183
- # @param node [BTreeNode] node to mark
184
- def mark_node_as_modified(node)
185
- unless @is_dirty
186
- @dirty_flag.lock
187
- @is_dirty = true
188
- end
189
- @node_cache.mark_as_modified(node)
190
- end
191
-
192
183
  # Delete the node at the given address in the BTree file.
193
184
  # @param address [Integer] address in file
194
185
  def delete_node(address)
@@ -201,32 +192,6 @@ module PEROBS
201
192
  @root.to_s
202
193
  end
203
194
 
204
- # Create a new BTreeNode. If the node_address is not nil, the node data is
205
- # read from the backing store. The parent and is_leaf arguments are
206
- # ignored in this case.
207
- # @param parent [BTreeNode] parent node
208
- # @param node_address [Integer or nil] address of the node to create
209
- # @param is_leaf[Boolean] True if node is a leaf node, false otherweise
210
- def new_node(parent, node_address = nil, is_leaf = true)
211
- node = BTreeNode.new(self, parent, node_address, is_leaf)
212
- @node_cache.insert(node)
213
-
214
- node
215
- end
216
-
217
- # Return the BTreeNode that matches the given node address. If a blob
218
- # address and size are given, a new node is created instead of being read
219
- # from the file.
220
- # @param node_address [Integer] Address of the node in the BTree file
221
- # @return [BTreeNode]
222
- def get_node(node_address)
223
- if (node = @node_cache[node_address])
224
- return node
225
- end
226
-
227
- new_node(nil, node_address)
228
- end
229
-
230
195
  end
231
196
 
232
197
  end
@@ -40,7 +40,6 @@ module PEROBS
40
40
  class BTreeNode
41
41
 
42
42
  attr_reader :node_address, :parent, :is_leaf, :keys, :values, :children
43
- attr_accessor :dirty
44
43
 
45
44
  # Create a new BTreeNode object for the given tree with the given parent
46
45
  # or recreate the node with the given node_address from the backing store.
@@ -53,44 +52,118 @@ module PEROBS
53
52
  # backing store
54
53
  # @param is_leaf [Boolean] true if the node should be a leaf node, false
55
54
  # if not
56
- def initialize(tree, parent = nil, node_address = nil, is_leaf = true)
55
+ def initialize(tree, node_address = nil, parent = nil, is_leaf = true,
56
+ keys = [], values = [], children = [])
57
57
  @tree = tree
58
- @parent = nil
59
58
  if node_address == 0
60
59
  PEROBS.log.fatal "Node address may not be 0"
61
60
  end
62
61
  @node_address = node_address
63
- @keys = []
62
+ @parent = parent ? BTreeNodeLink.new(tree, parent) : nil
63
+ @keys = keys
64
64
  if (@is_leaf = is_leaf)
65
- @values = []
66
- else
65
+ @values = values
67
66
  @children = []
67
+ else
68
+ @children = children
69
+ @values = []
68
70
  end
69
71
 
70
- if node_address
71
- unless node_address.is_a?(Integer)
72
- PEROBS.log.fatal "node_address is not Integer: #{node_address.class}"
73
- end
72
+ ObjectSpace.define_finalizer(
73
+ self, BTreeNode._finalize(@tree, @node_address, object_id))
74
+ @tree.node_cache.insert(self, false)
75
+ end
74
76
 
75
- # This must be an existing node. Try to read it and fill the instance
76
- # variables.
77
- unless read_node
78
- PEROBS.log.fatal "SpaceTree node at address #{node_address} " +
79
- "does not exist"
80
- end
77
+ # This method generates the destructor for the objects of this class. It
78
+ # is done this way to prevent the Proc object hanging on to a reference to
79
+ # self which would prevent the object from being collected. This internal
80
+ # method is not intended for users to call.
81
+ def BTreeNode::_finalize(tree, node_address, ruby_object_id)
82
+ proc { tree.node_cache._collect(node_address, ruby_object_id) }
83
+ end
84
+
85
+ # Create a new SpaceTreeNode. This method should be used for the creation
86
+ # of new nodes instead of calling the constructor directly.
87
+ # @param tree [BTree] The tree the new node should belong to
88
+ # @param parent [BTreeNode] The parent node
89
+ # @param is_leaf [Boolean] True if the node has no children, false
90
+ # otherwise
91
+ def BTreeNode::create(tree, parent = nil, is_leaf = true)
92
+ unless parent.nil? || parent.is_a?(BTreeNode) ||
93
+ parent.is_a?(BTreeNodeLink)
94
+ PEROBS.log.fatal "Parent node must be a BTreeNode but is of class " +
95
+ "#{parent.class}"
96
+ end
97
+
98
+ address = tree.nodes.free_address
99
+ node = BTreeNode.new(tree, address, parent, is_leaf)
100
+ # This is a new node. Make sure the data is written to the file.
101
+ tree.node_cache.insert(node)
102
+
103
+ node
104
+ end
105
+
106
+ # Restore a node from the backing store at the given address and tree.
107
+ # @param tree [BTree] The tree the node belongs to
108
+ # @param node_address [Integer] The address in the blob file.
109
+ def BTreeNode::load(tree, address)
110
+ unless address.is_a?(Integer)
111
+ PEROBS.log.fatal "address is not Integer: #{address.class}"
112
+ end
113
+
114
+ unless (bytes = tree.nodes.retrieve_blob(address))
115
+ PEROBS.log.fatal "SpaceTree node at address #{address} " +
116
+ "does not exist"
117
+ end
118
+
119
+ unless Zlib::crc32(bytes) != 0
120
+ PEROBS.log.fatal "Checksum failure in BTreeNode entry @#{address}"
121
+ end
122
+ ary = bytes.unpack(BTreeNode::node_bytes_format(tree))
123
+ # Read is_leaf
124
+ if ary[0] != 0 && ary[0] != 1
125
+ PEROBS.log.fatal "First byte of a BTreeNode entry must be 0 or 1"
126
+ end
127
+ is_leaf = ary[0] == 0 ? false : true
128
+ # This is the number of keys this node has.
129
+ key_count = ary[1]
130
+ data_count = ary[2]
131
+ # Read the parent node address
132
+ parent = ary[3] == 0 ? nil : BTreeNodeLink.new(tree, ary[3])
133
+ # Read the keys
134
+ keys = ary[4, key_count]
135
+
136
+ children = nil
137
+ values = nil
138
+ if is_leaf
139
+ # Read the values
140
+ values = ary[4 + tree.order, data_count]
81
141
  else
82
- unless parent.nil? || parent.is_a?(BTreeNode) ||
83
- parent.is_a?(BTreeNodeLink)
84
- PEROBS.log.fatal "Parent node must be a BTreeNode but is of class " +
85
- "#{parent.class}"
142
+ # Read the child addresses
143
+ children = []
144
+ data_count.times do |i|
145
+ child_address = ary[4 + tree.order + i]
146
+ unless child_address > 0
147
+ PEROBS.log.fatal "Child address must be larger than 0"
148
+ end
149
+ children << BTreeNodeLink.new(tree, child_address)
86
150
  end
87
-
88
- # This is a new node. Make sure the data is written to the file.
89
- @node_address = @tree.nodes.free_address
90
- self.parent = parent
91
151
  end
152
+
153
+ node = BTreeNode.new(tree, address, parent, is_leaf, keys, values,
154
+ children)
155
+ tree.node_cache.insert(node, false)
156
+
157
+ node
158
+ end
159
+
160
+ # @return [String] The format used for String.pack.
161
+ def BTreeNode::node_bytes_format(tree)
162
+ # This does not include the 4 bytes for the CRC32 checksum
163
+ "CSSQQ#{tree.order}Q#{tree.order + 1}"
92
164
  end
93
165
 
166
+ # @return [Integer] The number of bytes needed to store a node.
94
167
  def BTreeNode::node_bytes(order)
95
168
  1 + # is_leaf
96
169
  2 + # actual key count
@@ -101,6 +174,16 @@ module PEROBS
101
174
  4 # CRC32 checksum
102
175
  end
103
176
 
177
+ # Save the node into the blob file.
178
+ def save
179
+ write_node
180
+ end
181
+
182
+ # The node address uniquely identifies a BTreeNode.
183
+ def uid
184
+ @node_address
185
+ end
186
+
104
187
  # Insert or replace the given value by using the key as unique address.
105
188
  # @param key [Integer] Unique key to retrieve the value
106
189
  # @param value [Integer] value to insert
@@ -186,14 +269,14 @@ module PEROBS
186
269
  def split_node
187
270
  unless @parent
188
271
  # The node is the root node. We need to create a parent node first.
189
- self.parent = @tree.new_node(nil, nil, false)
272
+ self.parent = BTreeNode::create(@tree, nil, false)
190
273
  @parent.set_child(0, self)
191
274
  @tree.set_root(@parent)
192
275
  end
193
276
 
194
277
  # Create the new sibling that will take the 2nd half of the
195
278
  # node content.
196
- sibling = @tree.new_node(@parent, nil, @is_leaf)
279
+ sibling = BTreeNode::create(@tree, @parent, @is_leaf)
197
280
  # Determine the index of the middle element that gets moved to the
198
281
  # parent. The order must be an uneven number, so adding 1 will get us
199
282
  # the middle element.
@@ -229,7 +312,6 @@ module PEROBS
229
312
  PEROBS.log.fatal "Cannot insert into a full BTreeNode"
230
313
  end
231
314
 
232
- mark_as_modified
233
315
  i = search_key_index(key)
234
316
  if @keys[i] == key
235
317
  # Overwrite existing entries
@@ -248,6 +330,7 @@ module PEROBS
248
330
  @children.insert(i + 1, BTreeNodeLink.new(@tree, value_or_child))
249
331
  end
250
332
  end
333
+ @tree.node_cache.insert(self)
251
334
  end
252
335
 
253
336
  # Remove the element at the given index.
@@ -256,7 +339,6 @@ module PEROBS
256
339
  first_key = @keys[0]
257
340
  removed_value = nil
258
341
 
259
- mark_as_modified
260
342
  # Delete the key at the specified index.
261
343
  unless @keys.delete_at(index)
262
344
  PEROBS.log.fatal "Could not remove element #{index} from BTreeNode " +
@@ -269,6 +351,7 @@ module PEROBS
269
351
  # The corresponding child has can be found at 1 index higher.
270
352
  @children.delete_at(index + 1)
271
353
  end
354
+ @tree.node_cache.insert(self)
272
355
 
273
356
  # Find the lower and upper siblings and the index of the key for this
274
357
  # node in the parent node.
@@ -301,7 +384,6 @@ module PEROBS
301
384
  end
302
385
 
303
386
  dest_node.keys[dst_idx, count] = @keys[src_idx, count]
304
- dest_node.dirty = true
305
387
  if @is_leaf
306
388
  # For leaves we copy the keys and corresponding values.
307
389
  dest_node.values[dst_idx, count] = @values[src_idx, count]
@@ -313,11 +395,12 @@ module PEROBS
313
395
  dest_node.set_child(dst_idx + i, @children[src_idx + i])
314
396
  end
315
397
  end
398
+ @tree.node_cache.insert(dest_node)
316
399
  end
317
400
 
318
401
  def parent=(p)
319
402
  @parent = p ? BTreeNodeLink.new(@tree, p) : nil
320
- mark_as_modified
403
+ @tree.node_cache.insert(self)
321
404
  end
322
405
 
323
406
  def set_child(index, child)
@@ -327,17 +410,17 @@ module PEROBS
327
410
  else
328
411
  @children[index] = nil
329
412
  end
330
- mark_as_modified
413
+ @tree.node_cache.insert(self)
331
414
  end
332
415
 
333
416
  def trim(idx)
334
- mark_as_modified
335
417
  @keys = @keys[0..idx - 1]
336
418
  if @is_leaf
337
419
  @values = @values[0..idx - 1]
338
420
  else
339
421
  @children = @children[0..idx]
340
422
  end
423
+ @tree.node_cache.insert(self)
341
424
  end
342
425
 
343
426
  # Search the keys of the node that fits the given key. The result is
@@ -598,8 +681,6 @@ module PEROBS
598
681
  end
599
682
 
600
683
  def write_node
601
- return unless @dirty
602
-
603
684
  ary = [
604
685
  @is_leaf ? 1 : 0,
605
686
  @keys.size,
@@ -620,66 +701,13 @@ module PEROBS
620
701
  ary += @children.map{ |c| c.node_address } +
621
702
  ::Array.new(@tree.order + 1 - @children.size, 0)
622
703
  end
623
- bytes = ary.pack(node_bytes_format)
704
+ bytes = ary.pack(BTreeNode::node_bytes_format(@tree))
624
705
  bytes += [ Zlib::crc32(bytes) ].pack('L')
625
706
  @tree.nodes.store_blob(@node_address, bytes)
626
- @dirty = false
627
- end
628
-
629
- def mark_as_modified
630
- @tree.mark_node_as_modified(self)
631
- @dirty = true
632
707
  end
633
708
 
634
709
  private
635
710
 
636
- def read_node
637
- return false unless (bytes = @tree.nodes.retrieve_blob(@node_address))
638
-
639
- unless Zlib::crc32(bytes) != 0
640
- PEROBS.log.error "Checksum failure in BTreeNode entry @#{node_address}"
641
- return false
642
- end
643
- ary = bytes.unpack(node_bytes_format)
644
- # Read is_leaf
645
- if ary[0] != 0 && ary[0] != 1
646
- PEROBS.log.error "First byte of a BTreeNode entry must be 0 or 1"
647
- return false
648
- end
649
- @is_leaf = ary[0] == 0 ? false : true
650
- # This is the number of keys this node has.
651
- key_count = ary[1]
652
- data_count = ary[2]
653
- # Read the parent node address
654
- @parent = ary[3] == 0 ? nil : BTreeNodeLink.new(@tree, ary[3])
655
- # Read the keys
656
- @keys = ary[4, key_count]
657
-
658
- if @is_leaf
659
- # Read the values
660
- @values = ary[4 + @tree.order, data_count]
661
- else
662
- # Read the child addresses
663
- @children = []
664
- data_count.times do |i|
665
- address = ary[4 + @tree.order + i]
666
- unless address > 0
667
- PEROBS.log.fatal "Child address must not be 0"
668
- end
669
- @children << BTreeNodeLink.new(@tree, address)
670
- end
671
- end
672
-
673
- @dirty = false
674
-
675
- true
676
- end
677
-
678
- def node_bytes_format
679
- # This does not include the 4 bytes for the CRC32 checksum
680
- "CSSQQ#{@tree.order}Q#{@tree.order + 1}"
681
- end
682
-
683
711
  def find_closest_siblings(key)
684
712
  # The root node has no siblings.
685
713
  return [ nil, nil, nil ] unless @parent