diff options
author | Sami Hiltunen <shiltunen@gitlab.com> | 2020-11-10 11:55:09 +0300 |
---|---|---|
committer | Sami Hiltunen <shiltunen@gitlab.com> | 2020-11-17 14:12:25 +0300 |
commit | 96f3dafa8f00a8c1c7b03753bd3035dbb9673e03 (patch) | |
tree | c1f6b3482e2e7144e3c59aedb369367bd01c2945 | |
parent | 4fa998517623f98a944ccd9e2aa66230519a1acb (diff) |
add a concurrency safe wrapper for Random
This commit moves Random to it's own file and introduces a wrapper
to make Random safe for concurrent use. The global random in Go
is safe for concurrent use but random instances created otherwise
are not. Adding a locked wrapper allows us to inject a random in to
per repository router and have it provide safe safe access as the
global.
-rw-r--r-- | internal/praefect/random.go | 26 | ||||
-rw-r--r-- | internal/praefect/random_test.go | 41 | ||||
-rw-r--r-- | internal/praefect/router_per_repository.go | 11 | ||||
-rw-r--r-- | internal/praefect/router_per_repository_test.go | 4 |
4 files changed, 69 insertions, 13 deletions
diff --git a/internal/praefect/random.go b/internal/praefect/random.go new file mode 100644 index 000000000..36321c2c3 --- /dev/null +++ b/internal/praefect/random.go @@ -0,0 +1,26 @@ +package praefect + +import ( + "sync" +) + +// Random is the interface of the Go random number generator. +type Random interface { + // Intn returns a random integer in the range [0,n). + Intn(n int) int +} + +// randomFunc is an adapter to turn conforming functions in to a Random. +type randomFunc func(n int) int + +func (fn randomFunc) Intn(n int) int { return fn(n) } + +// NewLockedRandom wraps the passed in Random to make it safe for concurrent use. +func NewLockedRandom(r Random) Random { + var m sync.Mutex + return randomFunc(func(n int) int { + m.Lock() + defer m.Unlock() + return r.Intn(n) + }) +} diff --git a/internal/praefect/random_test.go b/internal/praefect/random_test.go new file mode 100644 index 000000000..915cd977b --- /dev/null +++ b/internal/praefect/random_test.go @@ -0,0 +1,41 @@ +package praefect + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestNewLockedRandom tests that n is correctly passed down to the random +// implementation and that the return value is correctly passed back. To test +// that access to the random is actually synchronized, we launch 50 goroutines +// to call the random func concurrently to increment actual. If the calls to +// random are not correctly synchronized, actual might be not match expected +// at the end and the race detector should detect racy accesses even in the +// cases where the values match. +func TestNewLockedRandom(t *testing.T) { + expected := 50 + actual := 0 + + random := NewLockedRandom(randomFunc(func(n int) int { + assert.Equal(t, 1, n) + actual++ + return 2 + })) + + var wg sync.WaitGroup + wg.Add(expected) + + for i := 0; i < expected; i++ { + go func() { + defer wg.Done() + assert.Equal(t, 2, random.Intn(1)) + }() + } + + wg.Wait() + + require.Equal(t, expected, actual) +} diff --git a/internal/praefect/router_per_repository.go b/internal/praefect/router_per_repository.go index fe9008a42..d2174c9bb 100644 --- a/internal/praefect/router_per_repository.go +++ b/internal/praefect/router_per_repository.go @@ -51,17 +51,6 @@ type PrimaryGetter interface { GetPrimary(ctx context.Context, virtualStorage string, relativePath string) (string, error) } -// Random is the interface of the Go random number generator. -type Random interface { - // Intn returns a random integer in the range [0,n). - Intn(n int) int -} - -// RandomFunc is an adapter to turn conforming functions in to a Random. -type RandomFunc func(n int) int - -func (fn RandomFunc) Intn(n int) int { return fn(n) } - // PerRepositoryRouter implements a router that routes requests respecting per repository primary nodes. type PerRepositoryRouter struct { conns Connections diff --git a/internal/praefect/router_per_repository_test.go b/internal/praefect/router_per_repository_test.go index abe355e89..02be7775b 100644 --- a/internal/praefect/router_per_repository_test.go +++ b/internal/praefect/router_per_repository_test.go @@ -99,7 +99,7 @@ func TestPerRepositoryRouter_RouteStorageAccessor(t *testing.T) { "valid-choice-2", }, }, - RandomFunc(func(n int) int { + randomFunc(func(n int) int { require.Equal(t, tc.numCandidates, n) return tc.pickCandidate }), @@ -198,7 +198,7 @@ func TestPerRepositoryRouter_RouteRepositoryAccessor(t *testing.T) { return "primary", nil }), tc.healthyNodes, - RandomFunc(func(n int) int { + randomFunc(func(n int) int { t.Helper() require.Equal(t, tc.numCandidates, n) return tc.pickCandidate |