Skip to content

Commit

Permalink
Fix garbage collection bug (#555)
Browse files Browse the repository at this point in the history
Make it traverse the whole tree including tombstones
  • Loading branch information
JOOHOJANG authored Jun 29, 2023
1 parent e9aec8f commit 4bebe31
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/document/crdt/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ export class CRDTTree extends CRDTGCElement {
}
}

this.indexTree.traverse((treeNode) => {
this.indexTree.traverseAll((treeNode) => {
if (nodesToRemoved.has(treeNode)) {
const parent = treeNode.parent;

Expand Down
21 changes: 21 additions & 0 deletions src/util/index_tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,20 @@ export function traverse<T extends IndexTreeNode<T>>(
callback(node, depth);
}

/**
* `traverseAll` traverses the whole tree (include tombstones) with postorder traversal.
*/
function traverseAll<T extends IndexTreeNode<T>>(
node: T,
callback: (node: T, depth: number) => void,
depth = 0,
) {
for (const child of node._children) {
traverseAll(child, callback, depth + 1);
}
callback(node, depth);
}

/**
* `findTreePos` finds the position of the given index in the given node.
*/
Expand Down Expand Up @@ -675,6 +689,13 @@ export class IndexTree<T extends IndexTreeNode<T>> {
traverse(this.root, callback, 0);
}

/**
* `traverseAll` traverses the whole tree (include tombstones) with postorder traversal.
*/
traverseAll(callback: (node: T) => void): void {
traverseAll(this.root, callback, 0);
}

/**
* `split` splits the node at the given index.
*/
Expand Down
34 changes: 34 additions & 0 deletions test/integration/gc_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ import {
toDocKey,
} from '@yorkie-js-sdk/test/integration/integration_helper';
import { Text } from '@yorkie-js-sdk/src/yorkie';
import { CRDTTreeNode } from '@yorkie-js-sdk/src/document/crdt/tree';
import { IndexTreeNode } from '@yorkie-js-sdk/src/util/index_tree';

// `getNodeLength` returns the number of nodes in the given tree.
function getNodeLength(root: IndexTreeNode<CRDTTreeNode>) {
let size = 0;

size += root._children.length;

if (root._children.length) {
root._children.forEach((child) => {
size += getNodeLength(child);
});
}

return size;
}

describe('Garbage Collection', function () {
it('garbage collection test', function () {
Expand Down Expand Up @@ -204,19 +221,31 @@ describe('Garbage Collection', function () {
});

// [text(a), text(b)]
let nodeLengthBeforeGC = getNodeLength(
doc.getRoot().t.getIndexTree().getRoot(),
);
assert.equal(doc.getGarbageLen(), 2);
assert.equal(doc.garbageCollect(MaxTimeTicket), 2);
assert.equal(doc.getGarbageLen(), 0);
let nodeLengthAfterGC = getNodeLength(
doc.getRoot().t.getIndexTree().getRoot(),
);
assert.equal(nodeLengthBeforeGC - nodeLengthAfterGC, 1);

doc.update((root) => {
root.t.editByPath([0, 0, 0], [0, 0, 2], { type: 'text', value: 'cv' });
assert.equal(root.t.toXML(), `<doc><p><tn>cv</tn><tn>cd</tn></p></doc>`);
});

// [text(cd)]
nodeLengthBeforeGC = getNodeLength(
doc.getRoot().t.getIndexTree().getRoot(),
);
assert.equal(doc.getGarbageLen(), 1);
assert.equal(doc.garbageCollect(MaxTimeTicket), 1);
assert.equal(doc.getGarbageLen(), 0);
nodeLengthAfterGC = getNodeLength(doc.getRoot().t.getIndexTree().getRoot());
assert.equal(nodeLengthBeforeGC - nodeLengthAfterGC, 1);

doc.update((root) => {
root.t.editByPath([0], [1], {
Expand All @@ -227,9 +256,14 @@ describe('Garbage Collection', function () {
});

// [p, tn, tn, text(cv), text(cd)]
nodeLengthBeforeGC = getNodeLength(
doc.getRoot().t.getIndexTree().getRoot(),
);
assert.equal(doc.getGarbageLen(), 5);
assert.equal(doc.garbageCollect(MaxTimeTicket), 5);
assert.equal(doc.getGarbageLen(), 0);
nodeLengthAfterGC = getNodeLength(doc.getRoot().t.getIndexTree().getRoot());
assert.equal(nodeLengthBeforeGC - nodeLengthAfterGC, 6);
});

it('Can handle tree garbage collection for multi client', async function () {
Expand Down

0 comments on commit 4bebe31

Please sign in to comment.