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
33 changes: 33 additions & 0 deletions uuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"runtime"
"strings"
"sync"
"testing"
"time"
"unsafe"
Expand Down Expand Up @@ -928,3 +929,35 @@ func TestVersion7MonotonicityStrict(t *testing.T) {
u1 = u2
}
}

func TestVersion7ConcurrentMonotonicity(t *testing.T) {
const goroutines = 16
const count = 5000
var wg sync.WaitGroup
errCh := make(chan error, goroutines)

for g := 0; g < goroutines; g++ {
wg.Add(1)
go func() {
defer wg.Done()
var prev UUID
for i := 0; i < count; i++ {
u, err := NewV7()
if err != nil {
errCh <- err
return
}
if i > 0 && Compare(prev, u) >= 0 {
errCh <- fmt.Errorf("monotonicity failed: %s >= %s", prev, u)
return
}
prev = u
}
}()
}
wg.Wait()
close(errCh)
for err := range errCh {
t.Fatal(err)
}
}
21 changes: 12 additions & 9 deletions version7.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package uuid

import (
"io"
"sync/atomic"
)

// UUID version 7 features a time-ordered value field derived from the widely
Expand Down Expand Up @@ -85,20 +86,22 @@ const nanoPerMilli = 1000000
// getV7Time returns the time in milliseconds and nanoseconds / 256.
// The returned (milli << 12 + seq) is guaranteed to be greater than
// (milli << 12 + seq) returned by any previous call to getV7Time.
// Monotonicity is ensured lock-free via atomic Compare-And-Swap (CAS).
func getV7Time() (milli, seq int64) {
timeMu.Lock()
defer timeMu.Unlock()

nano := timeNow().UnixNano()
milli = nano / nanoPerMilli
// Sequence number is between 0 and 3906 (nanoPerMilli>>8)
seq = (nano - milli*nanoPerMilli) >> 8
now := milli<<12 + seq
if now <= lastV7time {
now = lastV7time + 1
milli = now >> 12
seq = now & 0xfff

for {
last := atomic.LoadInt64(&lastV7time)
next := now
if next <= last {
next = last + 1
}
if atomic.CompareAndSwapInt64(&lastV7time, last, next) {
return next >> 12, next & 0xfff
}
}
lastV7time = now
return milli, seq
}
Loading