Skip to content

Commit 0bcd04a

Browse files
authored
Merge pull request #30958 from hashicorp/jbardin/dependencies-race
Fix race in state dependency encoding
2 parents 7e37d48 + 2bd7291 commit 0bcd04a

File tree

3 files changed

+59
-21
lines changed

3 files changed

+59
-21
lines changed

.github/workflows/checks.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ jobs:
9696
# it for select packages.
9797
- name: "Race detector"
9898
run: |
99-
go test -race ./internal/terraform ./internal/command
99+
go test -race ./internal/terraform ./internal/command ./internal/states
100100
101101
e2e-tests:
102102
# This is an intentionally-limited form of our E2E test run which only

internal/states/instance_object.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,20 @@ func (o *ResourceInstanceObject) Encode(ty cty.Type, schemaVersion uint64) (*Res
115115
// stored in state as an array. To avoid pointless thrashing of state in
116116
// refresh-only runs, we can either override comparison of dependency lists
117117
// (more desirable, but tricky for Reasons) or just sort when encoding.
118-
sort.Slice(o.Dependencies, func(i, j int) bool { return o.Dependencies[i].String() < o.Dependencies[j].String() })
118+
// Encoding of instances can happen concurrently, so we must copy the
119+
// dependencies to avoid mutating what may be a shared array of values.
120+
dependencies := make([]addrs.ConfigResource, len(o.Dependencies))
121+
copy(dependencies, o.Dependencies)
122+
123+
sort.Slice(dependencies, func(i, j int) bool { return dependencies[i].String() < dependencies[j].String() })
119124

120125
return &ResourceInstanceObjectSrc{
121126
SchemaVersion: schemaVersion,
122127
AttrsJSON: src,
123128
AttrSensitivePaths: pvm,
124129
Private: o.Private,
125130
Status: o.Status,
126-
Dependencies: o.Dependencies,
131+
Dependencies: dependencies,
127132
CreateBeforeDestroy: o.CreateBeforeDestroy,
128133
}, nil
129134
}
Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package states
22

33
import (
4+
"sync"
45
"testing"
56

67
"github.com/google/go-cmp/cmp"
@@ -24,27 +25,59 @@ func TestResourceInstanceObject_encode(t *testing.T) {
2425
addrs.RootModule.Resource(addrs.ManagedResourceMode, "test", "boop"),
2526
addrs.RootModule.Resource(addrs.ManagedResourceMode, "test", "honk"),
2627
}
27-
rioOne := &ResourceInstanceObject{
28-
Value: value,
29-
Status: ObjectPlanned,
30-
Dependencies: depsOne,
31-
}
32-
rioTwo := &ResourceInstanceObject{
33-
Value: value,
34-
Status: ObjectPlanned,
35-
Dependencies: depsTwo,
36-
}
37-
riosOne, err := rioOne.Encode(value.Type(), 0)
38-
if err != nil {
39-
t.Fatalf("unexpected error: %s", err)
28+
29+
// multiple instances may have been assigned the same deps slice
30+
objs := []*ResourceInstanceObject{
31+
&ResourceInstanceObject{
32+
Value: value,
33+
Status: ObjectPlanned,
34+
Dependencies: depsOne,
35+
},
36+
&ResourceInstanceObject{
37+
Value: value,
38+
Status: ObjectPlanned,
39+
Dependencies: depsTwo,
40+
},
41+
&ResourceInstanceObject{
42+
Value: value,
43+
Status: ObjectPlanned,
44+
Dependencies: depsOne,
45+
},
46+
&ResourceInstanceObject{
47+
Value: value,
48+
Status: ObjectPlanned,
49+
Dependencies: depsOne,
50+
},
4051
}
41-
riosTwo, err := rioTwo.Encode(value.Type(), 0)
42-
if err != nil {
43-
t.Fatalf("unexpected error: %s", err)
52+
53+
var encoded []*ResourceInstanceObjectSrc
54+
55+
// Encoding can happen concurrently, so we need to make sure the shared
56+
// Dependencies are safely handled
57+
var wg sync.WaitGroup
58+
var mu sync.Mutex
59+
60+
for _, obj := range objs {
61+
obj := obj
62+
wg.Add(1)
63+
go func() {
64+
defer wg.Done()
65+
rios, err := obj.Encode(value.Type(), 0)
66+
if err != nil {
67+
t.Errorf("unexpected error: %s", err)
68+
}
69+
mu.Lock()
70+
encoded = append(encoded, rios)
71+
mu.Unlock()
72+
}()
4473
}
74+
wg.Wait()
75+
4576
// However, identical sets of dependencies should always be written to state
4677
// in an identical order, so we don't do meaningless state updates on refresh.
47-
if diff := cmp.Diff(riosOne.Dependencies, riosTwo.Dependencies); diff != "" {
48-
t.Errorf("identical dependencies got encoded in different orders:\n%s", diff)
78+
for i := 0; i < len(encoded)-1; i++ {
79+
if diff := cmp.Diff(encoded[i].Dependencies, encoded[i+1].Dependencies); diff != "" {
80+
t.Errorf("identical dependencies got encoded in different orders:\n%s", diff)
81+
}
4982
}
5083
}

0 commit comments

Comments
 (0)