-
Notifications
You must be signed in to change notification settings - Fork 175
/
eject.go
125 lines (105 loc) · 3.75 KB
/
eject.go
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
// (c) 2019 Dapper Labs - ALL RIGHTS RESERVED
package stdmap
import (
"math"
"math/rand"
"sync"
"github.com/onflow/flow-go/model/flow"
)
// EjectFunc is a function used to pick an entity to evict from the memory pool
// backend when it overflows its limit. A custom eject function can be injected
// into the memory pool upon creation, which allows us to hook into the eject
// to clean up auxiliary data and/or to change the strategy of eviction.
type EjectFunc func(entities map[flow.Identifier]flow.Entity) (flow.Identifier, flow.Entity)
// EjectFakeRandom relies on the random map iteration in Go to pick the entity we eject
// from the entity set. It picks the first entity upon iteration, thus being the fastest
// way to pick an entity to be evicted; at the same time, it conserves the random bias
// of the Go map iteration.
func EjectFakeRandom(entities map[flow.Identifier]flow.Entity) (flow.Identifier, flow.Entity) {
var entityID flow.Identifier
var entity flow.Entity
for entityID, entity = range entities {
break
}
return entityID, entity
}
// EjectTrueRandom relies on a random generator to pick a random entity to eject from the
// entity set. It will, on average, iterate through half the entities of the set. However,
// it provides us with a truly evenly distributed random selection.
func EjectTrueRandom(entities map[flow.Identifier]flow.Entity) (flow.Identifier, flow.Entity) {
var entityID flow.Identifier
var entity flow.Entity
i := 0
n := rand.Intn(len(entities))
for entityID, entity = range entities {
if i == n {
break
}
i++
}
return entityID, entity
}
// EjectPanic simply panics, crashing the program. Useful when cache is not expected
// to grow beyond certain limits, but ejecting is not applicable
func EjectPanic(entities map[flow.Identifier]flow.Entity) (flow.Identifier, flow.Entity) {
panic("unexpected: mempool size over the limit")
}
// LRUEjector provides a swift FIFO ejection functionality
type LRUEjector struct {
sync.Mutex
table map[flow.Identifier]uint64 // keeps sequence number of entities it tracks
seqNum uint64 // keeps the most recent sequence number
}
func NewLRUEjector() *LRUEjector {
return &LRUEjector{
table: make(map[flow.Identifier]uint64),
seqNum: 0,
}
}
// Track should be called every time a new entity is added to the mempool.
// It tracks the entity for later ejection.
func (q *LRUEjector) Track(entityID flow.Identifier) {
q.Lock()
defer q.Unlock()
if _, ok := q.table[entityID]; ok {
// skips adding duplicate item
return
}
// TODO current table structure provides O(1) track and untrack features
// however, the Eject functionality is asymptotically O(n).
// With proper resource cleanups by the mempools, the Eject is supposed
// as a very less frequent operation. However, further optimizations on
// Eject efficiency is needed.
q.table[entityID] = q.seqNum
q.seqNum++
}
// Untrack simply removes the tracker of the ejector off the entityID
func (q *LRUEjector) Untrack(entityID flow.Identifier) {
q.Lock()
defer q.Unlock()
delete(q.table, entityID)
}
// Eject implements EjectFunc for LRUEjector. It finds the entity with the lowest sequence number (i.e.,
//the oldest entity). It also untracks
func (q *LRUEjector) Eject(entities map[flow.Identifier]flow.Entity) (flow.Identifier, flow.Entity) {
q.Lock()
defer q.Unlock()
// finds the oldest entity
oldestSQ := uint64(math.MaxUint64)
var oldestID flow.Identifier
for id := range entities {
if sq, ok := q.table[id]; ok {
if sq < oldestSQ {
oldestID = id
oldestSQ = sq
}
}
}
oldestEntity, ok := entities[oldestID]
if !ok {
oldestID, oldestEntity = EjectTrueRandom(entities)
}
// untracks the oldest id as it is supposed to be ejected
delete(q.table, oldestID)
return oldestID, oldestEntity
}