-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathupdate.go
More file actions
481 lines (438 loc) · 14.2 KB
/
update.go
File metadata and controls
481 lines (438 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
package radix
import (
"fmt"
"runtime"
"sync"
"sync/atomic"
)
const (
replaceEmptyRootWithLeaf uint32 = iota
replaceLeafRootWithNode
rootLeafUpdate
nodePrefixMiss
nodeLeafInsert
nodeLeafInsert256
nodeLeafUpdate
nodeLeafUpdate256
)
var debugOperationName = map[uint32]string{
replaceEmptyRootWithLeaf: "REPLACE_EMPTY_ROOT_WITH_LEAF",
replaceLeafRootWithNode: "REPLACE_LEAF_ROOT_WITH_NODE",
rootLeafUpdate: "ROOT_LEAF_UPDATE",
nodePrefixMiss: "NODE_PREFIX_MISS",
nodeLeafInsert: "NODE_LEAF_INSERT",
nodeLeafInsert256: "NODE_256_LEAF_INSERT",
nodeLeafUpdate: "NODE_LEAF_UPDATE",
nodeLeafUpdate256: "NODE_256_LEAF_UPDATE",
}
// UpdateOperation represents the action to insert a new value or update on existing value in a tree.
// An update operation is initiated through calling PrepareUpdate on a Tree or an Allocator.
type UpdateOperation struct {
// points to key data used in call to PrepareUpdate
Key []byte
// the value of the found item, if any
Value uint64
// the key data fetched by the caller after call to PrepareUpdate (and before FinalizeUpdate)
FetchedKey []byte
// set true by caller if FetchedKey equals Key
Match bool
// the radix tree for this operation
idx *Tree
// allocator for this operation
allocator *Allocator
// if the allocator should be released when operation is finalized or aborted
releaseAlloc bool
kind uint32
k byte
position int
nodeGen, parentGen int32
node, parent uint64
count int
depth int
// the extracted node prefix
prefix []byte
// the index into prefix where we got a mismatch against the current update key
prefixMismatch int
// points to where the node is inserted in the tree
nodePtr *uint64
// location of node data
data []uint64
prefixSlots int
// points to where the leaf is in the tree
leafPtr *uint64
// the current leaf value, to be used in CompareAndSwap
currentLeaf uint64
// current raw value of node
raw uint64
}
var updateOperationPool = sync.Pool{
New: func() interface{} {
return &UpdateOperation{}
},
}
// newUpdateOperation returns an UpdateOperation on *Tree idx using *Allocator alloc.
// If releaseAlloc is true, the allocator is released when the operation is completed or aborted.
func newUpdateOperation(idx *Tree, alloc *Allocator, releaseAlloc bool) (op *UpdateOperation) {
op = updateOperationPool.Get().(*UpdateOperation)
op.idx = idx
op.allocator = alloc
op.releaseAlloc = releaseAlloc
return
}
func (op *UpdateOperation) prepareUpdate(key []byte) (found bool) {
op.Key = key
op.Match = false
op.Value = 0
op.FetchedKey = op.FetchedKey[:0]
op.allocator.startOp()
S:
root := atomic.LoadUint64(&op.idx.root)
if root == 0 {
// index is empty, lock root
if !op.idx.lockLeafRoot(root) {
runtime.Gosched()
goto S
}
op.k = key[0]
op.kind = replaceEmptyRootWithLeaf
return false
}
if isLeaf(root) {
// root is a leaf, acquire a root lock
if !op.idx.lockLeafRoot(root) {
runtime.Gosched()
goto S
}
op.k = key[0]
op.currentLeaf = root
// check if key matches
if byte(root) == key[0] {
// key matches, this might be a hit, or we will have to insert a prefixed node here
op.kind = rootLeafUpdate
op.Value = getLeafValue(root)
return true
}
// we should replace root with a node-2 with two leafs (no prefix)
op.kind = replaceLeafRootWithNode
return false
}
op.depth = 0
op.nodePtr = &op.idx.root
op.raw = root
op.parent = 0
op.parentGen = 0
prefixLen := 0
searchLoop:
for {
_, op.node, op.count, prefixLen = explodeNode(op.raw)
// first fetch the current generation for node (it could actually be shared with multiple nodes)
op.nodeGen = op.idx.generation(op.node)
// now verify that the node has not been changed since we read it
if atomic.LoadUint64(op.nodePtr) != op.raw {
goto S
}
// in case the parent was changed while we where working through it the current node might now be disconnected
// so we check the current parent generation to safeguard against this.
if op.parent != 0 && op.parentGen != op.idx.generation(op.parent) {
goto S
}
block := int(op.node >> blockSlotsShift)
offset := int(op.node & blockSlotsOffsetMask)
op.data = op.idx.blocks[block].data[offset:]
data := op.data
if prefixLen > 0 {
op.prefix, op.prefixSlots = appendPrefix(op.prefix[:0], data, prefixLen)
// now check for prefix mismatch
for i, a := range op.prefix {
if key[op.depth+i] != a {
// part of or all of prefix did not match, we must insert a node here
// We are going to change data at nodePtrOffset, which is inside the data block of parent
// We will copy all children of node (node data block must also be protected)
if !op.lock() {
runtime.Gosched()
goto S
}
op.kind = nodePrefixMiss
op.k = key[op.depth]
op.prefixMismatch = i
return false
}
}
data = data[op.prefixSlots:]
op.depth += len(op.prefix)
} else {
op.prefix = op.prefix[:0]
op.prefixSlots = 0
}
op.k = key[op.depth]
// A. Check a node-256
if op.count >= fullAllocFrom {
p := &data[int(op.k)]
a := atomic.LoadUint64(p)
if a == 0 {
if !op.lock() {
runtime.Gosched()
goto S
}
op.kind = nodeLeafInsert256
op.leafPtr = p
return false
}
if isLeaf(a) {
if !op.lock() {
runtime.Gosched()
goto S
}
op.kind = nodeLeafUpdate256
op.leafPtr = p
op.currentLeaf = a
op.Value = getLeafValue(a)
return true
}
op.enterNode(p, a)
continue searchLoop
}
// B. Check a variable sized node
op.position = op.count
var (
p *uint64
a uint64
b byte
)
for i := range data[:op.count] {
p = &data[i]
a = atomic.LoadUint64(p)
b = byte(a)
if b >= op.k {
op.position = i
break
}
}
if b == op.k {
if isLeaf(a) {
if !op.lock() {
runtime.Gosched()
goto S
}
op.kind = nodeLeafUpdate
op.leafPtr = p
op.currentLeaf = a
op.Value = getLeafValue(a)
return true
}
op.enterNode(p, a)
continue searchLoop
}
if !op.lock() {
runtime.Gosched()
goto S
}
op.kind = nodeLeafInsert
return false
}
}
// Finalize performs the update or insert in the tree, storing the given value at the prepared location.
// In case the value is larger than 55 bits it will be truncated.
// It returns true if the update was successful. If false, there was a write conflict and the operation must be restarted.
// The *UpdateOperation may not be used again after the call.
func (op *UpdateOperation) Finalize(value uint64) (updated bool) {
switch op.kind {
case replaceEmptyRootWithLeaf:
updated = atomic.CompareAndSwapUint64(&op.idx.root, 0, buildLeaf(op.k, value))
op.idx.unlockRoot()
if updated {
op.allocator.itemCounter++
}
case replaceLeafRootWithNode:
newNode, data := op.idx.allocateNode(op.allocator, 2, 0)
addSortedChildren(data, op.currentLeaf, buildLeaf(op.k, value))
updated = atomic.CompareAndSwapUint64((*uint64)(&op.idx.root), op.currentLeaf, buildNode(0, newNode, 2, 0))
op.idx.unlockRoot()
if updated {
op.allocator.itemCounter++
}
case rootLeafUpdate:
if op.Match {
// root is a leaf, and it was match, simply update it
updated = atomic.CompareAndSwapUint64(&op.idx.root, op.currentLeaf, buildLeaf(op.k, value))
} else {
// check how many characters the keys have in common (we know it's >= 1)
prefixLen := 0
for op.Key[prefixLen] == op.FetchedKey[prefixLen] {
prefixLen++
// in case prefixLen == len(op.Key) or len(op.FetchedKey) we have a violoation of the rule that no key can be the prefix of another key!
if prefixLen == len(op.Key) || prefixLen == len(op.FetchedKey) {
panic("Prefix violation")
}
}
// allocate new NODE-2 and try to insert it
newNode, data := op.idx.allocateNodeWithPrefix(op.allocator, 2, op.Key[:prefixLen])
addSortedChildren(data,
buildLeaf(op.Key[prefixLen], value),
updateLeafKey(op.currentLeaf, op.FetchedKey[prefixLen]))
updated = atomic.CompareAndSwapUint64(&op.idx.root, op.currentLeaf, buildNode(0, newNode, 2, prefixLen))
if updated {
op.allocator.itemCounter++
}
}
op.idx.unlockRoot()
case nodePrefixMiss:
// None or only part of the prefix on a node matches, we must replace it with a shorter prefix node and insert the second part (with the node children untouched) as a child node
// The mismatch was at op.prefix[op.prefixMismatch]
a := op.Key[op.depth+op.prefixMismatch]
b := op.prefix[op.prefixMismatch]
// Clone the existing node but with an updated prefix
pl := len(op.prefix) - op.prefixMismatch - 1
updatedNode, dst := op.idx.allocateNodeWithPrefix(op.allocator, op.count, op.prefix[op.prefixMismatch+1:])
// copy children
src := op.data[op.prefixSlots:]
copy(dst, src[:op.count])
// create a new NODE-2 with the matched part of the prefix (if any) and insert it
newNode, data := op.idx.allocateNodeWithPrefix(op.allocator, 2, op.Key[op.depth:op.depth+op.prefixMismatch])
addSortedChildren(data,
buildLeaf(a, value),
buildNode(b, updatedNode, op.count, pl))
atomic.StoreUint64(op.nodePtr, buildNode(byte(op.raw), newNode, 2, op.prefixMismatch))
op.unlock()
op.allocator.recycle(op.node, op.count+op.prefixSlots)
op.allocator.itemCounter++
updated = true
case nodeLeafInsert256:
updated = atomic.CompareAndSwapUint64(op.leafPtr, 0, buildLeaf(op.k, value))
if updated {
op.allocator.itemCounter++
}
op.unlock()
case nodeLeafInsert:
// insert a leaf by making the node bigger using copy-on-write
if op.count < fullAllocAfter {
// GROW NODE SIZE BY 1
newNode, dst := op.idx.allocateNode(op.allocator, op.count+1, len(op.prefix))
src := op.data
split := op.prefixSlots + op.position
// copy full node up to insert position, including unmodified prefix
copy(dst, src[:split])
// append the new value as a leaf at op.position
dst[split] = buildLeaf(op.k, value)
// copy the rest
l := op.count - op.position
copy(dst[split+1:], src[split:split+l])
// insert the new node into the tree, no need to compare and swap since this is checked after locking
v := buildNode(byte(op.raw), newNode, op.count+1, len(op.prefix))
atomic.StoreUint64(op.nodePtr, v)
op.unlock()
op.allocator.recycle(op.node, op.count+op.prefixSlots)
op.allocator.itemCounter++
updated = true
} else {
// SWITCH TO A NODE-256
newNode, dst := op.idx.allocateNodeWithPrefix(op.allocator, 256, op.prefix)
// we need to zero it
for i := range dst[:256] {
dst[i] = 0
}
// put the existing nodes in their new places
src := op.data[op.prefixSlots : op.prefixSlots+op.count]
// set the new leaf
dst[int(op.k)] = buildLeaf(op.k, value)
// add the existing, while setting obsolete tag on to-be-removed nodes
for _, a := range src {
b := byte(a)
dst[int(b)] = a
}
// insert the new node
v := buildNode(byte(op.raw), newNode, 256, len(op.prefix))
atomic.StoreUint64(op.nodePtr, v)
op.unlock()
// recycle old node
op.allocator.recycle(op.node, op.count+op.prefixSlots)
op.allocator.itemCounter++
updated = true
}
case nodeLeafUpdate, nodeLeafUpdate256:
// These are pure leaf updates, so they never disconnected other nodes from the tree
if op.Match {
// compare and swap needed since leaf might have changed before we acquired the lock
updated = atomic.CompareAndSwapUint64(op.leafPtr, op.currentLeaf, buildLeaf(op.k, value))
} else {
// key did not match, replace leaf with a NODE-2
// check how many characters the keys have in common starting from depth+1 (we know they match at depth)
prefixLen := 0
i := op.depth + 1
for ; i < len(op.Key) && i < len(op.FetchedKey) && op.Key[i] == op.FetchedKey[i]; i++ {
prefixLen++
}
a := op.Key[i]
b := op.FetchedKey[i]
// creat a new NODE-2 and try to insert it
newNode, data := op.idx.allocateNodeWithPrefix(op.allocator, 2, op.Key[op.depth+1:op.depth+1+prefixLen])
addSortedChildren(data,
buildLeaf(a, value),
updateLeafKey(op.currentLeaf, b),
)
updated = atomic.CompareAndSwapUint64((*uint64)(op.leafPtr), op.currentLeaf, buildNode(op.k, newNode, 2, prefixLen))
if updated {
op.allocator.itemCounter++
}
}
op.unlock()
}
op.allocator.endOp()
if op.releaseAlloc {
op.idx.ReleaseAllocator(op.allocator)
}
updateOperationPool.Put(op)
return updated
}
// Abort aborts the update operation, unlocking any locks taken during the prepare call and recycles the *UpdateOperation.
// The *UpdateOperation may not be used again after the call.
func (op *UpdateOperation) Abort() {
switch op.kind {
case replaceEmptyRootWithLeaf, replaceLeafRootWithNode, rootLeafUpdate:
op.idx.unlockRoot()
default:
op.unlock()
}
op.allocator.endOp()
if op.releaseAlloc {
op.idx.allocatorQueue.put(op.allocator.id)
}
updateOperationPool.Put(op)
}
// String returns a description of the operation
func (op *UpdateOperation) String() (s string) {
s = fmt.Sprintf("Operation: %s\n Key: %s\n FetchedKey: %s\n Match=%v\n depth=%d\n Key at depth: %s\n node prefix: %s (%d bytes, %d slots)\nnode: %d\nparent: %d\n", debugOperationName[op.kind], op.Key, op.FetchedKey, op.Match, op.depth, op.Key[op.depth:], op.prefix, len(op.prefix), op.prefixSlots, op.node, op.parent)
switch op.kind {
case replaceLeafRootWithNode, rootLeafUpdate:
return s + fmt.Sprintf(" currentLeaf: %d\n", op.currentLeaf)
case nodePrefixMiss:
return s + fmt.Sprintf("\nat node=%d (parent=%d), current node count=%d, prefixMismatched=%d (%s), insert k=%x, insert depth=%d", op.node, op.parent, op.count, op.prefixMismatch, op.Key[op.depth:op.depth+op.prefixMismatch], op.k, op.depth)
}
return s
}
// lock is inlined by compiler
func (op *UpdateOperation) lock() bool {
return op.idx.lockNodes2(op.node, op.nodeGen, op.parent, op.parentGen)
}
// unlock is inlined by compiler
func (op *UpdateOperation) unlock() {
op.idx.unlockNodes2(op.node, op.parent)
}
// enterNode is inlined by compiler
func (op *UpdateOperation) enterNode(p *uint64, a uint64) {
op.parentGen = op.nodeGen
op.parent = op.node
op.nodePtr = p
op.raw = a
op.depth++
}
// addSortedChildren is inlined by compiler
func addSortedChildren(data []uint64, a, b uint64) {
if byte(a) < byte(b) {
data[0] = a
data[1] = b
return
}
data[0] = b
data[1] = a
}