mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
## What changed? Support gradual config rollouts by key (e.g. task queue partition). ## Why? Rolling out changes slowly, and also mostly-synchronized across processes. ## How did you test it? - [ ] built - [ ] run locally and tested manually - [x] covered by existing tests - [x] added new unit test(s) - [ ] added new functional test(s) ## Potential risks Time-based changes can be risky if you set something for the future and forget about it. We should have validation that prevents changes from being set too far in the future or outside of business hours.
79 lines
2.4 KiB
Go
79 lines
2.4 KiB
Go
package dynamicconfig
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"time"
|
|
)
|
|
|
|
// 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:
|
|
// Special case for time.Time: it has unexported fields so we can't copy it field by
|
|
// field, but we can copy zero values (which is all we need for default values).
|
|
if v.Type() == reflect.TypeFor[time.Time]() {
|
|
if v.Interface().(time.Time).IsZero() {
|
|
return reflect.ValueOf(time.Time{})
|
|
}
|
|
// 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 non-zero time.Time: %v", v.Interface()))
|
|
}
|
|
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))
|
|
}
|
|
}
|