Skip to content

Commit 250cfb7

Browse files
committed
Add a unit test for the package-global UUID source.
1 parent 120dcbf commit 250cfb7

File tree

2 files changed

+65
-12
lines changed

2 files changed

+65
-12
lines changed

x/mongo/driver/uuid/uuid.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ import (
1616
// UUID represents a UUID.
1717
type UUID [16]byte
1818

19-
// random is a package-global pseudo-random number generator.
20-
var random = randutil.NewLockedRand(rand.NewSource(randutil.CryptoSeed()))
19+
// A source is a UUID generator that reads random values from a randutil.LockedRand.
20+
// It is safe to use from multiple goroutines.
21+
type source struct {
22+
random *randutil.LockedRand
23+
}
2124

22-
// New returns a random UUIDv4. It uses a "math/rand" pseudo-random number generator seeded with a
23-
// cryptographically-secure random number at package initialization.
24-
//
25-
// New should not be used to generate cryptographically-secure random UUIDs.
26-
func New() (UUID, error) {
25+
// new returns a random UUIDv4 with bytes read from the source's random number generator.
26+
func (s *source) new() (UUID, error) {
2727
var uuid [16]byte
2828

29-
_, err := io.ReadFull(random, uuid[:])
29+
_, err := io.ReadFull(s.random, uuid[:])
3030
if err != nil {
3131
return [16]byte{}, err
3232
}
@@ -36,6 +36,26 @@ func New() (UUID, error) {
3636
return uuid, nil
3737
}
3838

39+
// newGlobalSource returns a source that uses a "math/rand" pseudo-random number generator seeded
40+
// with a cryptographically-secure random number. It is intended to be used to initialize the
41+
// package-global UUID generator.
42+
func newGlobalSource() *source {
43+
return &source{
44+
random: randutil.NewLockedRand(rand.NewSource(randutil.CryptoSeed())),
45+
}
46+
}
47+
48+
// globalSource is a package-global pseudo-random UUID generator.
49+
var globalSource = newGlobalSource()
50+
51+
// New returns a random UUIDv4. It uses a "math/rand" pseudo-random number generator seeded with a
52+
// cryptographically-secure random number at package initialization.
53+
//
54+
// New should not be used to generate cryptographically-secure random UUIDs.
55+
func New() (UUID, error) {
56+
return globalSource.new()
57+
}
58+
3959
// Equal returns true if two UUIDs are equal.
4060
func Equal(a, b UUID) bool {
4161
return a == b

x/mongo/driver/uuid/uuid_test.go

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,50 @@
11
package uuid
22

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

6-
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
78
)
89

910
func TestNew(t *testing.T) {
1011
m := make(map[UUID]bool)
11-
for i := 1; i < 100; i++ {
12+
for i := 1; i < 1000000; i++ {
1213
uuid, err := New()
13-
assert.NoError(t, err, "New error")
14-
assert.False(t, m[uuid], "New returned a duplicate UUID %v", uuid)
14+
require.NoError(t, err, "New error")
15+
require.False(t, m[uuid], "New returned a duplicate UUID %v", uuid)
1516
m[uuid] = true
1617
}
1718
}
19+
20+
// GODRIVER-2349
21+
// Test that initializing many package-global UUID sources concurrently never leads to any duplicate
22+
// UUIDs being generated.
23+
func TestGlobalSource(t *testing.T) {
24+
// Create a slice of 1,000 sources and initialize them in 1,000 separate goroutines. The goal is
25+
// to emulate many separate Go driver processes starting at the same time and initializing the
26+
// uuid package at the same time.
27+
sources := make([]*source, 1000)
28+
var wg sync.WaitGroup
29+
for i := range sources {
30+
wg.Add(1)
31+
go func(i int) {
32+
defer wg.Done()
33+
sources[i] = newGlobalSource()
34+
}(i)
35+
}
36+
wg.Wait()
37+
38+
// Read 1,000 UUIDs from each source and assert that there is never a duplicate value, either
39+
// from the same source or from separate sources.
40+
const iterations = 1000
41+
uuids := make(map[UUID]bool, len(sources)*iterations)
42+
for i := 0; i < iterations; i++ {
43+
for j, s := range sources {
44+
uuid, err := s.new()
45+
require.NoError(t, err, "new() error")
46+
require.Falsef(t, uuids[uuid], "source %d returned a duplicate UUID on iteration %d: %v", j, i, uuid)
47+
uuids[uuid] = true
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)