Skip to content

Commit a3f8548

Browse files
author
marcogrecopriolo
committed
MB-30924 improve util.uuid
uuid() (which is used to set requests ids) can be quite slow because of golang/go#22614 essentially access to /dev/urandom has to be serialized, and when everyone is piling in, the cost of assigning a request id is (measured) 3.5% of the whole execution time! This change makes the access to /dev/urandom asynchronous, reading in 32k chunks (one read every 4k requests), which eliminates the cost completely. It has been used in anger, at a rate of 50k requests/second, and it has not misbehaved. Change-Id: I47e524ef11344aadafff770444f214093fe6e008 Reviewed-on: http://review.couchbase.org/98891 Reviewed-by: Sitaram Vemulapalli <[email protected]> Reviewed-by: Johan Larson <[email protected]> Tested-by: Marco Greco <[email protected]>
1 parent 0abc068 commit a3f8548

File tree

1 file changed

+110
-2
lines changed

1 file changed

+110
-2
lines changed

util/uuid.go

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,136 @@ import (
1515
"fmt"
1616
"io"
1717
"sync"
18+
19+
atomic "github.com/couchbase/go-couchbase/platform"
1820
)
1921

2022
var uuidPool *sync.Pool
2123

2224
const _UUID_SIZE = 16
2325

26+
type randomBytesBuffer struct {
27+
switchHandler sync.WaitGroup
28+
switchWaiter sync.WaitGroup
29+
currentBuffer []byte
30+
nextBuffer []byte
31+
currentIndex uint32
32+
err error
33+
}
34+
35+
var randomBytes randomBytesBuffer
36+
37+
const _RANDOM_SIZE = 4096 * _UUID_SIZE
38+
const _FIRE_THRESHOLD = _RANDOM_SIZE / 2
39+
const _RANDOM_LOCKER = _RANDOM_SIZE + _UUID_SIZE
40+
2441
func init() {
2542
uuidPool = &sync.Pool{
2643
New: func() interface{} {
2744
b := make([]byte, _UUID_SIZE, _UUID_SIZE)
2845
return b
2946
},
3047
}
48+
randomBytes.currentBuffer = make([]byte, _RANDOM_SIZE)
49+
randomBytes.nextBuffer = make([]byte, _RANDOM_SIZE)
50+
randomBytes.switchHandler.Add(1)
51+
randomBytes.switchWaiter.Add(1)
52+
getNextBuffer()
53+
tmp := randomBytes.currentBuffer
54+
randomBytes.currentBuffer = randomBytes.nextBuffer
55+
randomBytes.nextBuffer = tmp
56+
}
57+
58+
func readFull(bytes []byte) error {
59+
for {
60+
61+
// copy pointer so that the structure can be changed
62+
buffer := randomBytes.currentBuffer
63+
64+
// get next position
65+
index := atomic.AddUint32(&randomBytes.currentIndex, _UUID_SIZE)
66+
67+
// we are close to needing the next buffer
68+
if index == _FIRE_THRESHOLD {
69+
randomBytes.switchHandler.Add(1)
70+
randomBytes.switchWaiter.Add(1)
71+
go getNextBuffer()
72+
}
73+
74+
// we are in luck
75+
if index <= _RANDOM_SIZE {
76+
copy(bytes, buffer[index-_UUID_SIZE:index])
77+
return nil
78+
}
79+
80+
// out of space - slow path
81+
// first reader waiting does the dirty work
82+
if index == _RANDOM_LOCKER {
83+
84+
// wait for the asynchronous read
85+
randomBytes.switchHandler.Wait()
86+
87+
// it didn't work
88+
if randomBytes.err != nil {
89+
90+
// wake the waiters (if extra readers come along they will
91+
// get an error anyway)
92+
randomBytes.switchWaiter.Done()
93+
94+
// try again: block everyone
95+
randomBytes.switchHandler.Add(1)
96+
randomBytes.switchWaiter.Add(1)
97+
98+
// set up another first reader
99+
atomic.StoreUint32(&randomBytes.currentIndex, _RANDOM_SIZE)
100+
101+
go getNextBuffer()
102+
return randomBytes.err
103+
}
104+
105+
// switch buffer
106+
tmp := randomBytes.currentBuffer
107+
randomBytes.currentBuffer = randomBytes.nextBuffer
108+
randomBytes.nextBuffer = tmp
109+
110+
// get our random data
111+
copy(bytes, randomBytes.currentBuffer[0:_UUID_SIZE])
112+
113+
// reset the index and wake up the others
114+
atomic.StoreUint32(&randomBytes.currentIndex, _UUID_SIZE)
115+
randomBytes.switchWaiter.Done()
116+
return nil
117+
} else {
118+
119+
// everyone else takes advantage
120+
randomBytes.switchHandler.Wait()
121+
if randomBytes.err != nil {
122+
return randomBytes.err
123+
}
124+
125+
// try again
126+
continue
127+
}
128+
}
129+
}
130+
131+
func getNextBuffer() {
132+
n, err := io.ReadFull(rand.Reader, randomBytes.nextBuffer)
133+
if n != _RANDOM_SIZE {
134+
randomBytes.err = fmt.Errorf("random reader only provide %v bytes", n)
135+
} else {
136+
randomBytes.err = err
137+
}
138+
randomBytes.switchHandler.Done()
31139
}
32140

33141
// UUID generates a random UUID according to RFC 4122
34142
func UUID() (string, error) {
35143
uuid := uuidPool.Get().([]byte)
36144
defer uuidPool.Put(uuid)
37145

38-
n, err := io.ReadFull(rand.Reader, uuid)
39-
if n != len(uuid) || err != nil {
146+
err := readFull(uuid)
147+
if err != nil {
40148
return "", err
41149
}
42150
// variant bits; see section 4.1.1

0 commit comments

Comments
 (0)