Skip to content
Open
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
5 changes: 5 additions & 0 deletions version6.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ func NewV6() (UUID, error) {
// are generating multiple UUIDs, it is recommended to increment the time.
// If getTime fails to return the current NewV6WithTime returns Nil and an error.
func NewV6WithTime(customTime *time.Time) (UUID, error) {
// getTime reads and mutates the shared clock sequence state, which is
// guarded by timeMu (see GetTime). Take the lock so concurrent generation
// stays race-free and keeps producing unique values.
timeMu.Lock()
now, seq, err := getTime(customTime)
timeMu.Unlock()
if err != nil {
return Nil, err
}
Expand Down
44 changes: 44 additions & 0 deletions version6_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package uuid

import (
"sync"
"testing"
"time"
)
Expand Down Expand Up @@ -66,6 +67,49 @@ func TestNewV6FromTimeGeneratesUniqueUUIDs(t *testing.T) {
}
}

func TestNewV6WithTimeConcurrentUnique(t *testing.T) {
// NewV6WithTime must take the clock-sequence lock so concurrent generation
// for the same timestamp stays race-free and keeps producing unique values.
// For a fixed timestamp the UUID varies only by the clock sequence, so up
// to 16384 calls must all be unique. Run with -race to also surface the
// underlying data race directly.
fixed := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)

const goroutines = 8
const perGoroutine = 2000 // 16000 total < 16384 clock-sequence values

var wg sync.WaitGroup
var mu sync.Mutex
seen := make(map[UUID]struct{}, goroutines*perGoroutine)
dups := 0

for g := 0; g < goroutines; g++ {
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < perGoroutine; i++ {
id, err := NewV6WithTime(&fixed)
if err != nil {
t.Errorf("NewV6WithTime returned unexpected error %v", err)
return
}
mu.Lock()
if _, ok := seen[id]; ok {
dups++
} else {
seen[id] = struct{}{}
}
mu.Unlock()
}
}()
}
wg.Wait()

if dups != 0 {
t.Errorf("got %d duplicate V6 UUIDs from concurrent NewV6WithTime calls", dups)
}
}

func BenchmarkNewV6WithTime(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Expand Down