Skip to content

Commit 2747d96

Browse files
authored
Merge pull request hashicorp#7877 from hashicorp/jbardin/races
core: Fix race conditions, mostly around diffs
2 parents 46b7828 + 074be9a commit 2747d96

14 files changed

Lines changed: 216 additions & 56 deletions

command/hook_count.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func (h *CountHook) PreApply(
4747
}
4848

4949
action := countHookActionChange
50-
if d.Destroy {
50+
if d.GetDestroy() {
5151
action = countHookActionRemove
5252
} else if s.ID == "" {
5353
action = countHookActionAdd

command/hook_ui.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,10 @@ func (h *UiHook) PreApply(
8484
// Get all the attributes that are changing, and sort them. Also
8585
// determine the longest key so that we can align them all.
8686
keyLen := 0
87-
keys := make([]string, 0, len(d.Attributes))
88-
for key, _ := range d.Attributes {
87+
88+
dAttrs := d.CopyAttributes()
89+
keys := make([]string, 0, len(dAttrs))
90+
for key, _ := range dAttrs {
8991
// Skip the ID since we do that specially
9092
if key == "id" {
9193
continue
@@ -100,7 +102,7 @@ func (h *UiHook) PreApply(
100102

101103
// Go through and output each attribute
102104
for _, attrK := range keys {
103-
attrDiff := d.Attributes[attrK]
105+
attrDiff, _ := d.GetAttribute(attrK)
104106

105107
v := attrDiff.New
106108
u := attrDiff.Old

config/raw_config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ func (r *RawConfig) Value() interface{} {
9393
// structure will always successfully decode into its ultimate
9494
// structure using something like mapstructure.
9595
func (r *RawConfig) Config() map[string]interface{} {
96+
r.lock.Lock()
97+
defer r.lock.Unlock()
9698
return r.config
9799
}
98100

helper/resource/wait.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ func Retry(timeout time.Duration, f RetryFunc) error {
2020
MinTimeout: 500 * time.Millisecond,
2121
Refresh: func() (interface{}, string, error) {
2222
rerr := f()
23+
24+
resultErrMu.Lock()
25+
defer resultErrMu.Unlock()
26+
2327
if rerr == nil {
2428
resultErr = nil
2529
return 42, "success", nil
2630
}
2731

28-
resultErrMu.Lock()
29-
defer resultErrMu.Unlock()
3032
resultErr = rerr.Err
3133

3234
if rerr.Retryable {

terraform/diff.go

Lines changed: 116 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"regexp"
99
"sort"
1010
"strings"
11+
"sync"
1112
)
1213

1314
// DiffChangeType is an enum with the kind of changes a diff has planned.
@@ -216,9 +217,9 @@ func (d *ModuleDiff) String() string {
216217

217218
crud := "UPDATE"
218219
switch {
219-
case rdiff.RequiresNew() && (rdiff.Destroy || rdiff.DestroyTainted):
220+
case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()):
220221
crud = "DESTROY/CREATE"
221-
case rdiff.Destroy:
222+
case rdiff.GetDestroy():
222223
crud = "DESTROY"
223224
case rdiff.RequiresNew():
224225
crud = "CREATE"
@@ -230,8 +231,9 @@ func (d *ModuleDiff) String() string {
230231
name))
231232

232233
keyLen := 0
233-
keys := make([]string, 0, len(rdiff.Attributes))
234-
for key, _ := range rdiff.Attributes {
234+
rdiffAttrs := rdiff.CopyAttributes()
235+
keys := make([]string, 0, len(rdiffAttrs))
236+
for key, _ := range rdiffAttrs {
235237
if key == "id" {
236238
continue
237239
}
@@ -244,7 +246,7 @@ func (d *ModuleDiff) String() string {
244246
sort.Strings(keys)
245247

246248
for _, attrK := range keys {
247-
attrDiff := rdiff.Attributes[attrK]
249+
attrDiff, _ := rdiff.GetAttribute(attrK)
248250

249251
v := attrDiff.New
250252
u := attrDiff.Old
@@ -279,6 +281,7 @@ func (d *ModuleDiff) String() string {
279281

280282
// InstanceDiff is the diff of a resource from some state to another.
281283
type InstanceDiff struct {
284+
mu sync.Mutex
282285
Attributes map[string]*ResourceAttrDiff
283286
Destroy bool
284287
DestroyTainted bool
@@ -324,18 +327,22 @@ func (d *InstanceDiff) init() {
324327
}
325328
}
326329

330+
func NewInstanceDiff() *InstanceDiff {
331+
return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)}
332+
}
333+
327334
// ChangeType returns the DiffChangeType represented by the diff
328335
// for this single instance.
329336
func (d *InstanceDiff) ChangeType() DiffChangeType {
330337
if d.Empty() {
331338
return DiffNone
332339
}
333340

334-
if d.RequiresNew() && (d.Destroy || d.DestroyTainted) {
341+
if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) {
335342
return DiffDestroyCreate
336343
}
337344

338-
if d.Destroy {
345+
if d.GetDestroy() {
339346
return DiffDestroy
340347
}
341348

@@ -352,6 +359,8 @@ func (d *InstanceDiff) Empty() bool {
352359
return true
353360
}
354361

362+
d.mu.Lock()
363+
defer d.mu.Unlock()
355364
return !d.Destroy && len(d.Attributes) == 0
356365
}
357366

@@ -366,6 +375,17 @@ func (d *InstanceDiff) RequiresNew() bool {
366375
return false
367376
}
368377

378+
d.mu.Lock()
379+
defer d.mu.Unlock()
380+
381+
return d.requiresNew()
382+
}
383+
384+
func (d *InstanceDiff) requiresNew() bool {
385+
if d == nil {
386+
return false
387+
}
388+
369389
if d.DestroyTainted {
370390
return true
371391
}
@@ -379,24 +399,103 @@ func (d *InstanceDiff) RequiresNew() bool {
379399
return false
380400
}
381401

402+
// These methods are properly locked, for use outside other InstanceDiff
403+
// methods but everywhere else within in the terraform package.
404+
// TODO refactor the locking scheme
405+
func (d *InstanceDiff) SetTainted(b bool) {
406+
d.mu.Lock()
407+
defer d.mu.Unlock()
408+
409+
d.DestroyTainted = b
410+
}
411+
412+
func (d *InstanceDiff) GetDestroyTainted() bool {
413+
d.mu.Lock()
414+
defer d.mu.Unlock()
415+
416+
return d.DestroyTainted
417+
}
418+
419+
func (d *InstanceDiff) SetDestroy(b bool) {
420+
d.mu.Lock()
421+
defer d.mu.Unlock()
422+
423+
d.Destroy = b
424+
}
425+
426+
func (d *InstanceDiff) GetDestroy() bool {
427+
d.mu.Lock()
428+
defer d.mu.Unlock()
429+
430+
return d.Destroy
431+
}
432+
433+
func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) {
434+
d.mu.Lock()
435+
defer d.mu.Unlock()
436+
437+
d.Attributes[key] = attr
438+
}
439+
440+
func (d *InstanceDiff) DelAttribute(key string) {
441+
d.mu.Lock()
442+
defer d.mu.Unlock()
443+
444+
delete(d.Attributes, key)
445+
}
446+
447+
func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) {
448+
d.mu.Lock()
449+
defer d.mu.Unlock()
450+
451+
attr, ok := d.Attributes[key]
452+
return attr, ok
453+
}
454+
func (d *InstanceDiff) GetAttributesLen() int {
455+
d.mu.Lock()
456+
defer d.mu.Unlock()
457+
458+
return len(d.Attributes)
459+
}
460+
461+
// Safely copies the Attributes map
462+
func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff {
463+
d.mu.Lock()
464+
defer d.mu.Unlock()
465+
466+
attrs := make(map[string]*ResourceAttrDiff)
467+
for k, v := range d.Attributes {
468+
attrs[k] = v
469+
}
470+
471+
return attrs
472+
}
473+
382474
// Same checks whether or not two InstanceDiff's are the "same". When
383475
// we say "same", it is not necessarily exactly equal. Instead, it is
384476
// just checking that the same attributes are changing, a destroy
385477
// isn't suddenly happening, etc.
386478
func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
387-
if d == nil && d2 == nil {
479+
// we can safely compare the pointers without a lock
480+
switch {
481+
case d == nil && d2 == nil:
482+
return true, ""
483+
case d == nil || d2 == nil:
484+
return false, "one nil"
485+
case d == d2:
388486
return true, ""
389-
} else if d == nil || d2 == nil {
390-
return false, "both nil"
391487
}
392488

393-
if d.Destroy != d2.Destroy {
489+
d.mu.Lock()
490+
defer d.mu.Unlock()
491+
492+
if d.Destroy != d2.GetDestroy() {
394493
return false, fmt.Sprintf(
395-
"diff: Destroy; old: %t, new: %t", d.Destroy, d2.Destroy)
494+
"diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy())
396495
}
397-
if d.RequiresNew() != d2.RequiresNew() {
496+
if d.requiresNew() != d2.RequiresNew() {
398497
return false, fmt.Sprintf(
399-
"diff RequiresNew; old: %t, new: %t", d.RequiresNew(), d2.RequiresNew())
498+
"diff RequiresNew; old: %t, new: %t", d.requiresNew(), d2.RequiresNew())
400499
}
401500

402501
// Go through the old diff and make sure the new diff has all the
@@ -406,7 +505,7 @@ func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
406505
for k, _ := range d.Attributes {
407506
checkOld[k] = struct{}{}
408507
}
409-
for k, _ := range d2.Attributes {
508+
for k, _ := range d2.CopyAttributes() {
410509
checkNew[k] = struct{}{}
411510
}
412511

@@ -431,7 +530,7 @@ func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
431530
delete(checkOld, k)
432531
delete(checkNew, k)
433532

434-
_, ok := d2.Attributes[k]
533+
_, ok := d2.GetAttribute(k)
435534
if !ok {
436535
// If there's no new attribute, and the old diff expected the attribute
437536
// to be removed, that's just fine.
@@ -483,7 +582,7 @@ func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
483582
// Similarly, in a RequiresNew scenario, a list that shows up in the plan
484583
// diff can disappear from the apply diff, which is calculated from an
485584
// empty state.
486-
if d.RequiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
585+
if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
487586
ok = true
488587
}
489588

terraform/eval_apply.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
3535
}
3636

3737
// Remove any output values from the diff
38-
for k, ad := range diff.Attributes {
38+
for k, ad := range diff.CopyAttributes() {
3939
if ad.Type == DiffAttrOutput {
40-
delete(diff.Attributes, k)
40+
diff.DelAttribute(k)
4141
}
4242
}
4343

@@ -49,7 +49,7 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
4949

5050
// Flag if we're creating a new instance
5151
if n.CreateNew != nil {
52-
*n.CreateNew = state.ID == "" && !diff.Destroy || diff.RequiresNew()
52+
*n.CreateNew = state.ID == "" && !diff.GetDestroy() || diff.RequiresNew()
5353
}
5454

5555
{

terraform/eval_check_prevent_destroy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func (n *EvalCheckPreventDestroy) Eval(ctx EvalContext) (interface{}, error) {
2222
diff := *n.Diff
2323
preventDestroy := n.Resource.Lifecycle.PreventDestroy
2424

25-
if diff.Destroy && preventDestroy {
25+
if diff.GetDestroy() && preventDestroy {
2626
return nil, fmt.Errorf(preventDestroyErrStr, n.Resource.Id())
2727
}
2828

0 commit comments

Comments
 (0)