From 6282364f1f241053c05ff8f03c720ca8643dfa31 Mon Sep 17 00:00:00 2001 From: Ignacio Van Droogenbroeck Date: Mon, 2 Mar 2026 19:03:48 -0600 Subject: [PATCH] perf(decode): replace goroutine-per-type value pool with sync.Pool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the cachedValues mechanism (unbounded goroutine per type + channel with buffer of 256) with sync.Pool per type stored in a sync.Map. Eliminates goroutine leak — the old approach spawned a goroutine for every unique decoded type that ran forever in an infinite loop. No performance regression in benchmarks. --- decode_typgen.go | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/decode_typgen.go b/decode_typgen.go index 0b4c1d0..e453bce 100644 --- a/decode_typgen.go +++ b/decode_typgen.go @@ -5,36 +5,18 @@ import ( "sync" ) -var cachedValues struct { - m map[reflect.Type]chan reflect.Value - sync.RWMutex -} +var cachedPools sync.Map // map[reflect.Type]*sync.Pool func cachedValue(t reflect.Type) reflect.Value { - cachedValues.RLock() - ch := cachedValues.m[t] - cachedValues.RUnlock() - if ch != nil { - return <-ch - } - - cachedValues.Lock() - defer cachedValues.Unlock() - if ch = cachedValues.m[t]; ch != nil { - return <-ch - } - - ch = make(chan reflect.Value, 256) - go func() { - for { - ch <- reflect.New(t) - } - }() - if cachedValues.m == nil { - cachedValues.m = make(map[reflect.Type]chan reflect.Value, 8) + v, ok := cachedPools.Load(t) + if !ok { + v, _ = cachedPools.LoadOrStore(t, &sync.Pool{ + New: func() interface{} { + return reflect.New(t) + }, + }) } - cachedValues.m[t] = ch - return <-ch + return v.(*sync.Pool).Get().(reflect.Value) } func (d *Decoder) newValue(t reflect.Type) reflect.Value {