mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-01 03:14:16 +00:00
82c6b3ad29
Resolves golang/protobuf#1521 In the internal/impl package, a helper computes metadata used for serialization and deserialization of extension values. It features a package var of type sync.Map that is used as cache. This ostensibly was for performance, however it has never worked, because the code that updates the cache inserts entries into the wrong map. (These erroneous entries do not cause any issues because they are keys that never conflict with those used in valid queries.) Instead of a one-line fix to have this code update the correct cache, this change removes the cache altogether. The existence of the cache means that once a protoreflect.ExtensionType is used to serialize or deserialize a message, it can *never* be garbage collected. Workloads that are long-lived servers using dynamic messages and extensions based on user requests will exhibit unbounded growth in memory usage as the cache only gets larger and larger. Since the cache has never worked, any advantages it ostensibly conferred have not been missed. So this fixes the unbounded memory growth instead. Change-Id: I15957fd8521852f9f7f9f89db7ebfd7170d85202 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/560095 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Michael Stapelberg <stapelberg@google.com> Reviewed-by: Lasse Folger <lassefolger@google.com> Reviewed-by: Damien Neil <dneil@google.com>
216 lines
5.8 KiB
Go
216 lines
5.8 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package impl
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"google.golang.org/protobuf/encoding/protowire"
|
|
"google.golang.org/protobuf/internal/errors"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
)
|
|
|
|
type extensionFieldInfo struct {
|
|
wiretag uint64
|
|
tagsize int
|
|
unmarshalNeedsValue bool
|
|
funcs valueCoderFuncs
|
|
validation validationInfo
|
|
}
|
|
|
|
func getExtensionFieldInfo(xt protoreflect.ExtensionType) *extensionFieldInfo {
|
|
if xi, ok := xt.(*ExtensionInfo); ok {
|
|
xi.lazyInit()
|
|
return xi.info
|
|
}
|
|
// Ideally we'd cache the resulting *extensionFieldInfo so we don't have to
|
|
// recompute this metadata repeatedly. But without support for something like
|
|
// weak references, such a cache would pin temporary values (like dynamic
|
|
// extension types, constructed for the duration of a user request) to the
|
|
// heap forever, causing memory usage of the cache to grow unbounded.
|
|
// See discussion in https://github.com/golang/protobuf/issues/1521.
|
|
return makeExtensionFieldInfo(xt.TypeDescriptor())
|
|
}
|
|
|
|
func makeExtensionFieldInfo(xd protoreflect.ExtensionDescriptor) *extensionFieldInfo {
|
|
var wiretag uint64
|
|
if !xd.IsPacked() {
|
|
wiretag = protowire.EncodeTag(xd.Number(), wireTypes[xd.Kind()])
|
|
} else {
|
|
wiretag = protowire.EncodeTag(xd.Number(), protowire.BytesType)
|
|
}
|
|
e := &extensionFieldInfo{
|
|
wiretag: wiretag,
|
|
tagsize: protowire.SizeVarint(wiretag),
|
|
funcs: encoderFuncsForValue(xd),
|
|
}
|
|
// Does the unmarshal function need a value passed to it?
|
|
// This is true for composite types, where we pass in a message, list, or map to fill in,
|
|
// and for enums, where we pass in a prototype value to specify the concrete enum type.
|
|
switch xd.Kind() {
|
|
case protoreflect.MessageKind, protoreflect.GroupKind, protoreflect.EnumKind:
|
|
e.unmarshalNeedsValue = true
|
|
default:
|
|
if xd.Cardinality() == protoreflect.Repeated {
|
|
e.unmarshalNeedsValue = true
|
|
}
|
|
}
|
|
return e
|
|
}
|
|
|
|
type lazyExtensionValue struct {
|
|
atomicOnce uint32 // atomically set if value is valid
|
|
mu sync.Mutex
|
|
xi *extensionFieldInfo
|
|
value protoreflect.Value
|
|
b []byte
|
|
fn func() protoreflect.Value
|
|
}
|
|
|
|
type ExtensionField struct {
|
|
typ protoreflect.ExtensionType
|
|
|
|
// value is either the value of GetValue,
|
|
// or a *lazyExtensionValue that then returns the value of GetValue.
|
|
value protoreflect.Value
|
|
lazy *lazyExtensionValue
|
|
}
|
|
|
|
func (f *ExtensionField) appendLazyBytes(xt protoreflect.ExtensionType, xi *extensionFieldInfo, num protowire.Number, wtyp protowire.Type, b []byte) {
|
|
if f.lazy == nil {
|
|
f.lazy = &lazyExtensionValue{xi: xi}
|
|
}
|
|
f.typ = xt
|
|
f.lazy.xi = xi
|
|
f.lazy.b = protowire.AppendTag(f.lazy.b, num, wtyp)
|
|
f.lazy.b = append(f.lazy.b, b...)
|
|
}
|
|
|
|
func (f *ExtensionField) canLazy(xt protoreflect.ExtensionType) bool {
|
|
if f.typ == nil {
|
|
return true
|
|
}
|
|
if f.typ == xt && f.lazy != nil && atomic.LoadUint32(&f.lazy.atomicOnce) == 0 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (f *ExtensionField) lazyInit() {
|
|
f.lazy.mu.Lock()
|
|
defer f.lazy.mu.Unlock()
|
|
if atomic.LoadUint32(&f.lazy.atomicOnce) == 1 {
|
|
return
|
|
}
|
|
if f.lazy.xi != nil {
|
|
b := f.lazy.b
|
|
val := f.typ.New()
|
|
for len(b) > 0 {
|
|
var tag uint64
|
|
if b[0] < 0x80 {
|
|
tag = uint64(b[0])
|
|
b = b[1:]
|
|
} else if len(b) >= 2 && b[1] < 128 {
|
|
tag = uint64(b[0]&0x7f) + uint64(b[1])<<7
|
|
b = b[2:]
|
|
} else {
|
|
var n int
|
|
tag, n = protowire.ConsumeVarint(b)
|
|
if n < 0 {
|
|
panic(errors.New("bad tag in lazy extension decoding"))
|
|
}
|
|
b = b[n:]
|
|
}
|
|
num := protowire.Number(tag >> 3)
|
|
wtyp := protowire.Type(tag & 7)
|
|
var out unmarshalOutput
|
|
var err error
|
|
val, out, err = f.lazy.xi.funcs.unmarshal(b, val, num, wtyp, lazyUnmarshalOptions)
|
|
if err != nil {
|
|
panic(errors.New("decode failure in lazy extension decoding: %v", err))
|
|
}
|
|
b = b[out.n:]
|
|
}
|
|
f.lazy.value = val
|
|
} else {
|
|
f.lazy.value = f.lazy.fn()
|
|
}
|
|
f.lazy.xi = nil
|
|
f.lazy.fn = nil
|
|
f.lazy.b = nil
|
|
atomic.StoreUint32(&f.lazy.atomicOnce, 1)
|
|
}
|
|
|
|
// Set sets the type and value of the extension field.
|
|
// This must not be called concurrently.
|
|
func (f *ExtensionField) Set(t protoreflect.ExtensionType, v protoreflect.Value) {
|
|
f.typ = t
|
|
f.value = v
|
|
f.lazy = nil
|
|
}
|
|
|
|
// SetLazy sets the type and a value that is to be lazily evaluated upon first use.
|
|
// This must not be called concurrently.
|
|
func (f *ExtensionField) SetLazy(t protoreflect.ExtensionType, fn func() protoreflect.Value) {
|
|
f.typ = t
|
|
f.lazy = &lazyExtensionValue{fn: fn}
|
|
}
|
|
|
|
// Value returns the value of the extension field.
|
|
// This may be called concurrently.
|
|
func (f *ExtensionField) Value() protoreflect.Value {
|
|
if f.lazy != nil {
|
|
if atomic.LoadUint32(&f.lazy.atomicOnce) == 0 {
|
|
f.lazyInit()
|
|
}
|
|
return f.lazy.value
|
|
}
|
|
return f.value
|
|
}
|
|
|
|
// Type returns the type of the extension field.
|
|
// This may be called concurrently.
|
|
func (f ExtensionField) Type() protoreflect.ExtensionType {
|
|
return f.typ
|
|
}
|
|
|
|
// IsSet returns whether the extension field is set.
|
|
// This may be called concurrently.
|
|
func (f ExtensionField) IsSet() bool {
|
|
return f.typ != nil
|
|
}
|
|
|
|
// IsLazy reports whether a field is lazily encoded.
|
|
// It is exported for testing.
|
|
func IsLazy(m protoreflect.Message, fd protoreflect.FieldDescriptor) bool {
|
|
var mi *MessageInfo
|
|
var p pointer
|
|
switch m := m.(type) {
|
|
case *messageState:
|
|
mi = m.messageInfo()
|
|
p = m.pointer()
|
|
case *messageReflectWrapper:
|
|
mi = m.messageInfo()
|
|
p = m.pointer()
|
|
default:
|
|
return false
|
|
}
|
|
xd, ok := fd.(protoreflect.ExtensionTypeDescriptor)
|
|
if !ok {
|
|
return false
|
|
}
|
|
xt := xd.Type()
|
|
ext := mi.extensionMap(p)
|
|
if ext == nil {
|
|
return false
|
|
}
|
|
f, ok := (*ext)[int32(fd.Number())]
|
|
if !ok {
|
|
return false
|
|
}
|
|
return f.typ == xt && f.lazy != nil && atomic.LoadUint32(&f.lazy.atomicOnce) == 0
|
|
}
|