Files
tasks/common/dynamicconfig/deepcopy.go
David ReissandGitHub 2a38f9584b Dynamic config conversion improvements (#7052)
## What changed?
- Split implementation of "constrained default" settings from "plain
default" settings. This is more code and the diff looks complex, but the
individual paths are both simpler than the mixed version.
- Add conversion cache using a weak map.
- Remove GlobalCachedTypedValue.
- Use "raw" values for subscription dispatch deduping to avoid
unnecessary conversions.
- Deep copy default values when using mapstructure, to avoid problems
with merging over shared default values.

## Why?
- Fixes #6756
- Performance improvement for "plain default" settings (almost all of
them)
- Performance improvement for settings with complex converters
- Remove footgun in defaults that aren't scalar values

## How did you test it?
existing+new unit tests
2025-08-08 08:32:58 -07:00

69 lines
1.9 KiB
Go

package dynamicconfig
import (
"fmt"
"reflect"
)
// deepCopyForMapstructure does a simple deep copy of T. Fancy cases (anything other than plain old data)
// is not handled and will panic.
func deepCopyForMapstructure[T any](t T) T {
// nolint:revive // this will be triggered from a static initializer before it can be triggered from production code
return deepCopyValue(reflect.ValueOf(t)).Interface().(T)
}
func deepCopyValue(v reflect.Value) reflect.Value {
switch v.Kind() {
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.String:
nv := reflect.New(v.Type()).Elem()
nv.Set(v)
return nv
case reflect.Array:
nv := reflect.New(v.Type()).Elem()
for i := range v.Len() {
nv.Index(i).Set(deepCopyValue(v.Index(i)))
}
return nv
case reflect.Map:
if v.IsNil() {
return v
}
nv := reflect.MakeMapWithSize(v.Type(), v.Len())
for i := v.MapRange(); i.Next(); {
nv.SetMapIndex(i.Key(), deepCopyValue(i.Value()))
}
return nv
case reflect.Pointer:
if v.IsNil() {
return v
}
return deepCopyValue(v.Elem()).Addr()
case reflect.Slice:
if v.IsNil() {
return v
}
nv := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
for i := range v.Len() {
nv.Index(i).Set(deepCopyValue(v.Index(i)))
}
return nv
case reflect.Struct:
nv := reflect.New(v.Type()).Elem()
for i := range v.Type().NumField() {
nv.Field(i).Set(deepCopyValue(v.Field(i)))
}
return nv
case reflect.Interface, reflect.Func, reflect.Chan:
// only nil values of any other reference types allowed!
if v.IsNil() {
return v
}
fallthrough
default:
// nolint:forbidigo // this will be triggered from a static initializer before it can be triggered from production code
panic(fmt.Sprintf("Can't deep copy value of type %s: %v", v.Type(), v))
}
}