Skip to content

Commit 1ef4f4a

Browse files
committed
Bump version to 1.6.2 while tweaking code, comments & readme
- Noticed that the regression test for `greedyClone(true)` does not fail when the bug is present. I fixed that. - Tweaked the fix to `mergeSibling` and rewrote the comment on it. - Updated the readme.
1 parent 6c0fda1 commit 1ef4f4a

File tree

6 files changed

+32
-23
lines changed

6 files changed

+32
-23
lines changed

b+tree.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,8 @@ export default class BTree<K = any, V = any> implements ISortedMapF<K, V>, ISort
309309
* This is known as copy-on-write behavior, or "lazy copying". */
310310
clone(): BTree<K, V>;
311311
/** Performs a greedy clone, immediately duplicating any nodes that are
312-
* not currently marked as shared, in order to avoid marking any nodes
313-
* as shared.
312+
* not currently marked as shared, in order to avoid marking any
313+
* additional nodes as shared.
314314
* @param force Clone all nodes, even shared ones.
315315
*/
316316
greedyClone(force?: boolean): BTree<K, V>;

b+tree.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -779,8 +779,8 @@ var BTree = /** @class */ (function () {
779779
return result;
780780
};
781781
/** Performs a greedy clone, immediately duplicating any nodes that are
782-
* not currently marked as shared, in order to avoid marking any nodes
783-
* as shared.
782+
* not currently marked as shared, in order to avoid marking any
783+
* additional nodes as shared.
784784
* @param force Clone all nodes, even shared ones.
785785
*/
786786
BTree.prototype.greedyClone = function (force) {
@@ -1392,7 +1392,7 @@ var BNodeInternal = /** @class */ (function (_super) {
13921392
__extends(BNodeInternal, _super);
13931393
/**
13941394
* This does not mark `children` as shared, so it is the responsibility of the caller
1395-
* to ensure that either children are marked shared, or it are not included in another tree.
1395+
* to ensure children are either marked shared, or aren't included in another tree.
13961396
*/
13971397
function BNodeInternal(children, keys) {
13981398
var _this = this;
@@ -1635,10 +1635,9 @@ var BNodeInternal = /** @class */ (function (_super) {
16351635
this.keys.push.apply(this.keys, rhs.keys);
16361636
var rhsChildren = rhs.children;
16371637
this.children.push.apply(this.children, rhsChildren);
1638-
if (rhs.isShared) {
1639-
// Because rhs might continue to be used in another tree since it is shared,
1640-
// this is adding a parent to its children instead of just changing what their parent is.
1641-
// Thus they need to be marked as shared.
1638+
if (rhs.isShared && !this.isShared) {
1639+
// All children of a shared node are implicitly shared, and since their new
1640+
// parent is not shared, they must now be explicitly marked as shared.
16421641
for (var i = 0; i < rhsChildren.length; i++)
16431642
rhsChildren[i].isShared = true;
16441643
}

b+tree.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,10 @@ describe("cloning and sharing tests", () => {
430430
}
431431
// Leaf nodes don't count, so this is depth 2
432432
expect(tree.height).toBe(2);
433+
434+
// To trigger the bug, mark children of the root node as shared (not just the root)
435+
tree.clone().set(1, 1);
436+
433437
const clone = tree.greedyClone(true);
434438

435439
// The bug was that `force` was not passed down. This meant that non-shared nodes below the second layer would not be cloned.

b+tree.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -875,8 +875,8 @@ export default class BTree<K=any, V=any> implements ISortedMapF<K,V>, ISortedMap
875875
}
876876

877877
/** Performs a greedy clone, immediately duplicating any nodes that are
878-
* not currently marked as shared, in order to avoid marking any nodes
879-
* as shared.
878+
* not currently marked as shared, in order to avoid marking any
879+
* additional nodes as shared.
880880
* @param force Clone all nodes, even shared ones.
881881
*/
882882
greedyClone(force?: boolean): BTree<K,V> {
@@ -1206,8 +1206,10 @@ class BNode<K,V> {
12061206
// If this is an internal node, _keys[i] is the highest key in children[i].
12071207
keys: K[];
12081208
values: V[];
1209-
// True if this node might multiple parents (equivalently: could be in multiple b-trees).
1210-
// This means it must be cloned before being mutated to avoid changing an unrelated tree.
1209+
// True if this node might be within multiple `BTree`s (or have multiple parents).
1210+
// If so, it must be cloned before being mutated to avoid changing an unrelated tree.
1211+
// This is transitive: if it's true, children are also shared even if `isShared!=true`
1212+
// in those children. (Certain operations will propagate isShared=true to children.)
12111213
isShared: true | undefined;
12121214
get isLeaf() { return (this as any).children === undefined; }
12131215

@@ -1532,7 +1534,7 @@ class BNodeInternal<K,V> extends BNode<K,V> {
15321534

15331535
/**
15341536
* This does not mark `children` as shared, so it is the responsibility of the caller
1535-
* to ensure that either children are marked shared, or it are not included in another tree.
1537+
* to ensure children are either marked shared, or aren't included in another tree.
15361538
*/
15371539
constructor(children: BNode<K,V>[], keys?: K[]) {
15381540
if (!keys) {
@@ -1793,10 +1795,9 @@ class BNodeInternal<K,V> extends BNode<K,V> {
17931795
const rhsChildren = (rhs as any as BNodeInternal<K,V>).children;
17941796
this.children.push.apply(this.children, rhsChildren);
17951797

1796-
if (rhs.isShared) {
1797-
// Because rhs might continue to be used in another tree since it is shared,
1798-
// this is adding a parent to its children instead of just changing what their parent is.
1799-
// Thus they need to be marked as shared.
1798+
if (rhs.isShared && !this.isShared) {
1799+
// All children of a shared node are implicitly shared, and since their new
1800+
// parent is not shared, they must now be explicitly marked as shared.
18001801
for (var i = 0; i < rhsChildren.length; i++)
18011802
rhsChildren[i].isShared = true;
18021803
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sorted-btree",
3-
"version": "1.6.1",
3+
"version": "1.6.2",
44
"description": "A sorted list of key-value pairs in a fast, typed in-memory B+ tree with a powerful API.",
55
"main": "b+tree.js",
66
"typings": "b+tree",

readme.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ Features
2323
constructor argument).
2424
- Supports O(1) fast cloning with subtree sharing. This works by marking the
2525
root node as "shared between instances". This makes the tree read-only
26-
with copy-on-edit behavior; both copies of the tree remain mutable.
27-
I call this category of data structure "dynamically persistent" because
28-
AFAIK no one else has given it a name; it walks the line between mutating
29-
and [persistent](https://en.wikipedia.org/wiki/Persistent_data_structure).
26+
with copy-on-edit behavior; both copies of the tree remain mutable. I call
27+
this category of data structure "dynamically persistent" or "mutably
28+
persistent" because AFAIK no one else has given it a name; it walks the line
29+
between mutating and [persistent](https://en.wikipedia.org/wiki/Persistent_data_structure).
3030
- Includes persistent methods such as `with` and `without`, which return a
3131
modified tree without changing the original (in O(log(size)) time).
3232
- When a node fills up, items are shifted to siblings when possible to
@@ -375,6 +375,11 @@ Benchmarks (in milliseconds for integer keys/values)
375375
Version history
376376
---------------
377377

378+
### v1.6.2 ###
379+
380+
- Bug fixes: two rare situations were discovered in which shared nodes could fail to be marked as shared, and as a result, mutations could affect copies that should have been completely separate.
381+
- Bug fix: greedyClone(true) did not clone shared nodes recursively.
382+
378383
### v1.6.0 ###
379384

380385
- Added `BTree.getPairOrNextLower` and `BTree.getPairOrNextHigher` methods (PR #23)

0 commit comments

Comments
 (0)