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
23 changes: 22 additions & 1 deletion generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"encoding/binary"
"io"
"net"
"runtime"
"sync"
"time"
)
Expand Down Expand Up @@ -434,7 +435,27 @@ func (g *Gen) getClockSequence(useUnixTSMs bool, atTime time.Time) (uint64, uint
// Clock didn't change since last UUID generation.
// Should increase clock sequence.
if timeNow <= g.lastTime {
g.clockSequence++
// Increment the 14-bit clock sequence (RFC-9562 §6.1).
// Only the lower 14 bits are encoded in the UUID; the upper two
// bits are overridden by the Variant in SetVariant().
g.clockSequence = (g.clockSequence + 1) & 0x3fff

// If the sequence wrapped (back to zero) we MUST wait for the
// timestamp to advance to preserve uniqueness (see RFC-9562 §6.1).
if g.clockSequence == 0 {
for {
if useUnixTSMs {
timeNow = uint64(g.epochFunc().UnixMilli())
} else {
timeNow = g.getEpoch(g.epochFunc())
}
if timeNow > g.lastTime {
break
}
// Yield the processor briefly to avoid busy-waiting.
runtime.Gosched()
Comment on lines +455 to +456
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using runtime.Gosched() in a tight loop creates a busy-wait pattern that can consume CPU resources. Consider using a small sleep duration like time.Sleep(time.Microsecond) to reduce CPU usage while waiting for timestamp advancement.

Suggested change
// Yield the processor briefly to avoid busy-waiting.
runtime.Gosched()
// Sleep briefly to avoid busy-waiting and reduce CPU usage.
time.Sleep(time.Microsecond)

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

@cameracker cameracker Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atlet99 This is backed up by another stackoverflow thread here: https://stackoverflow.com/a/57703034/31566036

What do you think of doing a sleep instead? Might need to be a millisecond sleep instead of a microsecond though...

}
}
}
g.lastTime = timeNow

Expand Down
70 changes: 70 additions & 0 deletions race_v1_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package uuid

import (
"sync"
"sync/atomic"
"testing"
)

// TestV1UniqueConcurrent verifies that Version-1 UUID generation remains
// collision-free under various levels of concurrent load. The test uses
// table-driven subtests to progressively increase the number of goroutines
// and UUIDs generated. We intentionally let the timestamp advance (default
// NewGen) to keep the test quick while still exercising the new
// clock-sequence logic under contention.
func TestV1UniqueConcurrent(t *testing.T) {
cases := []struct {
name string
goroutines int
uuidsPerGor int
}{
{"small", 20, 600}, // 12 000 UUIDs (baseline)
{"medium", 100, 1000}, // 100 000 UUIDs (original failure case)
{"large", 200, 1000}, // 200 000 UUIDs (high contention)
}

for _, tc := range cases {
tc := tc // capture range variable
t.Run(tc.name, func(t *testing.T) {
gen := NewGen()

var (
wg sync.WaitGroup
mu sync.Mutex
seen = make(map[UUID]struct{}, tc.goroutines*tc.uuidsPerGor)
dupCount uint32
genErr uint32
)

for i := 0; i < tc.goroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < tc.uuidsPerGor; j++ {
u, err := gen.NewV1()
if err != nil {
atomic.AddUint32(&genErr, 1)
return
}
mu.Lock()
if _, exists := seen[u]; exists {
dupCount++
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dupCount variable is accessed without atomic operations while other variables use atomic.AddUint32(). This creates inconsistent synchronization patterns. Consider using atomic.AddUint32(&dupCount, 1) for consistency.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

@cameracker cameracker Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not that worried about the consistency with how the ints are incremented in this case.

} else {
seen[u] = struct{}{}
}
mu.Unlock()
}
}()
}

wg.Wait()

if genErr > 0 {
t.Fatalf("%d errors occurred during UUID generation", genErr)
}
if dupCount > 0 {
t.Fatalf("duplicate UUIDs detected: %d", dupCount)
}
})
}
}
Loading