Skip to content

Commit

Permalink
fix NewULID for systems with low time resolution (#270)
Browse files Browse the repository at this point in the history
NewULID uses a pool of rand.Source using time.Now() as seed. In some systems with high concurrency and lower time resolution, this leads to multiple sources with the same sead, making NewULID return equal values on subsequent calls.

This commit changes the seed to add a random number from the global shared rand.Source (which is thread-safe).

Signed-off-by: Santiago M. Mola <santi@mola.io>
  • Loading branch information
smola authored and roobre committed Jun 6, 2018
1 parent cf0ccd9 commit f853d80
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 10 deletions.
3 changes: 2 additions & 1 deletion model.go
Expand Up @@ -229,7 +229,8 @@ type Record interface {

var randPool = &sync.Pool{
New: func() interface{} {
return rand.NewSource(time.Now().UnixNano())
seed := time.Now().UnixNano() + rand.Int63()
return rand.NewSource(seed)
},
}

Expand Down
37 changes: 28 additions & 9 deletions model_test.go
@@ -1,6 +1,7 @@
package kallax

import (
"sync"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -23,16 +24,34 @@ func TestULID_Value(t *testing.T) {

func TestUULID_ThreeNewIDsAreDifferent(t *testing.T) {
r := require.New(t)
id1 := NewULID()
id2 := NewULID()
id3 := NewULID()

r.NotEqual(id1, id2)
r.NotEqual(id1, id3)
r.NotEqual(id2, id3)

r.True(id1 == id1)
r.False(id1 == id2)
goroutines := 100
ids_per_goroutine := 1000

ids := make(map[ULID]bool, ids_per_goroutine*goroutines)
m := &sync.Mutex{}

wg := &sync.WaitGroup{}
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func() {
var oids []ULID
for j := 0; j < ids_per_goroutine; j++ {
oids = append(oids, NewULID())
}

m.Lock()
for _, id := range oids {
ids[id] = true
}
m.Unlock()
wg.Done()
}()
}

wg.Wait()

r.Equal(goroutines*ids_per_goroutine, len(ids))
}

func TestULID_ScanValue(t *testing.T) {
Expand Down

0 comments on commit f853d80

Please sign in to comment.