perobs 4.2.0 → 4.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +27 -16
- data/lib/perobs/BTree.rb +2 -2
- data/lib/perobs/BTreeNode.rb +46 -29
- data/lib/perobs/BigArrayNode.rb +11 -9
- data/lib/perobs/Cache.rb +32 -6
- data/lib/perobs/EquiBlobsFile.rb +2 -0
- data/lib/perobs/FlatFile.rb +40 -60
- data/lib/perobs/FuzzyStringMatcher.rb +32 -49
- data/lib/perobs/Hash.rb +68 -23
- data/lib/perobs/IDListPageFile.rb +2 -1
- data/lib/perobs/IDListPageRecord.rb +1 -1
- data/lib/perobs/Log.rb +5 -0
- data/lib/perobs/ObjectBase.rb +7 -0
- data/lib/perobs/SpaceTree.rb +1 -1
- data/lib/perobs/Store.rb +177 -125
- data/lib/perobs/version.rb +1 -1
- data/lib/perobs.rb +1 -0
- data/perobs.gemspec +1 -1
- data/test/FlatFileDB_spec.rb +30 -0
- data/test/FuzzyStringMatcher_spec.rb +94 -4
- data/test/Hash_spec.rb +12 -1
- data/test/Store_spec.rb +14 -0
- metadata +8 -10
- data/lib/perobs/BTreeNodeCache.rb +0 -109
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90c77003da7bd628fa753bfe35918d8c702b433ac5113ba9fc206f7afa114f6c
|
4
|
+
data.tar.gz: c38f786cb10a040048b7d08b75c16baf034c116a3f9648ece3af7b50e7898446
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1fea934c359190c3b171c99e4dbffb3af58ec2f9dd755eedc0acb7db556f0e2471bd3c527a2910e5d8f17cd59b3f1ca4079436bc80bd11ea5d6efa60f55a54e9
|
7
|
+
data.tar.gz: db71320a9672848a3e35088e7a8b3389278c987a1bea905336e3f8bfff6daeb27564724b6f9d681d364684a57ce8593021041626615dace155e1849b0944003b
|
data/README.md
CHANGED
@@ -6,11 +6,12 @@ them from PEROBS::Object. They will be in memory when needed and
|
|
6
6
|
transparently stored into a persistent storage.
|
7
7
|
|
8
8
|
This library is ideal for Ruby applications that work on huge, mostly
|
9
|
-
|
9
|
+
static data sets and usually process a small subset of the data at a
|
10
10
|
time. To ensure data consistency of a larger data set, you can use
|
11
11
|
transactions to make modifications of multiple objects atomicaly.
|
12
12
|
Transactions can be nested and are aborted when an exception is
|
13
|
-
raised.
|
13
|
+
raised. PEROBS is thread-safe, so you can use it in a multi-threaded
|
14
|
+
application.
|
14
15
|
|
15
16
|
## Usage
|
16
17
|
|
@@ -108,7 +109,7 @@ class Person < PEROBS::Object
|
|
108
109
|
attr_init(:father) do { @store.new(Person, 'Dad') }
|
109
110
|
end
|
110
111
|
|
111
|
-
def
|
112
|
+
def marry(spouse)
|
112
113
|
self.spouse = spouse
|
113
114
|
self.status = :married
|
114
115
|
end
|
@@ -120,15 +121,18 @@ class Person < PEROBS::Object
|
|
120
121
|
|
121
122
|
end
|
122
123
|
|
123
|
-
|
124
|
-
store
|
125
|
-
store['
|
126
|
-
|
127
|
-
jim
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
124
|
+
begin
|
125
|
+
store = PEROBS::Store.new('family')
|
126
|
+
store['grandpa'] = joe = store.new(Person, 'Joe')
|
127
|
+
store['grandma'] = jane = store.new(Person, 'Jane')
|
128
|
+
jim = store.new(Person, 'Jim')
|
129
|
+
jim.father = joe
|
130
|
+
joe.kids << jim
|
131
|
+
jim.mother = jane
|
132
|
+
jane.kids << jim
|
133
|
+
ensure
|
134
|
+
store.exit
|
135
|
+
end
|
132
136
|
```
|
133
137
|
|
134
138
|
When you run this script, a folder named 'family' will be created. It
|
@@ -166,9 +170,15 @@ object to another object.
|
|
166
170
|
|
167
171
|
### Caveats and known issues
|
168
172
|
|
169
|
-
|
170
|
-
|
171
|
-
|
173
|
+
You cannot simultaneously access the database from multiple
|
174
|
+
applications concurrently. The library uses locks to ensure that only
|
175
|
+
one Store object is accessing the database at a time.
|
176
|
+
|
177
|
+
In case the application terminates without calling Store::exit(), the
|
178
|
+
database or the database index could get corrupted. To check the
|
179
|
+
consistency of your database you can use Store::check(). To check and
|
180
|
+
repair the database you can call Store::repair(). Depending on the
|
181
|
+
size of your database, these operations can last minutes to hours.
|
172
182
|
|
173
183
|
## Installation
|
174
184
|
|
@@ -188,7 +198,8 @@ Or install it yourself as:
|
|
188
198
|
|
189
199
|
## Copyright and License
|
190
200
|
|
191
|
-
Copyright (c) 2015, 2016, 2017
|
201
|
+
Copyright (c) 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 by
|
202
|
+
Chris Schlaeger <chris@taskjuggler.org>
|
192
203
|
|
193
204
|
PEROBS and all accompanying files are licensed under this MIT License
|
194
205
|
|
data/lib/perobs/BTree.rb
CHANGED
@@ -70,7 +70,7 @@ module PEROBS
|
|
70
70
|
@nodes.register_custom_data('first_leaf')
|
71
71
|
@nodes.register_custom_data('last_leaf')
|
72
72
|
@nodes.register_custom_data('btree_size')
|
73
|
-
@node_cache = PersistentObjectCache.new(2**
|
73
|
+
@node_cache = PersistentObjectCache.new(2**13, 2**13, BTreeNode, self)
|
74
74
|
@root = @first_leaf = @last_leaf = nil
|
75
75
|
@size = 0
|
76
76
|
|
@@ -190,7 +190,7 @@ module PEROBS
|
|
190
190
|
"Number of leave nodes: #{stats.leave_nodes}; " +
|
191
191
|
"Number of leaves: #{stats.leaves}"
|
192
192
|
|
193
|
-
|
193
|
+
true
|
194
194
|
end
|
195
195
|
|
196
196
|
# Register a new node as root node of the tree.
|
data/lib/perobs/BTreeNode.rb
CHANGED
@@ -59,7 +59,7 @@ module PEROBS
|
|
59
59
|
# if not
|
60
60
|
def initialize(tree, node_address = nil, parent = nil, is_leaf = true,
|
61
61
|
prev_sibling = nil, next_sibling = nil,
|
62
|
-
keys =
|
62
|
+
keys = nil, values = nil, children = nil)
|
63
63
|
@tree = tree
|
64
64
|
if node_address == 0
|
65
65
|
PEROBS.log.fatal "Node address may not be 0"
|
@@ -68,17 +68,17 @@ module PEROBS
|
|
68
68
|
@parent = link(parent)
|
69
69
|
@prev_sibling = link(prev_sibling)
|
70
70
|
@next_sibling = link(next_sibling)
|
71
|
-
@keys = keys
|
71
|
+
@keys = keys || []
|
72
72
|
if (@is_leaf = is_leaf)
|
73
|
-
@values = values
|
74
|
-
@children =
|
73
|
+
@values = values || []
|
74
|
+
@children = nil
|
75
75
|
else
|
76
|
-
@children = children
|
77
|
-
@values =
|
76
|
+
@children = children || []
|
77
|
+
@values = nil
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
-
# Create a new
|
81
|
+
# Create a new BTreeNode. This method should be used for the creation
|
82
82
|
# of new nodes instead of calling the constructor directly.
|
83
83
|
# @param tree [BTree] The tree the new node should belong to
|
84
84
|
# @param parent [BTreeNode] The parent node
|
@@ -130,7 +130,8 @@ module PEROBS
|
|
130
130
|
ary = bytes.unpack(BTreeNode::node_bytes_format(tree))
|
131
131
|
# Read is_leaf
|
132
132
|
if ary[0] != 0 && ary[0] != 1
|
133
|
-
PEROBS.log.fatal "First byte of a BTreeNode entry
|
133
|
+
PEROBS.log.fatal "First byte of a BTreeNode entry at address " +
|
134
|
+
"#{address} must be 0 or 1 but is #{ary[0]}"
|
134
135
|
end
|
135
136
|
is_leaf = ary[0] == 0 ? false : true
|
136
137
|
# This is the number of keys this node has.
|
@@ -350,6 +351,7 @@ module PEROBS
|
|
350
351
|
unless @parent
|
351
352
|
# The node is the root node. We need to create a parent node first.
|
352
353
|
self.parent = link(BTreeNode::create(@tree, nil, false))
|
354
|
+
@tree.node_cache.insert(self)
|
353
355
|
@parent.set_child(0, self)
|
354
356
|
@tree.set_root(@parent)
|
355
357
|
end
|
@@ -382,6 +384,7 @@ module PEROBS
|
|
382
384
|
end
|
383
385
|
|
384
386
|
i = search_key_index(key)
|
387
|
+
@tree.node_cache.insert(self)
|
385
388
|
if @keys[i] == key
|
386
389
|
# Overwrite existing entries
|
387
390
|
@keys[i] = key
|
@@ -390,7 +393,6 @@ module PEROBS
|
|
390
393
|
else
|
391
394
|
@children[i + 1] = link(value_or_child)
|
392
395
|
end
|
393
|
-
@tree.node_cache.insert(self)
|
394
396
|
|
395
397
|
return false
|
396
398
|
else
|
@@ -401,7 +403,6 @@ module PEROBS
|
|
401
403
|
else
|
402
404
|
@children.insert(i + 1, link(value_or_child))
|
403
405
|
end
|
404
|
-
@tree.node_cache.insert(self)
|
405
406
|
|
406
407
|
return true
|
407
408
|
end
|
@@ -417,8 +418,8 @@ module PEROBS
|
|
417
418
|
update_branch_key(key) if index == 0
|
418
419
|
|
419
420
|
# Delete the corresponding value.
|
420
|
-
removed_value = @values.delete_at(index)
|
421
421
|
@tree.node_cache.insert(self)
|
422
|
+
removed_value = @values.delete_at(index)
|
422
423
|
|
423
424
|
if @keys.length < min_keys
|
424
425
|
if @prev_sibling && @prev_sibling.parent == @parent
|
@@ -481,9 +482,9 @@ module PEROBS
|
|
481
482
|
PEROBS.log.fatal "Leaf nodes are too big to merge"
|
482
483
|
end
|
483
484
|
|
485
|
+
@tree.node_cache.insert(self)
|
484
486
|
@keys += node.keys
|
485
487
|
@values += node.values
|
486
|
-
@tree.node_cache.insert(self)
|
487
488
|
|
488
489
|
node.parent.remove_child(node)
|
489
490
|
end
|
@@ -494,11 +495,11 @@ module PEROBS
|
|
494
495
|
end
|
495
496
|
|
496
497
|
index = @parent.search_node_index(node) - 1
|
498
|
+
@tree.node_cache.insert(self)
|
497
499
|
@keys << @parent.keys[index]
|
498
500
|
@keys += node.keys
|
499
501
|
node.children.each { |c| c.parent = link(self) }
|
500
502
|
@children += node.children
|
501
|
-
@tree.node_cache.insert(self)
|
502
503
|
|
503
504
|
node.parent.remove_child(node)
|
504
505
|
end
|
@@ -525,6 +526,7 @@ module PEROBS
|
|
525
526
|
"#{dest_node.is_leaf} node must be of same kind"
|
526
527
|
end
|
527
528
|
|
529
|
+
@tree.node_cache.insert(dest_node)
|
528
530
|
dest_node.keys[dst_idx, count] = @keys[src_idx, count]
|
529
531
|
if @is_leaf
|
530
532
|
# For leaves we copy the keys and corresponding values.
|
@@ -537,17 +539,17 @@ module PEROBS
|
|
537
539
|
dest_node.set_child(dst_idx + i, @children[src_idx + i])
|
538
540
|
end
|
539
541
|
end
|
540
|
-
@tree.node_cache.insert(dest_node)
|
541
542
|
end
|
542
543
|
|
543
544
|
def parent=(p)
|
544
|
-
@parent = p
|
545
545
|
@tree.node_cache.insert(self)
|
546
|
+
@parent = p
|
546
547
|
|
547
548
|
p
|
548
549
|
end
|
549
550
|
|
550
551
|
def prev_sibling=(node)
|
552
|
+
@tree.node_cache.insert(self)
|
551
553
|
@prev_sibling = node
|
552
554
|
if node.nil? && @is_leaf
|
553
555
|
# If this node is a leaf node without a previous sibling we need to
|
@@ -555,14 +557,12 @@ module PEROBS
|
|
555
557
|
@tree.set_first_leaf(BTreeNodeLink.new(@tree, self))
|
556
558
|
end
|
557
559
|
|
558
|
-
@tree.node_cache.insert(self)
|
559
|
-
|
560
560
|
node
|
561
561
|
end
|
562
562
|
|
563
563
|
def next_sibling=(node)
|
564
|
-
@next_sibling = node
|
565
564
|
@tree.node_cache.insert(self)
|
565
|
+
@next_sibling = node
|
566
566
|
if node.nil? && @is_leaf
|
567
567
|
# If this node is a leaf node without a next sibling we need to
|
568
568
|
# register it as the last leaf node.
|
@@ -573,25 +573,25 @@ module PEROBS
|
|
573
573
|
end
|
574
574
|
|
575
575
|
def set_child(index, child)
|
576
|
+
@tree.node_cache.insert(self)
|
576
577
|
if child
|
577
578
|
@children[index] = link(child)
|
578
579
|
@children[index].parent = link(self)
|
579
580
|
else
|
580
581
|
@children[index] = nil
|
581
582
|
end
|
582
|
-
@tree.node_cache.insert(self)
|
583
583
|
|
584
584
|
child
|
585
585
|
end
|
586
586
|
|
587
587
|
def trim(idx)
|
588
|
-
@
|
588
|
+
@tree.node_cache.insert(self)
|
589
|
+
@keys.slice!(idx, @keys.length - idx)
|
589
590
|
if @is_leaf
|
590
|
-
@values
|
591
|
+
@values.slice!(idx, @values.length - idx)
|
591
592
|
else
|
592
|
-
@children
|
593
|
+
@children.slice!(idx + 1, @children.length - idx - 1)
|
593
594
|
end
|
594
|
-
@tree.node_cache.insert(self)
|
595
595
|
end
|
596
596
|
|
597
597
|
# Search the keys of the node that fits the given key. The result is
|
@@ -654,13 +654,18 @@ module PEROBS
|
|
654
654
|
# @yield [key, value]
|
655
655
|
# @return [nil or Hash] nil in case of errors or a hash with some
|
656
656
|
# statistical information about the tree
|
657
|
-
def check
|
657
|
+
def check(&block)
|
658
658
|
stats = Stats.new(nil, 0, 0, 0)
|
659
659
|
|
660
660
|
traverse do |node, position, stack|
|
661
661
|
if position == 0
|
662
662
|
stats.nodes_count += 1
|
663
663
|
if node.parent
|
664
|
+
unless node.parent.is_a?(BTreeNodeLink)
|
665
|
+
node.error "parent is a #{node.parent.class} instead of a " +
|
666
|
+
"BTreeNodeLink"
|
667
|
+
return nil
|
668
|
+
end
|
664
669
|
# After a split the nodes will only have half the maximum keys.
|
665
670
|
# For branch nodes one of the split nodes will have even 1 key
|
666
671
|
# less as this will become the branch key in a parent node.
|
@@ -695,6 +700,16 @@ module PEROBS
|
|
695
700
|
else
|
696
701
|
stats.branch_depth = node.tree_level
|
697
702
|
end
|
703
|
+
if node.prev_sibling && !node.prev_sibling.is_a?(BTreeNodeLink)
|
704
|
+
node.error "prev_sibling is a #{node.prev_sibling.class} " +
|
705
|
+
"instead of a BTreeNodeLink"
|
706
|
+
return nil
|
707
|
+
end
|
708
|
+
if node.next_sibling && !node.next_sibling.is_a?(BTreeNodeLink)
|
709
|
+
node.error "next_sibling is a #{node.next_sibling.class} " +
|
710
|
+
"instead of a BTreeNodeLink"
|
711
|
+
return nil
|
712
|
+
end
|
698
713
|
if node.prev_sibling.nil? && @tree.first_leaf != node
|
699
714
|
node.error "Leaf node #{node.node_address} has no previous " +
|
700
715
|
"sibling but is not the first leaf of the tree"
|
@@ -708,9 +723,9 @@ module PEROBS
|
|
708
723
|
unless node.keys.size == node.values.size
|
709
724
|
node.error "Key count (#{node.keys.size}) and value " +
|
710
725
|
"count (#{node.values.size}) don't match"
|
711
|
-
|
726
|
+
return nil
|
712
727
|
end
|
713
|
-
unless node.children.
|
728
|
+
unless node.children.nil?
|
714
729
|
node.error "@children must be nil for a leaf node"
|
715
730
|
return nil
|
716
731
|
end
|
@@ -718,14 +733,14 @@ module PEROBS
|
|
718
733
|
stats.leave_nodes += 1
|
719
734
|
stats.leaves += node.keys.length
|
720
735
|
else
|
721
|
-
unless node.values.
|
736
|
+
unless node.values.nil?
|
722
737
|
node.error "@values must be nil for a branch node"
|
723
738
|
return nil
|
724
739
|
end
|
725
740
|
unless node.children.size == node.keys.size + 1
|
726
741
|
node.error "Key count (#{node.keys.size}) must be one " +
|
727
742
|
"less than children count (#{node.children.size})"
|
728
|
-
|
743
|
+
return nil
|
729
744
|
end
|
730
745
|
node.children.each_with_index do |child, i|
|
731
746
|
unless child.is_a?(BTreeNodeLink)
|
@@ -789,7 +804,9 @@ module PEROBS
|
|
789
804
|
else
|
790
805
|
if block_given?
|
791
806
|
# If a block was given, call this block with the key and value.
|
792
|
-
|
807
|
+
unless yield(node.keys[index], node.values[index])
|
808
|
+
return nil
|
809
|
+
end
|
793
810
|
end
|
794
811
|
end
|
795
812
|
end
|
data/lib/perobs/BigArrayNode.rb
CHANGED
@@ -135,27 +135,29 @@ module PEROBS
|
|
135
135
|
node = self
|
136
136
|
|
137
137
|
# Traverse the tree to find the right node to add or replace the value.
|
138
|
+
idx = index
|
138
139
|
while node do
|
139
140
|
# Once we have reached a leaf node we can insert or replace the value.
|
140
141
|
if node.is_leaf?
|
141
|
-
if
|
142
|
+
if idx >= node.values.size
|
142
143
|
node.fatal "Set index (#{index}) larger than values array " +
|
143
|
-
"(#{node.values.size})."
|
144
|
+
"(#{idx} >= #{node.values.size})."
|
144
145
|
end
|
145
|
-
node.values[
|
146
|
+
node.values[idx] = value
|
146
147
|
return
|
147
148
|
else
|
148
149
|
# Descend into the right child node to add the value to.
|
149
|
-
cidx = node.search_child_index(
|
150
|
-
if (
|
151
|
-
node.fatal "
|
150
|
+
cidx = node.search_child_index(idx)
|
151
|
+
if (idx -= node.offsets[cidx]) < 0
|
152
|
+
node.fatal "Idx (#{idx}) became negative while looking for " +
|
153
|
+
"index #{index}."
|
152
154
|
end
|
153
155
|
node = node.children[cidx]
|
154
156
|
end
|
155
157
|
end
|
156
158
|
|
157
159
|
node.fatal "Could not find proper node to set the value while " +
|
158
|
-
"looking for index #{index}"
|
160
|
+
"looking for index #{index}."
|
159
161
|
end
|
160
162
|
|
161
163
|
# Insert the given value at the given index. All following values will be
|
@@ -610,7 +612,7 @@ module PEROBS
|
|
610
612
|
end
|
611
613
|
end
|
612
614
|
|
613
|
-
# @param
|
615
|
+
# @param offset [Integer] offset to search the child index for
|
614
616
|
# @return [Integer] Index of the matching offset or the insert position.
|
615
617
|
def search_child_index(offset)
|
616
618
|
# Handle special case for empty offsets list.
|
@@ -811,7 +813,7 @@ module PEROBS
|
|
811
813
|
|
812
814
|
# Print and log an error message for the node.
|
813
815
|
def fatal(msg)
|
814
|
-
msg = "Fatal error in BigArray node @#{@_id}: #{msg}\n"
|
816
|
+
msg = "Fatal error in BigArray node @#{@_id}: #{msg}\n"
|
815
817
|
$stderr.puts msg
|
816
818
|
PEROBS.log.fatal msg
|
817
819
|
end
|
data/lib/perobs/Cache.rb
CHANGED
@@ -122,12 +122,25 @@ module PEROBS
|
|
122
122
|
# @param id [Integer] ID of the cached PEROBS::ObjectBase
|
123
123
|
def object_by_id(id)
|
124
124
|
idx = id & @mask
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
#
|
129
|
-
|
130
|
-
|
125
|
+
|
126
|
+
if @transaction_stack.empty?
|
127
|
+
# The index is just a hash. We still need to check if the object IDs are
|
128
|
+
# actually the same before we can return the object.
|
129
|
+
if (obj = @writes[idx]) && obj._id == id
|
130
|
+
# The object was in the write cache.
|
131
|
+
return obj
|
132
|
+
end
|
133
|
+
else
|
134
|
+
# During transactions, the read cache is used to provide fast access
|
135
|
+
# to modified objects. But it does not store all modified objects
|
136
|
+
# since there can be hash collisions. So we also have to check all
|
137
|
+
# transaction objects first.
|
138
|
+
if (obj = @transaction_objects[id])
|
139
|
+
return obj
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
if (obj = @reads[idx]) && obj._id == id
|
131
144
|
# The object was in the read cache.
|
132
145
|
return obj
|
133
146
|
end
|
@@ -152,10 +165,20 @@ module PEROBS
|
|
152
165
|
# active, the write cache is flushed before the transaction is started.
|
153
166
|
def begin_transaction
|
154
167
|
if @transaction_stack.empty?
|
168
|
+
if @transaction_thread
|
169
|
+
PEROBS.log.fatal 'transaction_thread must be nil'
|
170
|
+
end
|
171
|
+
@transaction_thread = Thread.current
|
155
172
|
# The new transaction is the top-level transaction. Flush the write
|
156
173
|
# buffer to save the current state of all objects.
|
157
174
|
flush
|
158
175
|
else
|
176
|
+
# Nested transactions are currently only supported within the same
|
177
|
+
# thread. If we are in another thread, raise TransactionInOtherThread
|
178
|
+
# to pause the calling thread for a bit.
|
179
|
+
if @transaction_thread != Thread.current
|
180
|
+
raise TransactionInOtherThread
|
181
|
+
end
|
159
182
|
# Save a copy of all objects that were modified during the enclosing
|
160
183
|
# transaction.
|
161
184
|
@transaction_stack.last.each do |id|
|
@@ -179,6 +202,7 @@ module PEROBS
|
|
179
202
|
# into the backend storage.
|
180
203
|
@transaction_stack.pop.each { |id| @transaction_objects[id]._sync }
|
181
204
|
@transaction_objects = ::Hash.new
|
205
|
+
@transaction_thread = nil
|
182
206
|
else
|
183
207
|
# A nested transaction completed successfully. We add the list of
|
184
208
|
# modified objects to the list of the enclosing transaction.
|
@@ -200,6 +224,7 @@ module PEROBS
|
|
200
224
|
@transaction_stack.pop.each do |id|
|
201
225
|
@transaction_objects[id]._restore(@transaction_stack.length)
|
202
226
|
end
|
227
|
+
@transaction_thread = nil
|
203
228
|
end
|
204
229
|
|
205
230
|
# Clear all cached entries. You must call flush before calling this
|
@@ -211,6 +236,7 @@ module PEROBS
|
|
211
236
|
@reads = ::Array.new(2 ** @bits)
|
212
237
|
@writes = ::Array.new(2 ** @bits)
|
213
238
|
@transaction_stack = ::Array.new
|
239
|
+
@transaction_thread = nil
|
214
240
|
@transaction_objects = ::Hash.new
|
215
241
|
end
|
216
242
|
|