Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add garbage collection for text type #104

Merged
merged 15 commits into from
Dec 21, 2020
Merged
12 changes: 7 additions & 5 deletions api/converter/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ package converter_test
import (
"math"
"testing"
"time"
gotime "time"

"github.com/stretchr/testify/assert"

"github.com/yorkie-team/yorkie/api/converter"
"github.com/yorkie-team/yorkie/pkg/document"
"github.com/yorkie-team/yorkie/pkg/document/proxy"
"github.com/yorkie-team/yorkie/pkg/document/time"
)

func TestConverter(t *testing.T) {
Expand Down Expand Up @@ -66,7 +67,7 @@ func TestConverter(t *testing.T) {
SetDouble("1.4", 1.79).
SetString("k1.5", "4").
SetBytes("k1.6", []byte{65, 66}).
SetDate("k1.7", time.Now())
SetDate("k1.7", gotime.Now())

// an array
root.SetNewArray("k2").
Expand All @@ -76,7 +77,7 @@ func TestConverter(t *testing.T) {
AddDouble(3.0).
AddString("4").
AddBytes([]byte{65}).
AddDate(time.Now())
AddDate(gotime.Now())

// plain text
root.SetNewText("k3").
Expand Down Expand Up @@ -121,7 +122,7 @@ func TestConverter(t *testing.T) {
SetDouble("1.4", 1.79).
SetString("k1.5", "4").
SetBytes("k1.6", []byte{65, 66}).
SetDate("k1.7", time.Now())
SetDate("k1.7", gotime.Now())

// an array
root.SetNewArray("k2").
Expand All @@ -131,7 +132,7 @@ func TestConverter(t *testing.T) {
AddDouble(3.0).
AddString("4").
AddBytes([]byte{65}).
AddDate(time.Now())
AddDate(gotime.Now())

// plain text
root.SetNewText("k3").
Expand All @@ -153,6 +154,7 @@ func TestConverter(t *testing.T) {

pbPack := converter.ToChangePack(d1.CreateChangePack())
pack, err := converter.FromChangePack(pbPack)
pack.MinSyncedTicket = time.MaxTicket
assert.NoError(t, err)

d2 := document.New("c1", "d1")
Expand Down
93 changes: 85 additions & 8 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ func TestClientAndDocument(t *testing.T) {
assert.Equal(t, d1.Marshal(), d2.Marshal())
})

t.Run("garbage collection test", func(t *testing.T) {
t.Run("garbage collection for container type test", func(t *testing.T) {
ctx := context.Background()

d1 := document.New(testhelper.Collection, t.Name())
Expand Down Expand Up @@ -881,6 +881,79 @@ func TestClientAndDocument(t *testing.T) {
assert.Equal(t, 0, d2.GarbageLen())
})

t.Run("garbage collection for text type test", func(t *testing.T) {
ctx := context.Background()

d1 := document.New(testhelper.Collection, t.Name())
err := c1.Attach(ctx, d1)
assert.NoError(t, err)

d2 := document.New(testhelper.Collection, t.Name())
err = c2.Attach(ctx, d2)
assert.NoError(t, err)

err = d1.Update(func(root *proxy.ObjectProxy) error {
root.SetNewText("text").
Edit(0, 0, "Hello world")
root.SetNewRichText("richText").
Edit(0, 0, "Hello world", nil)
return nil
}, "sets test and richText")
assert.NoError(t, err)
assert.Equal(t, 0, d1.GarbageLen())
assert.Equal(t, 0, d2.GarbageLen())

// (0, 0) -> (1, 0): syncedseqs:(0, 0)
err = c1.Sync(ctx)
assert.NoError(t, err)

// (1, 0) -> (1, 1): syncedseqs:(0, 0)
err = c2.Sync(ctx)
assert.NoError(t, err)

err = d2.Update(func(root *proxy.ObjectProxy) error {
root.GetText("text").
Edit(0, 1, "a").
Edit(1, 2, "b")
root.GetRichText("richText").
Edit(0, 1, "a", map[string]string{"b": "1"})
return nil
}, "edit text type elements")
assert.NoError(t, err)
assert.Equal(t, 0, d1.GarbageLen())
assert.Equal(t, 3, d2.GarbageLen())

// (1, 1) -> (1, 2): syncedseqs:(0, 1)
err = c2.Sync(ctx)
assert.NoError(t, err)
assert.Equal(t, 0, d1.GarbageLen())
assert.Equal(t, 3, d2.GarbageLen())

// (1, 2) -> (2, 2): syncedseqs:(1, 1)
err = c1.Sync(ctx)
assert.NoError(t, err)
assert.Equal(t, 3, d1.GarbageLen())
assert.Equal(t, 3, d2.GarbageLen())

// (2, 2) -> (2, 2): syncedseqs:(1, 2)
err = c2.Sync(ctx)
assert.NoError(t, err)
assert.Equal(t, 3, d1.GarbageLen())
assert.Equal(t, 3, d2.GarbageLen())

// (2, 2) -> (2, 2): syncedseqs:(2, 2): meet GC condition
err = c1.Sync(ctx)
assert.NoError(t, err)
assert.Equal(t, 0, d1.GarbageLen())
assert.Equal(t, 3, d2.GarbageLen())

// (2, 2) -> (2, 2): syncedseqs:(2, 2): meet GC condition
err = c2.Sync(ctx)
assert.NoError(t, err)
assert.Equal(t, 0, d1.GarbageLen())
assert.Equal(t, 0, d2.GarbageLen())
})

t.Run("garbage collection with detached document test", func(t *testing.T) {
ctx := context.Background()

Expand All @@ -896,8 +969,10 @@ func TestClientAndDocument(t *testing.T) {
root.SetInteger("1", 1)
root.SetNewArray("2").AddInteger(1, 2, 3)
root.SetInteger("3", 3)
root.SetNewText("4").Edit(0, 0, "Hi")
root.SetNewRichText("5").Edit(0, 0, "Hi", nil)
return nil
}, "sets 1,2,3")
}, "sets 1,2,3,4,5")
assert.NoError(t, err)
assert.Equal(t, 0, d1.GarbageLen())
assert.Equal(t, 0, d2.GarbageLen())
Expand All @@ -912,16 +987,18 @@ func TestClientAndDocument(t *testing.T) {

err = d1.Update(func(root *proxy.ObjectProxy) error {
root.Delete("2")
root.GetText("4").Edit(0, 1, "h")
root.GetRichText("5").Edit(0, 1, "h", map[string]string{"b": "1"})
return nil
}, "removes 2")
}, "removes 2 and edit text type elements")
assert.NoError(t, err)
assert.Equal(t, 4, d1.GarbageLen())
assert.Equal(t, 6, d1.GarbageLen())
assert.Equal(t, 0, d2.GarbageLen())

// (1, 1) -> (2, 1): syncedseqs:(1, 0)
err = c1.Sync(ctx)
assert.NoError(t, err)
assert.Equal(t, 4, d1.GarbageLen())
assert.Equal(t, 6, d1.GarbageLen())
assert.Equal(t, 0, d2.GarbageLen())

err = c2.Detach(ctx, d2)
Expand All @@ -930,14 +1007,14 @@ func TestClientAndDocument(t *testing.T) {
// (2, 1) -> (2, 2): syncedseqs:(1, x)
err = c2.Sync(ctx)
assert.NoError(t, err)
assert.Equal(t, 4, d1.GarbageLen())
assert.Equal(t, 4, d2.GarbageLen())
assert.Equal(t, 6, d1.GarbageLen())
assert.Equal(t, 6, d2.GarbageLen())

// (2, 2) -> (2, 2): syncedseqs:(2, x): meet GC condition
err = c1.Sync(ctx)
assert.NoError(t, err)
assert.Equal(t, 0, d1.GarbageLen())
assert.Equal(t, 4, d2.GarbageLen())
assert.Equal(t, 6, d2.GarbageLen())
})
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/document/change/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,8 @@ func (c *Context) RegisterElement(elem json.Element) {
func (c *Context) RegisterRemovedElementPair(parent json.Container, deleted json.Element) {
c.root.RegisterRemovedElementPair(parent, deleted)
}

// RegisterRemovedNodeTextElement registers a given text type element to hash table.
func (c *Context) RegisterRemovedNodeTextElement(textType json.TextElement) {
c.root.RegisterRemovedNodeTextElement(textType)
}
155 changes: 155 additions & 0 deletions pkg/document/document_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ package document_test
import (
"errors"
"fmt"
"runtime"
"testing"

"github.com/stretchr/testify/assert"

"github.com/yorkie-team/yorkie/api/converter"
"github.com/yorkie-team/yorkie/pkg/document"
"github.com/yorkie-team/yorkie/pkg/document/checkpoint"
"github.com/yorkie-team/yorkie/pkg/document/json"
"github.com/yorkie-team/yorkie/pkg/document/proxy"
"github.com/yorkie-team/yorkie/pkg/document/time"
"github.com/yorkie-team/yorkie/testhelper"
Expand Down Expand Up @@ -171,6 +173,159 @@ func TestDocument(t *testing.T) {
testhelper.PrintMemStats()
})

t.Run("garbage collection for text test", func(t *testing.T) {
doc := document.New("c1", "d1")
assert.Equal(t, "{}", doc.Marshal())
assert.False(t, doc.HasLocalChanges())

// check garbage length
expected := `{"k1":"Hello mario"}`
err := doc.Update(func(root *proxy.ObjectProxy) error {
root.SetNewText("k1").
Edit(0, 0, "Hello world").
Edit(6, 11, "mario")
assert.Equal(t, expected, root.Marshal())
return nil
}, "edit text k1")
assert.NoError(t, err)
assert.Equal(t, expected, doc.Marshal())
assert.Equal(t, 1, doc.GarbageLen())

expected = `{"k1":"Hi jane"}`
err = doc.Update(func(root *proxy.ObjectProxy) error {
text := root.GetText("k1")
text.Edit(0, 5, "Hi")
text.Edit(3, 4, "j")
text.Edit(4, 8, "ane")
assert.Equal(t, expected, root.Marshal())
return nil
}, "edit text k1")
assert.NoError(t, err)
assert.Equal(t, expected, doc.Marshal())

expectedGarbageLen := 4
assert.Equal(t, expectedGarbageLen, doc.GarbageLen())
// garbage collect
assert.Equal(t, expectedGarbageLen, doc.GarbageCollect(time.MaxTicket))
})

t.Run("garbage collection for rich text test", func(t *testing.T) {
doc := document.New("c1", "d1")
assert.Equal(t, "{}", doc.Marshal())
assert.False(t, doc.HasLocalChanges())

// check garbage length
expected := `{"k1":[{"attrs":{"b":"1"},"val":"Hello "},{"attrs":{},"val":"mario"}]}`
err := doc.Update(func(root *proxy.ObjectProxy) error {
root.SetNewRichText("k1").
Edit(0, 0, "Hello world", map[string]string{"b": "1"}).
Edit(6, 11, "mario", nil)
assert.Equal(t, expected, root.Marshal())
return nil
}, "edit text k1")
assert.NoError(t, err)
assert.Equal(t, expected, doc.Marshal())
assert.Equal(t, 1, doc.GarbageLen())

expected = `{"k1":[{"attrs":{"b":"1"},"val":"Hi"},{"attrs":{"b":"1"},"val":" "},` +
`{"attrs":{},"val":"j"},{"attrs":{"b":"1"},"val":"ane"}]}`
err = doc.Update(func(root *proxy.ObjectProxy) error {
text := root.GetRichText("k1")
text.Edit(0, 5, "Hi", map[string]string{"b": "1"})
text.Edit(3, 4, "j", nil)
text.Edit(4, 8, "ane", map[string]string{"b": "1"})
assert.Equal(t, expected, root.Marshal())
return nil
}, "edit text k1")
assert.NoError(t, err)
assert.Equal(t, expected, doc.Marshal())

expectedGarbageLen := 4
assert.Equal(t, expectedGarbageLen, doc.GarbageLen())
// garbage collect
assert.Equal(t, expectedGarbageLen, doc.GarbageCollect(time.MaxTicket))
})

t.Run("garbage collection for large size of text garbage test", func(t *testing.T) {
doc := document.New("c1", "d1")
assert.Equal(t, "{}", doc.Marshal())
assert.False(t, doc.HasLocalChanges())

printMemStats := func(root *json.Object) {
bytes, err := converter.ObjectToBytes(doc.RootObject())
assert.NoError(t, err)
testhelper.PrintSnapshotBytesSize(bytes)
testhelper.PrintMemStats()
}

textSize := 1_000
// 01. initial
err := doc.Update(func(root *proxy.ObjectProxy) error {
text := root.SetNewText("k1")
for i := 0; i < textSize; i++ {
text.Edit(i, i, "a")
}
return nil
}, "initial")
assert.NoError(t, err)
fmt.Println("-----initial")
printMemStats(doc.RootObject())

// 02. 1000 nodes modified
err = doc.Update(func(root *proxy.ObjectProxy) error {
text := root.GetText("k1")
for i := 0; i < textSize; i++ {
text.Edit(i, i+1, "b")
}
return nil
}, "1000 nodes modified")
assert.NoError(t, err)
fmt.Println("-----1000 nodes modified")
printMemStats(doc.RootObject())
assert.Equal(t, textSize, doc.GarbageLen())

// 03. GC
assert.Equal(t, textSize, doc.GarbageCollect(time.MaxTicket))
runtime.GC()
fmt.Println("-----Garbage collect")
printMemStats(doc.RootObject())

// 04. long text by one node
err = doc.Update(func(root *proxy.ObjectProxy) error {
text := root.SetNewText("k2")
str := ""
for i := 0; i < textSize; i++ {
str += "a"
}
text.Edit(0, 0, str)
return nil
}, "initial")
fmt.Println("-----long text by one node")
assert.NoError(t, err)
printMemStats(doc.RootObject())

// 05. Modify one node multiple times
err = doc.Update(func(root *proxy.ObjectProxy) error {
text := root.GetText("k2")
for i := 0; i < textSize; i++ {
if i != textSize {
text.Edit(i, i+1, "b")
}
}
return nil
}, "Modify one node multiple times")
assert.NoError(t, err)
fmt.Println("-----Modify one node multiple times")
printMemStats(doc.RootObject())

// 06. GC
assert.Equal(t, textSize, doc.GarbageLen())
assert.Equal(t, textSize, doc.GarbageCollect(time.MaxTicket))
runtime.GC()
fmt.Println("-----Garbage collect")
printMemStats(doc.RootObject())
})

t.Run("object test", func(t *testing.T) {
doc := document.New("c1", "d1")
err := doc.Update(func(root *proxy.ObjectProxy) error {
Expand Down
3 changes: 2 additions & 1 deletion pkg/document/json/array_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import (

func TestArray(t *testing.T) {
t.Run("marshal test", func(t *testing.T) {
ctx := testhelper.TextChangeContext()
root := testhelper.TestRoot()
ctx := testhelper.TextChangeContext(root)

a := json.NewArray(json.NewRGATreeList(), ctx.IssueTimeTicket())

Expand Down
Loading