Skip to content

Commit dd2ccc7

Browse files
authored
Tidy up and unify sync pool handling (#8068)
As @srenatus pointed out in a previous review, these were a little messy in some places. This moves the generic SyncPool type to the util package for reuse across the codebase. Also some various related improvements. Signed-off-by: Anders Eknert <anders.eknert@apple.com>
1 parent 51a50ca commit dd2ccc7

5 files changed

Lines changed: 49 additions & 88 deletions

File tree

v1/ast/syncpools.go

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import (
44
"bytes"
55
"strings"
66
"sync"
7+
8+
"github.com/open-policy-agent/opa/v1/util"
79
)
810

911
var (
10-
TermPtrPool = NewSyncPool[Term]()
11-
BytesReaderPool = NewSyncPool[bytes.Reader]()
12-
IndexResultPool = NewSyncPool[IndexResult]()
12+
TermPtrPool = util.NewSyncPool[Term]()
13+
BytesReaderPool = util.NewSyncPool[bytes.Reader]()
14+
IndexResultPool = util.NewSyncPool[IndexResult]()
15+
bbPool = util.NewSyncPool[bytes.Buffer]()
1316
// Needs custom pool because of custom Put logic.
1417
sbPool = &stringBuilderPool{
1518
pool: sync.Pool{
@@ -29,37 +32,10 @@ var (
2932
)
3033

3134
type (
32-
syncPool[T any] struct {
33-
pool sync.Pool
34-
}
35-
stringBuilderPool struct {
36-
pool sync.Pool
37-
}
38-
vvPool struct {
39-
pool sync.Pool
40-
}
35+
stringBuilderPool struct{ pool sync.Pool }
36+
vvPool struct{ pool sync.Pool }
4137
)
4238

43-
func NewSyncPool[T any]() *syncPool[T] {
44-
return &syncPool[T]{
45-
pool: sync.Pool{
46-
New: func() any {
47-
return new(T)
48-
},
49-
},
50-
}
51-
}
52-
53-
func (p *syncPool[T]) Get() *T {
54-
return p.pool.Get().(*T)
55-
}
56-
57-
func (p *syncPool[T]) Put(x *T) {
58-
if x != nil {
59-
p.pool.Put(x)
60-
}
61-
}
62-
6339
func (p *stringBuilderPool) Get() *strings.Builder {
6440
return p.pool.Get().(*strings.Builder)
6541
}

v1/ast/term.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,12 +1153,6 @@ func IsVarCompatibleString(s string) bool {
11531153
return varRegexp.MatchString(s)
11541154
}
11551155

1156-
var bbPool = &sync.Pool{
1157-
New: func() any {
1158-
return new(bytes.Buffer)
1159-
},
1160-
}
1161-
11621156
func (ref Ref) String() string {
11631157
// Note(anderseknert):
11641158
// Options tried in the order of cheapness, where after some effort,
@@ -1180,7 +1174,7 @@ func (ref Ref) String() string {
11801174

11811175
_var := ref[0].Value.String()
11821176

1183-
bb := bbPool.Get().(*bytes.Buffer)
1177+
bb := bbPool.Get()
11841178
bb.Reset()
11851179

11861180
defer bbPool.Put(bb)

v1/topdown/eval.go

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -120,25 +120,10 @@ type eval struct {
120120
}
121121

122122
type (
123-
evp struct {
124-
pool sync.Pool
125-
}
126-
evfp struct {
127-
pool sync.Pool
128-
}
129-
evbp struct {
130-
pool sync.Pool
131-
}
123+
evfp struct{ pool sync.Pool }
124+
evbp struct{ pool sync.Pool }
132125
)
133126

134-
func (ep *evp) Put(e *eval) {
135-
ep.pool.Put(e)
136-
}
137-
138-
func (ep *evp) Get() *eval {
139-
return ep.pool.Get().(*eval)
140-
}
141-
142127
func (ep *evfp) Put(e *evalFunc) {
143128
if e != nil {
144129
e.e, e.terms, e.ir = nil, nil, nil
@@ -162,13 +147,9 @@ func (ep *evbp) Get() *evalBuiltin {
162147
}
163148

164149
var (
165-
evalPool = &evp{
166-
pool: sync.Pool{
167-
New: func() any {
168-
return &eval{}
169-
},
170-
},
171-
}
150+
evalPool = util.NewSyncPool[eval]()
151+
deecPool = util.NewSyncPool[deferredEarlyExitContainer]()
152+
resolverPool = util.NewSyncPool[evalResolver]()
172153
evalFuncPool = &evfp{
173154
pool: sync.Pool{
174155
New: func() any {
@@ -1688,7 +1669,7 @@ func (e *eval) getRules(ref ast.Ref, args []*ast.Term) (*ast.IndexResult, error)
16881669
return nil, nil
16891670
}
16901671

1691-
resolver := resolverPool.Get().(*evalResolver)
1672+
resolver := resolverPool.Get()
16921673
defer func() {
16931674
resolver.e = nil
16941675
resolver.args = nil
@@ -1743,14 +1724,6 @@ type evalResolver struct {
17431724
args []*ast.Term
17441725
}
17451726

1746-
var (
1747-
resolverPool = sync.Pool{
1748-
New: func() any {
1749-
return &evalResolver{}
1750-
},
1751-
}
1752-
)
1753-
17541727
func (e *evalResolver) Resolve(ref ast.Ref) (ast.Value, error) {
17551728
e.e.instr.startTimer(evalOpResolve)
17561729

@@ -2439,12 +2412,6 @@ func (dc *deferredEarlyExitContainer) copyError() *deferredEarlyExitError {
24392412
return &cpy
24402413
}
24412414

2442-
var deecPool = sync.Pool{
2443-
New: func() any {
2444-
return &deferredEarlyExitContainer{}
2445-
},
2446-
}
2447-
24482415
type evalTree struct {
24492416
e *eval
24502417
bindings *bindings
@@ -2530,7 +2497,7 @@ func (e evalTree) enumerate(iter unifyIterator) error {
25302497
return err
25312498
}
25322499

2533-
dc := deecPool.Get().(*deferredEarlyExitContainer)
2500+
dc := deecPool.Get()
25342501
dc.deferred = nil
25352502
defer deecPool.Put(dc)
25362503

v1/util/performance.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,34 @@ import (
77
"unsafe"
88
)
99

10+
// SyncPool is a generic sync.Pool for type T, providing some convenience
11+
// over sync.Pool directly: [SyncPool.Put] ensures that nil values are not
12+
// put into the pool, and [SyncPool.Get] returns a pointer to T without having
13+
// to do a type assertion at the call site.
14+
type SyncPool[T any] struct {
15+
pool sync.Pool
16+
}
17+
18+
func NewSyncPool[T any]() *SyncPool[T] {
19+
return &SyncPool[T]{
20+
pool: sync.Pool{
21+
New: func() any {
22+
return new(T)
23+
},
24+
},
25+
}
26+
}
27+
28+
func (p *SyncPool[T]) Get() *T {
29+
return p.pool.Get().(*T)
30+
}
31+
32+
func (p *SyncPool[T]) Put(x *T) {
33+
if x != nil {
34+
p.pool.Put(x)
35+
}
36+
}
37+
1038
// NewPtrSlice returns a slice of pointers to T with length n,
1139
// with only 2 allocations performed no matter the size of n.
1240
// See:
@@ -133,5 +161,7 @@ func (sp *SlicePool[T]) Get(length int) *[]T {
133161

134162
// Put returns a pointer to a slice of type T to the pool.
135163
func (sp *SlicePool[T]) Put(s *[]T) {
136-
sp.pool.Put(s)
164+
if s != nil {
165+
sp.pool.Put(s)
166+
}
137167
}

v1/util/read_gzip_body.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,11 @@ import (
88
"io"
99
"net/http"
1010
"strings"
11-
"sync"
1211

1312
"github.com/open-policy-agent/opa/v1/util/decoding"
1413
)
1514

16-
var gzipReaderPool = sync.Pool{
17-
New: func() any {
18-
reader := new(gzip.Reader)
19-
return reader
20-
},
21-
}
15+
var gzipReaderPool = NewSyncPool[gzip.Reader]()
2216

2317
// Note(philipc): Originally taken from server/server.go
2418
// The DecodingLimitHandler handles validating that the gzip payload is within the
@@ -49,7 +43,7 @@ func ReadMaybeCompressedBody(r *http.Request) ([]byte, error) {
4943
return nil, errors.New("gzip payload too large")
5044
}
5145

52-
gzReader := gzipReaderPool.Get().(*gzip.Reader)
46+
gzReader := gzipReaderPool.Get()
5347
defer func() {
5448
gzReader.Close()
5549
gzipReaderPool.Put(gzReader)

0 commit comments

Comments
 (0)