Skip to content

Commit

Permalink
Merge pull request #8452 from hashicorp/jbardin/locked-copy
Browse files Browse the repository at this point in the history
Add locks to state structs for copying
  • Loading branch information
jbardin authored Aug 25, 2016
2 parents fc67b63 + 4559eec commit f326dad
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 77 deletions.
119 changes: 73 additions & 46 deletions terraform/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"sort"
"strconv"
"strings"
"sync"

"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform/config"
Expand Down Expand Up @@ -78,8 +79,13 @@ type State struct {

// Modules contains all the modules in a breadth-first order
Modules []*ModuleState `json:"modules"`

mu sync.Mutex
}

func (s *State) Lock() { s.mu.Lock() }
func (s *State) Unlock() { s.mu.Unlock() }

// NewState is used to initialize a blank state
func NewState() *State {
s := &State{}
Expand Down Expand Up @@ -463,7 +469,7 @@ func (s *State) SameLineage(other *State) bool {
// DeepCopy performs a deep copy of the state structure and returns
// a new structure.
func (s *State) DeepCopy() *State {
copy, err := copystructure.Copy(s)
copy, err := copystructure.Config{Lock: true}.Copy(s)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -573,10 +579,6 @@ func (s *State) sort() {
}
}

func (s *State) GoString() string {
return fmt.Sprintf("*%#v", *s)
}

func (s *State) String() string {
if s == nil {
return "<nil>"
Expand Down Expand Up @@ -617,15 +619,26 @@ type RemoteState struct {
// Config is used to store arbitrary configuration that
// is type specific
Config map[string]string `json:"config"`

mu sync.Mutex
}

func (s *RemoteState) Lock() { s.mu.Lock() }
func (s *RemoteState) Unlock() { s.mu.Unlock() }

func (r *RemoteState) init() {
r.Lock()
defer r.Unlock()

if r.Config == nil {
r.Config = make(map[string]string)
}
}

func (r *RemoteState) deepcopy() *RemoteState {
r.Lock()
defer r.Unlock()

confCopy := make(map[string]string, len(r.Config))
for k, v := range r.Config {
confCopy[k] = v
Expand Down Expand Up @@ -655,10 +668,6 @@ func (r *RemoteState) Equals(other *RemoteState) bool {
return true
}

func (r *RemoteState) GoString() string {
return fmt.Sprintf("*%#v", *r)
}

// OutputState is used to track the state relevant to a single output.
type OutputState struct {
// Sensitive describes whether the output is considered sensitive,
Expand All @@ -670,8 +679,13 @@ type OutputState struct {
// Value contains the value of the output, in the structure described
// by the Type field.
Value interface{} `json:"value"`

mu sync.Mutex
}

func (s *OutputState) Lock() { s.mu.Lock() }
func (s *OutputState) Unlock() { s.mu.Unlock() }

func (s *OutputState) String() string {
return fmt.Sprintf("%#v", s.Value)
}
Expand Down Expand Up @@ -707,18 +721,12 @@ func (s *OutputState) deepcopy() *OutputState {
return nil
}

valueCopy, err := copystructure.Copy(s.Value)
stateCopy, err := copystructure.Config{Lock: true}.Copy(s)
if err != nil {
panic(fmt.Errorf("Error copying output value: %s", err))
}

n := &OutputState{
Type: s.Type,
Sensitive: s.Sensitive,
Value: valueCopy,
}

return n
return stateCopy.(*OutputState)
}

// ModuleState is used to track all the state relevant to a single
Expand Down Expand Up @@ -753,8 +761,13 @@ type ModuleState struct {
// overall state, then it assumes it isn't managed and doesn't
// worry about it.
Dependencies []string `json:"depends_on"`

mu sync.Mutex
}

func (s *ModuleState) Lock() { s.mu.Lock() }
func (s *ModuleState) Unlock() { s.mu.Unlock() }

// Equal tests whether one module state is equal to another.
func (m *ModuleState) Equal(other *ModuleState) bool {
// Paths must be equal
Expand Down Expand Up @@ -862,6 +875,9 @@ func (m *ModuleState) View(id string) *ModuleState {
}

func (m *ModuleState) init() {
m.Lock()
defer m.Unlock()

if m.Path == nil {
m.Path = []string{}
}
Expand All @@ -885,21 +901,13 @@ func (m *ModuleState) deepcopy() *ModuleState {
if m == nil {
return nil
}
n := &ModuleState{
Path: make([]string, len(m.Path)),
Outputs: make(map[string]*OutputState, len(m.Outputs)),
Resources: make(map[string]*ResourceState, len(m.Resources)),
Dependencies: make([]string, len(m.Dependencies)),
}
copy(n.Path, m.Path)
copy(n.Dependencies, m.Dependencies)
for k, v := range m.Outputs {
n.Outputs[k] = v.deepcopy()
}
for k, v := range m.Resources {
n.Resources[k] = v.deepcopy()

stateCopy, err := copystructure.Config{Lock: true}.Copy(m)
if err != nil {
panic(err)
}
return n

return stateCopy.(*ModuleState)
}

// prune is used to remove any resources that are no longer required
Expand All @@ -925,10 +933,6 @@ func (m *ModuleState) sort() {
}
}

func (m *ModuleState) GoString() string {
return fmt.Sprintf("*%#v", *m)
}

func (m *ModuleState) String() string {
var buf bytes.Buffer

Expand Down Expand Up @@ -1176,8 +1180,13 @@ type ResourceState struct {
// e.g. "aws_instance" goes with the "aws" provider.
// If the resource block contained a "provider" key, that value will be set here.
Provider string `json:"provider"`

mu sync.Mutex
}

func (s *ResourceState) Lock() { s.mu.Lock() }
func (s *ResourceState) Unlock() { s.mu.Unlock() }

// Equal tests whether two ResourceStates are equal.
func (s *ResourceState) Equal(other *ResourceState) bool {
if s.Type != other.Type {
Expand Down Expand Up @@ -1223,6 +1232,9 @@ func (r *ResourceState) Untaint() {
}

func (r *ResourceState) init() {
r.Lock()
defer r.Unlock()

if r.Primary == nil {
r.Primary = &InstanceState{}
}
Expand All @@ -1242,7 +1254,7 @@ func (r *ResourceState) init() {
}

func (r *ResourceState) deepcopy() *ResourceState {
copy, err := copystructure.Copy(r)
copy, err := copystructure.Config{Lock: true}.Copy(r)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -1270,10 +1282,6 @@ func (r *ResourceState) sort() {
sort.Strings(r.Dependencies)
}

func (s *ResourceState) GoString() string {
return fmt.Sprintf("*%#v", *s)
}

func (s *ResourceState) String() string {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("Type = %s", s.Type))
Expand Down Expand Up @@ -1304,9 +1312,17 @@ type InstanceState struct {

// Tainted is used to mark a resource for recreation.
Tainted bool `json:"tainted"`

mu sync.Mutex
}

func (s *InstanceState) Lock() { s.mu.Lock() }
func (s *InstanceState) Unlock() { s.mu.Unlock() }

func (i *InstanceState) init() {
i.Lock()
defer i.Unlock()

if i.Attributes == nil {
i.Attributes = make(map[string]string)
}
Expand All @@ -1316,8 +1332,23 @@ func (i *InstanceState) init() {
i.Ephemeral.init()
}

// Copy all the Fields from another InstanceState
func (i *InstanceState) Set(from *InstanceState) {
i.Lock()
defer i.Unlock()

from.Lock()
defer from.Unlock()

i.ID = from.ID
i.Attributes = from.Attributes
i.Ephemeral = from.Ephemeral
i.Meta = from.Meta
i.Tainted = from.Tainted
}

func (i *InstanceState) DeepCopy() *InstanceState {
copy, err := copystructure.Copy(i)
copy, err := copystructure.Config{Lock: true}.Copy(i)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -1415,10 +1446,6 @@ func (s *InstanceState) MergeDiff(d *InstanceDiff) *InstanceState {
return result
}

func (i *InstanceState) GoString() string {
return fmt.Sprintf("*%#v", *i)
}

func (i *InstanceState) String() string {
var buf bytes.Buffer

Expand Down Expand Up @@ -1470,7 +1497,7 @@ func (e *EphemeralState) init() {
}

func (e *EphemeralState) DeepCopy() *EphemeralState {
copy, err := copystructure.Copy(e)
copy, err := copystructure.Config{Lock: true}.Copy(e)
if err != nil {
panic(err)
}
Expand Down
20 changes: 10 additions & 10 deletions terraform/state_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ import (
//
// The full semantics of Add:
//
// ┌───────────────────────┬───────────────────────┬───────────────────────┐
// Module Address Resource Address Instance Address
// ┌───────────────────────┼───────────────────────┼───────────────────────┼───────────────────────┤
// ModuleState x x
// ├───────────────────────┼───────────────────────┼───────────────────────┼───────────────────────┤
// ResourceState maybe*
// ├───────────────────────┼───────────────────────┼───────────────────────┼───────────────────────┤
// Instance State
// └───────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┘
// ┌───────────────────┬──────────────────────────────────────┐
// │ Module Address Resource Address Instance Address │
// ┌─────────────────┼───────────────────┼──────────────────────────────────────┤
// ModuleState │ ✓ │ x │ x
// ├─────────────────┼───────────────────┼──────────────────────────────────────┤
// ResourceState │ ✓ │ ✓ │ maybe* │
// ├─────────────────┼───────────────────┼──────────────────────────────────────┤
// Instance State │ ✓ │ ✓ │
// └─────────────────┴───────────────────┴──────────────────────────────────────┘
//
// *maybe - Resources can be added at an instance address only if the resource
// represents a single instance (primary). Example:
Expand Down Expand Up @@ -219,7 +219,7 @@ func stateAddFunc_Instance_Instance(s *State, fromAddr, addr *ResourceAddress, r
instance := instanceRaw.(*InstanceState)

// Set it
*instance = *src
instance.Set(src)

return nil
}
Expand Down
Loading

0 comments on commit f326dad

Please sign in to comment.