Skip to content

Commit 696b8b7

Browse files
Merge pull request #29 from form3tech-oss/mike-interaction-mutex
Refactor interaction mutex to be simpler and more consistent
2 parents 94ab8c6 + 72adeee commit 696b8b7

File tree

4 files changed

+73
-49
lines changed

4 files changed

+73
-49
lines changed

internal/app/pactproxy/interaction.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"regexp"
99
"strings"
1010
"sync"
11-
"sync/atomic"
1211

1312
log "github.com/sirupsen/logrus"
1413

@@ -42,15 +41,16 @@ func (m *regexPathMatcher) match(val string) bool {
4241
}
4342

4443
type interaction struct {
44+
mu sync.RWMutex
4545
pathMatcher pathMatcher
4646
method string
4747
Alias string
4848
Description string
4949
definition map[string]interface{}
50-
constraints sync.Map
50+
constraints map[string]interactionConstraint
5151
Modifiers *interactionModifiers
52-
lastRequest atomic.Value
53-
requestCount int32
52+
lastRequest requestDocument
53+
requestCount int
5454
}
5555

5656
func LoadInteraction(data []byte, alias string) (*interaction, error) {
@@ -95,11 +95,12 @@ func LoadInteraction(data []byte, alias string) (*interaction, error) {
9595
Alias: alias,
9696
definition: definition,
9797
Description: description,
98+
constraints: map[string]interactionConstraint{},
9899
}
99100

100101
interaction.Modifiers = &interactionModifiers{
101102
interaction: interaction,
102-
modifiers: sync.Map{},
103+
modifiers: map[string]*interactionModifier{},
103104
}
104105

105106
requestBody, ok := request["body"]
@@ -296,7 +297,9 @@ func (i *interaction) Match(path, method string) bool {
296297
}
297298

298299
func (i *interaction) AddConstraint(constraint interactionConstraint) {
299-
i.constraints.Store(constraint.Key(), constraint)
300+
i.mu.Lock()
301+
defer i.mu.Unlock()
302+
i.constraints[constraint.Key()] = constraint
300303
}
301304

302305
func (i *interaction) loadValuesFromSource(constraint interactionConstraint, interactions *Interactions) ([]interface{}, error) {
@@ -306,8 +309,10 @@ func (i *interaction) loadValuesFromSource(constraint interactionConstraint, int
306309
return nil, errors.Errorf("cannot find source interaction '%s' for constraint", constraint.Source)
307310
}
308311

309-
sourceRequest, ok := sourceInteraction.lastRequest.Load().(requestDocument)
310-
if !ok {
312+
i.mu.RLock()
313+
sourceRequest := sourceInteraction.lastRequest
314+
i.mu.RUnlock()
315+
if len(sourceRequest) == 0 {
311316
return nil, errors.Errorf("source interaction '%s' as no requests", constraint.Source)
312317
}
313318

@@ -322,16 +327,17 @@ func (i *interaction) EvaluateConstrains(request requestDocument, interactions *
322327
result := true
323328
violations := make([]string, 0)
324329

325-
i.constraints.Range(func(_, v interface{}) bool {
326-
constraint := v.(interactionConstraint)
330+
i.mu.RLock()
331+
defer i.mu.RUnlock()
332+
for _, constraint := range i.constraints {
327333
values := constraint.Values
328334
if constraint.Source != "" {
329335
var err error
330336
values, err = i.loadValuesFromSource(constraint, interactions)
331337
if err != nil {
332338
violations = append(violations, err.Error())
333339
result = false
334-
return true
340+
continue
335341
}
336342
}
337343

@@ -342,7 +348,7 @@ func (i *interaction) EvaluateConstrains(request requestDocument, interactions *
342348
}
343349
if reflect.TypeOf(val) == reflect.TypeOf([]interface{}{}) {
344350
log.Infof("skipping matching on interface{} type for path '%s'", constraint.Path)
345-
return true
351+
continue
346352
}
347353
if err == nil {
348354
actual = fmt.Sprintf("%v", val)
@@ -353,18 +359,24 @@ func (i *interaction) EvaluateConstrains(request requestDocument, interactions *
353359
violations = append(violations, fmt.Sprintf("value '%s' at path '%s' does not match constraint '%s'", actual, constraint.Path, expected))
354360
result = false
355361
}
356-
357-
return true
358-
})
362+
}
359363

360364
return result, violations
361365
}
362366

363367
func (i *interaction) StoreRequest(request requestDocument) {
364-
i.lastRequest.Store(request)
365-
atomic.AddInt32(&i.requestCount, 1)
368+
i.mu.Lock()
369+
defer i.mu.Unlock()
370+
i.lastRequest = request
371+
i.requestCount++
366372
}
367373

368374
func (i *interaction) HasRequests(count int) bool {
369-
return atomic.LoadInt32(&i.requestCount) >= int32(count)
375+
return i.getRequestCount() >= count
376+
}
377+
378+
func (i *interaction) getRequestCount() int {
379+
i.mu.RLock()
380+
defer i.mu.RUnlock()
381+
return i.requestCount
370382
}

internal/app/pactproxy/interaction_test.go

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ package pactproxy
22

33
import (
44
"encoding/json"
5+
"testing"
6+
57
"github.com/stretchr/testify/assert"
68
"github.com/stretchr/testify/require"
7-
"testing"
89
)
910

1011
func TestLoadInteractionPlainTextConstraints(t *testing.T) {
@@ -104,18 +105,17 @@ func TestLoadInteractionPlainTextConstraints(t *testing.T) {
104105
}
105106
for _, tt := range tests {
106107
t.Run(tt.name, func(t *testing.T) {
107-
got, err := LoadInteraction(tt.interaction, "alias")
108+
interaction, err := LoadInteraction(tt.interaction, "alias")
108109

109110
require.Equalf(t, tt.wantErr, err != nil, "error %v", err)
110111

111-
var gotConstraint interactionConstraint
112-
got.constraints.Range(func(key, value interface{}) bool {
113-
var present bool
114-
gotConstraint, present = value.(interactionConstraint)
115-
return present
116-
})
112+
var foundConstraint interactionConstraint
113+
for _, constraint := range interaction.constraints {
114+
foundConstraint = constraint
115+
break
116+
}
117117

118-
assert.EqualValues(t, tt.wantConstraint, gotConstraint)
118+
assert.EqualValues(t, tt.wantConstraint, foundConstraint)
119119
})
120120
}
121121
}
@@ -239,18 +239,17 @@ func TestV3MatchingRulesLeadToCorrectConstraints(t *testing.T) {
239239
}
240240
for _, tt := range tests {
241241
t.Run(tt.name, func(t *testing.T) {
242-
got, err := LoadInteraction(tt.interaction, "alias")
242+
interaction, err := LoadInteraction(tt.interaction, "alias")
243243

244244
require.Equalf(t, tt.wantErr, err != nil, "error %v", err)
245245

246-
var gotConstraint interactionConstraint
247-
got.constraints.Range(func(key, value interface{}) bool {
248-
var present bool
249-
gotConstraint, present = value.(interactionConstraint)
250-
return present
251-
})
246+
var foundConstraint interactionConstraint
247+
for _, constraint := range interaction.constraints {
248+
foundConstraint = constraint
249+
break
250+
}
252251

253-
assert.EqualValues(t, tt.wantConstraint, gotConstraint)
252+
assert.EqualValues(t, tt.wantConstraint, foundConstraint)
254253
})
255254
}
256255
}

internal/app/pactproxy/modifier.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import (
44
"fmt"
55
"strconv"
66
"strings"
7-
"sync"
8-
"sync/atomic"
97

108
"github.com/tidwall/sjson"
119
)
@@ -19,30 +17,34 @@ type interactionModifier struct {
1917

2018
type interactionModifiers struct {
2119
interaction *interaction
22-
modifiers sync.Map
20+
modifiers map[string]*interactionModifier
2321
}
2422

2523
func (im *interactionModifier) Key() string {
2624
return strings.Join([]string{im.Interaction, im.Path}, "_")
2725
}
2826

2927
func (ims *interactionModifiers) AddModifier(modifier *interactionModifier) {
30-
ims.modifiers.Store(modifier.Key(), modifier)
28+
ims.interaction.mu.Lock()
29+
defer ims.interaction.mu.Unlock()
30+
ims.modifiers[modifier.Key()] = modifier
3131
}
3232

3333
func (ims *interactionModifiers) Modifiers() []*interactionModifier {
3434
var result []*interactionModifier
35-
ims.modifiers.Range(func(_, v interface{}) bool {
36-
result = append(result, v.(*interactionModifier))
37-
return true
38-
})
35+
ims.interaction.mu.RLock()
36+
defer ims.interaction.mu.RUnlock()
37+
for _, modifier := range ims.modifiers {
38+
result = append(result, modifier)
39+
}
3940
return result
4041
}
4142

4243
func (ims *interactionModifiers) modifyBody(b []byte) ([]byte, error) {
4344
for _, m := range ims.Modifiers() {
45+
requestCount := ims.interaction.getRequestCount()
4446
if strings.HasPrefix(m.Path, "$.body.") {
45-
if m.Attempt == nil || *m.Attempt == int(atomic.LoadInt32(&ims.interaction.requestCount)) {
47+
if m.Attempt == nil || *m.Attempt == requestCount {
4648
var err error
4749
b, err = sjson.SetBytes(b, m.Path[7:], m.Value)
4850
if err != nil {
@@ -56,8 +58,9 @@ func (ims *interactionModifiers) modifyBody(b []byte) ([]byte, error) {
5658

5759
func (ims *interactionModifiers) modifyStatusCode() (bool, int) {
5860
for _, m := range ims.Modifiers() {
61+
requestCount := ims.interaction.getRequestCount()
5962
if m.Path == "$.status" {
60-
if m.Attempt == nil || *m.Attempt == int(atomic.LoadInt32(&ims.interaction.requestCount)) {
63+
if m.Attempt == nil || *m.Attempt == requestCount {
6164
code, err := strconv.Atoi(fmt.Sprintf("%v", m.Value))
6265
if err == nil {
6366
return true, code

internal/app/pactproxy/proxy_test.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,7 @@ func TestInteractionsWaitHandler(t *testing.T) {
4646
name: "timing out existing interaction",
4747
interactions: func() *Interactions {
4848
interactions := Interactions{}
49-
interactions.Store(&interaction{
50-
Alias: "existing",
51-
Description: "Existing",
52-
})
49+
interactions.Store(newInteraction("existing"))
5350
return &interactions
5451
}(),
5552
req: func() *http.Request {
@@ -72,3 +69,16 @@ func TestInteractionsWaitHandler(t *testing.T) {
7269
})
7370
}
7471
}
72+
73+
func newInteraction(alias string) *interaction {
74+
i := &interaction{
75+
Alias: alias,
76+
Description: alias,
77+
constraints: map[string]interactionConstraint{},
78+
}
79+
i.Modifiers = &interactionModifiers{
80+
interaction: i,
81+
modifiers: map[string]*interactionModifier{},
82+
}
83+
return i
84+
}

0 commit comments

Comments
 (0)