Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(solver) resolve a data race with ops counters #381

Merged
merged 1 commit into from
May 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,14 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism,
stats, errs := solver.Solve(ctx, s, wsClient, nil, parallelism, dry)
printFn := color.New(color.FgGreen, color.Bold).PrintfFunc()
printFn("Summary:\n")
printFn(" Created: %v\n", stats.CreateOps)
printFn(" Updated: %v\n", stats.UpdateOps)
printFn(" Deleted: %v\n", stats.DeleteOps)
printFn(" Created: %v\n", stats.CreateOps.Count())
printFn(" Updated: %v\n", stats.UpdateOps.Count())
printFn(" Deleted: %v\n", stats.DeleteOps.Count())
if errs != nil {
return utils.ErrArray{Errors: errs}
}
if diffCmdNonZeroExitCode &&
stats.CreateOps+stats.UpdateOps+stats.DeleteOps != 0 {
stats.CreateOps.Count()+stats.UpdateOps.Count()+stats.DeleteOps.Count() != 0 {
os.Exit(exitCodeDiffDetection)
}
return nil
Expand Down
8 changes: 4 additions & 4 deletions cmd/common_konnect.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,14 @@ func syncKonnect(ctx context.Context,
stats, errs := solver.Solve(ctx, s, kongClient, konnectClient, parallelism, dry)
printFn := color.New(color.FgGreen, color.Bold).PrintfFunc()
printFn("Summary:\n")
printFn(" Created: %v\n", stats.CreateOps)
printFn(" Updated: %v\n", stats.UpdateOps)
printFn(" Deleted: %v\n", stats.DeleteOps)
printFn(" Created: %v\n", stats.CreateOps.Count())
printFn(" Updated: %v\n", stats.UpdateOps.Count())
printFn(" Deleted: %v\n", stats.DeleteOps.Count())
if errs != nil {
return utils.ErrArray{Errors: errs}
}
if diffCmdNonZeroExitCode &&
stats.CreateOps+stats.UpdateOps+stats.DeleteOps != 0 {
stats.CreateOps.Count()+stats.UpdateOps.Count()+stats.DeleteOps.Count() != 0 {
os.Exit(exitCodeDiffDetection)
}

Expand Down
36 changes: 29 additions & 7 deletions solver/solver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package solver

import (
"context"
"sync"

"github.com/kong/deck/crud"
"github.com/kong/deck/diff"
Expand All @@ -14,9 +15,26 @@ import (

// Stats holds the stats related to a Solve.
type Stats struct {
CreateOps int
UpdateOps int
DeleteOps int
CreateOps *AtomicInt32Counter
UpdateOps *AtomicInt32Counter
DeleteOps *AtomicInt32Counter
}

type AtomicInt32Counter struct {
counter int32
lock sync.RWMutex
}

func (a *AtomicInt32Counter) Increment(delta int32) {
a.lock.Lock()
defer a.lock.Unlock()
a.counter += delta
}

func (a *AtomicInt32Counter) Count() int32 {
a.lock.RLock()
defer a.lock.RUnlock()
return a.counter
}

// Solve generates a diff and walks the graph.
Expand All @@ -26,15 +44,19 @@ func Solve(ctx context.Context, syncer *diff.Syncer,

r := buildRegistry(client, konnectClient)

var stats Stats
stats := Stats{
CreateOps: &AtomicInt32Counter{},
UpdateOps: &AtomicInt32Counter{},
DeleteOps: &AtomicInt32Counter{},
}
recordOp := func(op crud.Op) {
switch op {
case crud.Create:
stats.CreateOps = stats.CreateOps + 1
stats.CreateOps.Increment(1)
case crud.Update:
stats.UpdateOps = stats.UpdateOps + 1
stats.UpdateOps.Increment(1)
case crud.Delete:
stats.DeleteOps = stats.DeleteOps + 1
stats.DeleteOps.Increment(1)
}
}

Expand Down
23 changes: 23 additions & 0 deletions solver/solver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package solver

import (
"sync"
"testing"

"github.com/stretchr/testify/assert"
)

func TestAtomicInt32Counter(t *testing.T) {
var a AtomicInt32Counter
var wg sync.WaitGroup

wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
a.Increment(int32(1))
}()
}
Comment on lines +15 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: too see any meaningful risk of race condition occuring, you probably need each of the goroutines to increment the counter 1000 times in a loop.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really to trigger a data race bug with go test -race.
If we disable locking, this reliably produces the race:

go test -race
==================
WARNING: DATA RACE
Read at 0x00c000156240 by goroutine 9:
  github.com/kong/deck/solver.(*AtomicInt32Counter).Increment()
      /home/hbagdi/deck/solver/solver.go:31 +0x6f
  github.com/kong/deck/solver.TestStats_CreateOps.func1()
      /home/hbagdi/deck/solver/solver_test.go:18 +0x66

Previous write at 0x00c000156240 by goroutine 8:
  github.com/kong/deck/solver.(*AtomicInt32Counter).Increment()
      /home/hbagdi/deck/solver/solver.go:31 +0x84
  github.com/kong/deck/solver.TestStats_CreateOps.func1()
      /home/hbagdi/deck/solver/solver_test.go:18 +0x66

Goroutine 9 (running) created at:
  github.com/kong/deck/solver.TestStats_CreateOps()
      /home/hbagdi/deck/solver/solver_test.go:16 +0xf2
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:1194 +0x202

Goroutine 8 (finished) created at:
  github.com/kong/deck/solver.TestStats_CreateOps()
      /home/hbagdi/deck/solver/solver_test.go:16 +0xf2
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:1194 +0x202
==================
--- FAIL: TestStats_CreateOps (0.00s)
    testing.go:1093: race detected during execution of test
FAIL
exit status 1
FAIL    github.com/kong/deck/solver     0.008s

wg.Wait()
assert.Equal(t, int32(10), a.Count())
}