diff --git a/pkg/document/crdt/root.go b/pkg/document/crdt/root.go
index 73db7de88..9f338b804 100644
--- a/pkg/document/crdt/root.go
+++ b/pkg/document/crdt/root.go
@@ -157,7 +157,7 @@ func (r *Root) GarbageCollect(ticket *time.Ticket) (int, error) {
return 0, err
}
- if purgedNodes > 0 {
+ if node.removedNodesLen() == 0 {
delete(r.elementHasRemovedNodesSetByCreatedAt, node.CreatedAt().Key())
}
count += purgedNodes
diff --git a/test/integration/gc_test.go b/test/integration/gc_test.go
index 0c972d5bf..9f5bae462 100644
--- a/test/integration/gc_test.go
+++ b/test/integration/gc_test.go
@@ -452,4 +452,154 @@ func TestGarbageCollection(t *testing.T) {
assert.Equal(t, 5, d1.GarbageCollect(time.MaxTicket))
})
+ t.Run("Should work properly when there are multiple nodes to be collected in text type", func(t *testing.T) {
+ ctx := context.Background()
+ d1 := document.New(helper.TestDocKey(t))
+ err := c1.Attach(ctx, d1)
+ assert.NoError(t, err)
+
+ d2 := document.New(helper.TestDocKey(t))
+ err = c2.Attach(ctx, d2)
+ assert.NoError(t, err)
+
+ err = d1.Update(func(root *json.Object, p *presence.Presence) error {
+ root.SetNewText("text").Edit(0, 0, "z")
+ return nil
+ })
+ assert.NoError(t, err)
+ err = d1.Update(func(root *json.Object, p *presence.Presence) error {
+ root.GetText("text").Edit(0, 1, "a")
+ return nil
+ })
+ assert.NoError(t, err)
+ err = d1.Update(func(root *json.Object, p *presence.Presence) error {
+ root.GetText("text").Edit(1, 1, "b")
+ return nil
+ })
+ assert.NoError(t, err)
+ err = d1.Update(func(root *json.Object, p *presence.Presence) error {
+ root.GetText("text").Edit(2, 2, "d")
+ return nil
+ })
+ assert.NoError(t, err)
+ err = c1.Sync(ctx)
+ assert.NoError(t, err)
+ err = c2.Sync(ctx)
+ assert.NoError(t, err)
+
+ assert.Equal(t, `{"text":[{"val":"a"},{"val":"b"},{"val":"d"}]}`, d1.Marshal())
+ assert.Equal(t, `{"text":[{"val":"a"},{"val":"b"},{"val":"d"}]}`, d2.Marshal())
+ assert.Equal(t, 1, d1.GarbageLen()) // z
+
+ err = d1.Update(func(root *json.Object, p *presence.Presence) error {
+ root.GetText("text").Edit(2, 2, "c")
+ return nil
+ })
+ assert.NoError(t, err)
+ err = c1.Sync(ctx)
+ assert.NoError(t, err)
+ err = c2.Sync(ctx)
+ assert.NoError(t, err)
+ err = c2.Sync(ctx)
+ assert.NoError(t, err)
+ assert.Equal(t, `{"text":[{"val":"a"},{"val":"b"},{"val":"c"},{"val":"d"}]}`, d1.Marshal())
+ assert.Equal(t, `{"text":[{"val":"a"},{"val":"b"},{"val":"c"},{"val":"d"}]}`, d2.Marshal())
+
+ err = d1.Update(func(root *json.Object, p *presence.Presence) error {
+ root.GetText("text").Edit(1, 3, "")
+ return nil
+ })
+ assert.NoError(t, err)
+ err = c1.Sync(ctx)
+ assert.NoError(t, err)
+ assert.Equal(t, `{"text":[{"val":"a"},{"val":"d"}]}`, d1.Marshal())
+ assert.Equal(t, 2, d1.GarbageLen()) // b,c
+
+ err = c2.Sync(ctx)
+ assert.NoError(t, err)
+ err = c2.Sync(ctx)
+ assert.NoError(t, err)
+ err = c1.Sync(ctx)
+ assert.NoError(t, err)
+ assert.Equal(t, `{"text":[{"val":"a"},{"val":"d"}]}`, d2.Marshal())
+ assert.Equal(t, 0, d1.GarbageLen())
+ })
+
+ t.Run("Should work properly when there are multiple nodes to be collected in tree type", func(t *testing.T) {
+ ctx := context.Background()
+ d1 := document.New(helper.TestDocKey(t))
+ err := c1.Attach(ctx, d1)
+ assert.NoError(t, err)
+
+ d2 := document.New(helper.TestDocKey(t))
+ err = c2.Attach(ctx, d2)
+ assert.NoError(t, err)
+
+ err = d1.Update(func(root *json.Object, p *presence.Presence) error {
+ root.SetNewTree("tree", &json.TreeNode{
+ Type: "r",
+ Children: []json.TreeNode{{
+ Type: "text", Value: "z",
+ }},
+ })
+ return nil
+ })
+ assert.NoError(t, err)
+ err = d1.Update(func(root *json.Object, p *presence.Presence) error {
+ root.GetTree("tree").EditByPath([]int{0}, []int{1}, &json.TreeNode{Type: "text", Value: "a"}, 0)
+ return nil
+ })
+ assert.NoError(t, err)
+ err = d1.Update(func(root *json.Object, p *presence.Presence) error {
+ root.GetTree("tree").EditByPath([]int{1}, []int{1}, &json.TreeNode{Type: "text", Value: "b"}, 0)
+ return nil
+ })
+ assert.NoError(t, err)
+ err = d1.Update(func(root *json.Object, p *presence.Presence) error {
+ root.GetTree("tree").EditByPath([]int{2}, []int{2}, &json.TreeNode{Type: "text", Value: "d"}, 0)
+ return nil
+ })
+ assert.NoError(t, err)
+ err = c1.Sync(ctx)
+ assert.NoError(t, err)
+ err = c2.Sync(ctx)
+ assert.NoError(t, err)
+
+ assert.Equal(t, `abd`, d1.Root().GetTree("tree").ToXML())
+ assert.Equal(t, `abd`, d2.Root().GetTree("tree").ToXML())
+ assert.Equal(t, 1, d1.GarbageLen()) // z
+
+ err = d1.Update(func(root *json.Object, p *presence.Presence) error {
+ root.GetTree("tree").EditByPath([]int{2}, []int{2}, &json.TreeNode{Type: "text", Value: "c"}, 0)
+ return nil
+ })
+ assert.NoError(t, err)
+ err = c1.Sync(ctx)
+ assert.NoError(t, err)
+ err = c2.Sync(ctx)
+ assert.NoError(t, err)
+ err = c2.Sync(ctx)
+ assert.NoError(t, err)
+ assert.Equal(t, `abcd`, d1.Root().GetTree("tree").ToXML())
+ assert.Equal(t, `abcd`, d2.Root().GetTree("tree").ToXML())
+
+ err = d1.Update(func(root *json.Object, p *presence.Presence) error {
+ root.GetTree("tree").EditByPath([]int{1}, []int{3}, nil, 0)
+ return nil
+ })
+ assert.NoError(t, err)
+ err = c1.Sync(ctx)
+ assert.NoError(t, err)
+ assert.Equal(t, `ad`, d1.Root().GetTree("tree").ToXML())
+ assert.Equal(t, 2, d1.GarbageLen()) // b,c
+
+ err = c2.Sync(ctx)
+ assert.NoError(t, err)
+ err = c2.Sync(ctx)
+ assert.NoError(t, err)
+ err = c1.Sync(ctx)
+ assert.NoError(t, err)
+ assert.Equal(t, `ad`, d2.Root().GetTree("tree").ToXML())
+ assert.Equal(t, 0, d1.GarbageLen())
+ })
}