- 
                Notifications
    You must be signed in to change notification settings 
- Fork 122
fix: clock sequence race and add stress test; #219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
cbcc0e0
              8e67d5b
              c9f79a2
              73dc725
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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++ | ||
| 
     | ||
| } 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) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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...