Skip to content

Commit a2b2b32

Browse files
authored
fix: Monotonicity in UUIDv7 (#150)
* Monotonicity in UUIDv7 * fix Monotonicity * fix comment * Monotonicity 2 * lock * fix comment * fix comment
1 parent c58770e commit a2b2b32

File tree

2 files changed

+48
-14
lines changed

2 files changed

+48
-14
lines changed

uuid_test.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,7 @@ func TestVersion6(t *testing.T) {
825825
func TestVersion7(t *testing.T) {
826826
SetRand(nil)
827827
m := make(map[string]bool)
828-
for x := 1; x < 32; x++ {
828+
for x := 1; x < 128; x++ {
829829
uuid, err := NewV7()
830830
if err != nil {
831831
t.Fatalf("could not create UUID: %v", err)
@@ -874,20 +874,25 @@ func TestVersion7_pooled(t *testing.T) {
874874
func TestVersion7FromReader(t *testing.T) {
875875
myString := "8059ddhdle77cb52"
876876
r := bytes.NewReader([]byte(myString))
877-
r2 := bytes.NewReader([]byte(myString))
878-
uuid1, err := NewV7FromReader(r)
877+
_, err := NewV7FromReader(r)
879878
if err != nil {
880879
t.Errorf("failed generating UUID from a reader")
881880
}
882881
_, err = NewV7FromReader(r)
883882
if err == nil {
884883
t.Errorf("expecting an error as reader has no more bytes. Got uuid. NewV7FromReader may not be using the provided reader")
885884
}
886-
uuid3, err := NewV7FromReader(r2)
887-
if err != nil {
888-
t.Errorf("failed generating UUID from a reader")
889-
}
890-
if uuid1 != uuid3 {
891-
t.Errorf("expected duplicates, got %q and %q", uuid1, uuid3)
885+
}
886+
887+
func TestVersion7Monotonicity(t *testing.T) {
888+
length := 10000
889+
u1 := Must(NewV7()).String()
890+
for i := 0; i < length; i++ {
891+
u2 := Must(NewV7()).String()
892+
if u2 <= u1 {
893+
t.Errorf("monotonicity failed at #%d: %s(next) < %s(before)", i, u2, u1)
894+
break
895+
}
896+
u1 = u2
892897
}
893898
}

version7.go

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ func NewV7FromReader(r io.Reader) (UUID, error) {
4444

4545
// makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6])
4646
// uuid[8] already has the right version number (Variant is 10)
47-
// see function NewV7 and NewV7FromReader
47+
// see function NewV7 and NewV7FromReader
4848
func makeV7(uuid []byte) {
4949
/*
5050
0 1 2 3
5151
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
5252
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
5353
| unix_ts_ms |
5454
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
55-
| unix_ts_ms | ver | rand_a |
55+
| unix_ts_ms | ver | rand_a (12 bit seq) |
5656
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
5757
|var| rand_b |
5858
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -61,7 +61,7 @@ func makeV7(uuid []byte) {
6161
*/
6262
_ = uuid[15] // bounds check
6363

64-
t := timeNow().UnixMilli()
64+
t, s := getV7Time()
6565

6666
uuid[0] = byte(t >> 40)
6767
uuid[1] = byte(t >> 32)
@@ -70,6 +70,35 @@ func makeV7(uuid []byte) {
7070
uuid[4] = byte(t >> 8)
7171
uuid[5] = byte(t)
7272

73-
uuid[6] = 0x70 | (uuid[6] & 0x0F)
74-
// uuid[8] has already has right version
73+
uuid[6] = 0x70 | (0x0F & byte(s>>8))
74+
uuid[7] = byte(s)
75+
}
76+
77+
// lastV7time is the last last time we returned stored as:
78+
//
79+
// 52 bits of time in milliseconds since epoch
80+
// 12 bits of (fractional nanoseconds) >> 8
81+
var lastV7time int64
82+
83+
const nanoPerMilli = 1000000
84+
85+
// getV7Time returns the time in milliseconds and nanoseconds / 256.
86+
// The returned (milli << 12 + seq) is guarenteed to be greater than
87+
// (milli << 12 + seq) returned by any previous call to getV7Time.
88+
func getV7Time() (milli, seq int64) {
89+
timeMu.Lock()
90+
defer timeMu.Unlock()
91+
92+
nano := timeNow().UnixNano()
93+
milli = nano / nanoPerMilli
94+
// Sequence number is between 0 and 3906 (nanoPerMilli>>8)
95+
seq = (nano - milli*nanoPerMilli) >> 8
96+
now := milli<<12 + seq
97+
if now <= lastV7time {
98+
now = lastV7time + 1
99+
milli = now >> 12
100+
seq = now & 0xfff
101+
}
102+
lastV7time = now
103+
return milli, seq
75104
}

0 commit comments

Comments
 (0)