diff --git a/src/document/crdt/root.ts b/src/document/crdt/root.ts index beecb02f3..9b6e760a4 100644 --- a/src/document/crdt/root.ts +++ b/src/document/crdt/root.ts @@ -233,18 +233,21 @@ export class CRDTRoot { */ public getGarbageLen(): number { let count = 0; + const seen = new Set(); for (const createdAt of this.removedElementSetByCreatedAt) { - count++; + seen.add(createdAt); const pair = this.elementPairMapByCreatedAt.get(createdAt)!; if (pair.element instanceof CRDTContainer) { - pair.element.getDescendants(() => { - count++; + pair.element.getDescendants((el) => { + seen.add(el.getCreatedAt().toIDString()); return false; }); } } + count += seen.size; + for (const createdAt of this.elementHasRemovedNodesSetByCreatedAt) { const pair = this.elementPairMapByCreatedAt.get(createdAt)!; const elem = pair.element as CRDTGCElement; diff --git a/test/integration/gc_test.ts b/test/integration/gc_test.ts index 48b17052a..7f552dede 100644 --- a/test/integration/gc_test.ts +++ b/test/integration/gc_test.ts @@ -114,6 +114,52 @@ describe('Garbage Collection', function () { assert.equal(root, clone); }); + it('getGarbageLen should return the actual number of elements garbage-collected', async function ({ task }) { + type TestDoc = { point?: { x?: number; y?: number } }; + const docKey = toDocKey(`${task.name}-${new Date().getTime()}`); + const doc1 = new yorkie.Document(docKey); + const doc2 = new yorkie.Document(docKey); + + const client1 = new yorkie.Client(testRPCAddr); + const client2 = new yorkie.Client(testRPCAddr); + + await client1.activate(); + await client2.activate(); + + // 1. initial state + await client1.attach(doc1, { isRealtimeSync: false }); + doc1.update((root) => (root.point = { x: 0, y: 0 })); + await client1.sync(); + await client2.attach(doc2, { isRealtimeSync: false }); + + // 2. client1 updates doc + doc1.update((root) => { + delete root.point; + }); + assert.equal(doc1.getGarbageLen(), 3); // point, x, y + + // 3. client2 updates doc + doc2.update((root) => { + delete root.point?.x; + }); + assert.equal(doc2.getGarbageLen(), 1); // x + + await client1.sync(); + await client2.sync(); + await client1.sync(); + + const gcNodeLen = 3; // point, x, y + assert.equal(doc1.getGarbageLen(), gcNodeLen); + assert.equal(doc2.getGarbageLen(), gcNodeLen); + + // Actual garbage-collected nodes + assert.equal(doc1.garbageCollect(MaxTimeTicket), gcNodeLen); + assert.equal(doc2.garbageCollect(MaxTimeTicket), gcNodeLen); + + await client1.deactivate(); + await client2.deactivate(); + }); + it('text garbage collection test', function () { const doc = new yorkie.Document<{ text: Text }>('test-doc'); doc.update((root) => (root.text = new Text()));