From 31259e1d051274dabb47941f8db6206af8a42fe6 Mon Sep 17 00:00:00 2001 From: AdamMagued Date: Fri, 2 Oct 2026 20:45:53 +0000 Subject: [PATCH] perf(v7): lock-free atomic compare-and-swap monotonicity for NewV7 --- uuid_test.go | 33 +++++++++++++++++++++++++++++++++ version7.go | 21 ++++++++++++--------- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/uuid_test.go b/uuid_test.go index 906ecbe..0b28532 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -11,6 +11,7 @@ import ( "os" "runtime" "strings" + "sync" "testing" "time" "unsafe" @@ -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) + } +} diff --git a/version7.go b/version7.go index 3fec671..8c52eb7 100644 --- a/version7.go +++ b/version7.go @@ -6,6 +6,7 @@ package uuid import ( "io" + "sync/atomic" ) // UUID version 7 features a time-ordered value field derived from the widely @@ -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 }