update github.com/mitchellh/copystructure

This add the locked copy feature, and also fixed #8100
This commit is contained in:
James Bardin 2016-08-25 16:53:41 -04:00
parent 0e6e206465
commit c047bc63d8
2 changed files with 132 additions and 21 deletions

View File

@ -2,27 +2,14 @@ package copystructure
import (
"reflect"
"sync"
"github.com/mitchellh/reflectwalk"
)
// Copy returns a deep copy of v.
func Copy(v interface{}) (interface{}, error) {
w := new(walker)
err := reflectwalk.Walk(v, w)
if err != nil {
return nil, err
}
// Get the result. If the result is nil, then we want to turn it
// into a typed nil if we can.
result := w.Result
if result == nil {
val := reflect.ValueOf(v)
result = reflect.Indirect(reflect.New(val.Type())).Interface()
}
return result, nil
return Config{}.Copy(v)
}
// CopierFunc is a function that knows how to deep copy a specific type.
@ -40,6 +27,42 @@ type CopierFunc func(interface{}) (interface{}, error)
// this map as well as to Copy in a mutex.
var Copiers map[reflect.Type]CopierFunc = make(map[reflect.Type]CopierFunc)
type Config struct {
// Lock any types that are a sync.Locker and are not a mutex while copying.
// If there is an RLocker method, use that to get the sync.Locker.
Lock bool
// Copiers is a map of types associated with a CopierFunc. Use the global
// Copiers map if this is nil.
Copiers map[reflect.Type]CopierFunc
}
func (c Config) Copy(v interface{}) (interface{}, error) {
w := new(walker)
if c.Lock {
w.useLocks = true
}
if c.Copiers == nil {
c.Copiers = Copiers
}
err := reflectwalk.Walk(v, w)
if err != nil {
return nil, err
}
// Get the result. If the result is nil, then we want to turn it
// into a typed nil if we can.
result := w.Result
if result == nil {
val := reflect.ValueOf(v)
result = reflect.Indirect(reflect.New(val.Type())).Interface()
}
return result, nil
}
type walker struct {
Result interface{}
@ -48,14 +71,31 @@ type walker struct {
vals []reflect.Value
cs []reflect.Value
ps []bool
// any locks we've taken, indexed by depth
locks []sync.Locker
// take locks while walking the structure
useLocks bool
}
func (w *walker) Enter(l reflectwalk.Location) error {
w.depth++
// ensure we have enough elements to index via w.depth
for w.depth >= len(w.locks) {
w.locks = append(w.locks, nil)
}
return nil
}
func (w *walker) Exit(l reflectwalk.Location) error {
locker := w.locks[w.depth]
w.locks[w.depth] = nil
if locker != nil {
defer locker.Unlock()
}
w.depth--
if w.ignoreDepth > w.depth {
w.ignoreDepth = 0
@ -76,13 +116,25 @@ func (w *walker) Exit(l reflectwalk.Location) error {
mv := w.valPop()
mk := w.valPop()
m := w.cs[len(w.cs)-1]
// If mv is the zero value, SetMapIndex deletes the key form the map,
// or in this case never adds it. We need to create a properly typed
// zero value so that this key can be set.
if !mv.IsValid() {
mv = reflect.Zero(m.Type().Elem())
}
m.SetMapIndex(mk, mv)
case reflectwalk.SliceElem:
// Pop off the value and the index and set it on the slice
v := w.valPop()
i := w.valPop().Interface().(int)
s := w.cs[len(w.cs)-1]
s.Index(i).Set(v)
if v.IsValid() {
i := w.valPop().Interface().(int)
s := w.cs[len(w.cs)-1]
se := s.Index(i)
if se.CanSet() {
se.Set(v)
}
}
case reflectwalk.Struct:
w.replacePointerMaybe()
@ -112,6 +164,7 @@ func (w *walker) Map(m reflect.Value) error {
if w.ignoring() {
return nil
}
w.lock(m)
// Create the map. If the map itself is nil, then just make a nil map
var newMap reflect.Value
@ -152,6 +205,7 @@ func (w *walker) Primitive(v reflect.Value) error {
if w.ignoring() {
return nil
}
w.lock(v)
// IsValid verifies the v is non-zero and CanInterface verifies
// that we're allowed to read this value (unexported fields).
@ -170,6 +224,7 @@ func (w *walker) Slice(s reflect.Value) error {
if w.ignoring() {
return nil
}
w.lock(s)
var newS reflect.Value
if s.IsNil() {
@ -199,6 +254,7 @@ func (w *walker) Struct(s reflect.Value) error {
if w.ignoring() {
return nil
}
w.lock(s)
var v reflect.Value
if c, ok := Copiers[s.Type()]; ok {
@ -277,3 +333,58 @@ func (w *walker) replacePointerMaybe() {
w.valPush(reflect.Indirect(w.valPop()))
}
}
// if this value is a Locker, lock it and add it to the locks slice
func (w *walker) lock(v reflect.Value) {
if !w.useLocks {
return
}
if !v.IsValid() || !v.CanInterface() {
return
}
type rlocker interface {
RLocker() sync.Locker
}
var locker sync.Locker
// first check if we can get a locker from the value
switch l := v.Interface().(type) {
case rlocker:
// don't lock a mutex directly
if _, ok := l.(*sync.RWMutex); !ok {
locker = l.RLocker()
}
case sync.Locker:
locker = l
}
// the value itself isn't a locker, so check the method on a pointer too
if locker == nil && v.CanAddr() {
switch l := v.Addr().Interface().(type) {
case rlocker:
// don't lock a mutex directly
if _, ok := l.(*sync.RWMutex); !ok {
locker = l.RLocker()
}
case sync.Locker:
locker = l
}
}
// still no callable locker
if locker == nil {
return
}
// don't lock a mutex directly
switch locker.(type) {
case *sync.Mutex, *sync.RWMutex:
return
}
locker.Lock()
w.locks[w.depth] = locker
}

6
vendor/vendor.json vendored
View File

@ -1436,10 +1436,10 @@
"revision": "8631ce90f28644f54aeedcb3e389a85174e067d1"
},
{
"checksumSHA1": "86nE93o1VIND0Doe8PuhCXnhUx0=",
"checksumSHA1": "y2HsVMt3eYGqywv5ljijnywJMb4=",
"path": "github.com/mitchellh/copystructure",
"revision": "cdac8253d00f2ecf0a0b19fbff173a9a72de4f82",
"revisionTime": "2016-08-04T03:23:30Z"
"revision": "6871c41ca9148d368715dedcda473f396f205df5",
"revisionTime": "2016-08-25T20:45:07Z"
},
{
"path": "github.com/mitchellh/go-homedir",