From b80f64a53e0e38b0a49b45c3e134754eb006bce4 Mon Sep 17 00:00:00 2001 From: Jean Froundjian Date: Mon, 9 Sep 2024 10:18:06 -0400 Subject: [PATCH 1/3] feat: Add configurable size limit to recycled Parsers in pool --- parser_test.go | 30 ++++++++++++++++++++++++++++++ pool.go | 13 +++++++++++++ 2 files changed, 43 insertions(+) diff --git a/parser_test.go b/parser_test.go index 90da264..53c40b2 100644 --- a/parser_test.go +++ b/parser_test.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "strings" + "sync" "testing" "time" ) @@ -188,6 +189,35 @@ func TestParserPool(t *testing.T) { } } +func TestParserPoolMaxSize(t *testing.T) { + var numNew, numNewLimit int + ppr := &ParserPool{ + sync.Pool{New: func() interface{} { numNew++; return new(Parser) }}, + } + pprLimit := &ParserPool{ + sync.Pool{New: func() interface{} { numNewLimit++; return new(Parser) }}, + } + + parse := func(ppr *ParserPool, maxSize int, index int) { + var json = fmt.Sprintf(`{"%d":"test"}`, index) + pr := ppr.Get() + _, _ = pr.Parse(json) + ppr.PutIfSizeLessThan(pr, maxSize) + } + for i := 0; i < 10; i++ { + parse(ppr, 0, i) + parse(pprLimit, 1, i) + } + + if numNew != 1 { + t.Fatalf("Expected exactly 1 calls to Pool New with no Max Size (not %d)", numNew) + } + + if numNewLimit != 10 { + t.Fatalf("Expected exactly 10 calls to Pool with a Max Size (not %d)", numNewLimit) + } +} + func TestValueInvalidTypeConversion(t *testing.T) { var p Parser diff --git a/pool.go b/pool.go index c942df6..02a7880 100644 --- a/pool.go +++ b/pool.go @@ -28,6 +28,19 @@ func (pp *ParserPool) Put(p *Parser) { pp.pool.Put(p) } +// PutIfSizeLessThan PutIfLessThan Put returns p to pp only if the number of values in the cache is less than maxSize. +// If set to <= 0, no size limit is applied. +// +// p and objects recursively returned from p cannot be used after p is put into pp or released +func (pp *ParserPool) PutIfSizeLessThan(p *Parser, maxSize int) { + // Release the parser if the cache is too big + if maxSize > 0 && cap(p.c.vs) > maxSize { + return + } + + pp.pool.Put(p) +} + // ArenaPool may be used for pooling Arenas for similarly typed JSONs. type ArenaPool struct { pool sync.Pool From dd37eb9852851d1dbd0c3ecc7357e8688b93e34e Mon Sep 17 00:00:00 2001 From: Jean Froundjian Date: Tue, 10 Sep 2024 09:57:39 -0400 Subject: [PATCH 2/3] Move pool tests into their own test and disable for race --- parser_test.go | 41 ----------------------------------------- pool_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 41 deletions(-) create mode 100644 pool_test.go diff --git a/parser_test.go b/parser_test.go index 53c40b2..8ddd984 100644 --- a/parser_test.go +++ b/parser_test.go @@ -4,7 +4,6 @@ import ( "fmt" "math" "strings" - "sync" "testing" "time" ) @@ -178,46 +177,6 @@ func TestParseRawString(t *testing.T) { }) } -func TestParserPool(t *testing.T) { - var pp ParserPool - for i := 0; i < 10; i++ { - p := pp.Get() - if _, err := p.Parse("null"); err != nil { - t.Fatalf("cannot parse null: %s", err) - } - pp.Put(p) - } -} - -func TestParserPoolMaxSize(t *testing.T) { - var numNew, numNewLimit int - ppr := &ParserPool{ - sync.Pool{New: func() interface{} { numNew++; return new(Parser) }}, - } - pprLimit := &ParserPool{ - sync.Pool{New: func() interface{} { numNewLimit++; return new(Parser) }}, - } - - parse := func(ppr *ParserPool, maxSize int, index int) { - var json = fmt.Sprintf(`{"%d":"test"}`, index) - pr := ppr.Get() - _, _ = pr.Parse(json) - ppr.PutIfSizeLessThan(pr, maxSize) - } - for i := 0; i < 10; i++ { - parse(ppr, 0, i) - parse(pprLimit, 1, i) - } - - if numNew != 1 { - t.Fatalf("Expected exactly 1 calls to Pool New with no Max Size (not %d)", numNew) - } - - if numNewLimit != 10 { - t.Fatalf("Expected exactly 10 calls to Pool with a Max Size (not %d)", numNewLimit) - } -} - func TestValueInvalidTypeConversion(t *testing.T) { var p Parser diff --git a/pool_test.go b/pool_test.go new file mode 100644 index 0000000..77edc49 --- /dev/null +++ b/pool_test.go @@ -0,0 +1,50 @@ +// Pool is no-op under race detector, so all these tests do not work. +// go:build !race + +package astjson + +import ( + "fmt" + "sync" + "testing" +) + +func TestParserPool(t *testing.T) { + var pp ParserPool + for i := 0; i < 10; i++ { + p := pp.Get() + if _, err := p.Parse("null"); err != nil { + t.Fatalf("cannot parse null: %s", err) + } + pp.Put(p) + } +} + +func TestParserPoolMaxSize(t *testing.T) { + var numNew, numNewLimit int + ppr := &ParserPool{ + sync.Pool{New: func() interface{} { numNew++; return new(Parser) }}, + } + pprLimit := &ParserPool{ + sync.Pool{New: func() interface{} { numNewLimit++; return new(Parser) }}, + } + + parse := func(ppr *ParserPool, maxSize int, index int) { + var json = fmt.Sprintf(`{"%d":"test"}`, index) + pr := ppr.Get() + _, _ = pr.Parse(json) + ppr.PutIfSizeLessThan(pr, maxSize) + } + for i := 0; i < 10; i++ { + parse(ppr, 0, i) + parse(pprLimit, 1, i) + } + + if numNew != 1 { + t.Fatalf("Expected exactly 1 calls to Pool New with no Max Size (not %d)", numNew) + } + + if numNewLimit != 10 { + t.Fatalf("Expected exactly 10 calls to Pool with a Max Size (not %d)", numNewLimit) + } +} From ce629e20d1b3a161c948bbf956eeffa6a8bdf254 Mon Sep 17 00:00:00 2001 From: Jean Froundjian Date: Tue, 10 Sep 2024 10:08:22 -0400 Subject: [PATCH 3/3] Remove space after // for directive --- pool_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pool_test.go b/pool_test.go index 77edc49..0111b4e 100644 --- a/pool_test.go +++ b/pool_test.go @@ -1,5 +1,5 @@ // Pool is no-op under race detector, so all these tests do not work. -// go:build !race +//go:build !race package astjson