github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/gtl/tests/rcu_map.go (about) 1 // Code generated by "../generate.py --prefix=rcuTest --PREFIX=RCUTest -DKEY=string -DVALUE=uint64 -DHASH=testhash --package=tests --output=rcu_map.go ../rcu_map.go.tpl". DO NOT EDIT. 2 3 package tests 4 5 import ( 6 "sync/atomic" 7 "unsafe" 8 ) 9 10 // RCUTestMap is a concurrent map. A reader can access the map without lock, 11 // regardless of background updates. The writer side must coordinate using an 12 // external mutex if there are multiple writers. This map is linearizable. 13 // 14 // Example: 15 // 16 // m := NewRCUTestMap(10) 17 // go func() { // writer 18 // m.Store("foo", "bar") 19 // }() 20 // go func() { // reader 21 // val, ok := m.Load("foo") 22 // } 23 type RCUTestMap struct { 24 p unsafe.Pointer // *rcuTestMapState 25 } 26 27 // RCUTestMapState represents a fixed-size chained hash table. It can store up to 28 // maxCapacity key/value pairs. Beyond that, the caller must create a new 29 // RCUTestMapState with a larger capacity. 30 type rcuTestMapState struct { 31 log2Len uint // ==log2(len(table)) 32 mask uint64 // == ^(log2Len-1) 33 table []unsafe.Pointer // *rcuTestMapNode 34 n int // # of objects currently stored in the table 35 maxCapacity int // max # of object that can be stored 36 } 37 38 // RCUTestMapNode represents a hash bucket. 39 type rcuTestMapNode struct { 40 key string 41 value uint64 42 43 // next points to the next element in the same hash bucket 44 next unsafe.Pointer // *rcuTestMapNode 45 } 46 47 func newRCUTestMapState(log2Len uint) *rcuTestMapState { 48 len := int(1 << log2Len) 49 table := &rcuTestMapState{ 50 log2Len: log2Len, 51 mask: uint64(log2Len - 1), 52 table: make([]unsafe.Pointer, 1<<log2Len), 53 maxCapacity: int(float64(len) * 0.8), 54 } 55 if table.maxCapacity < len { 56 table.maxCapacity = len 57 } 58 return table 59 } 60 61 // NewRCUTestMap creates a new map. Arg initialLenHint suggests the the initial 62 // capacity. If you plan to store 100 keys, then pass 100 as the value. If you 63 // don't know the capacity, pass 0 as initialLenHint. 64 func NewRCUTestMap(initialLenHint int) *RCUTestMap { 65 log2Len := uint(3) // 8 nodes 66 for (1 << log2Len) < initialLenHint { 67 if log2Len > 31 { 68 // TODO(saito) We could make the table to grow larger than 32 bits, but 69 // doing so will break 32bit builds. 70 panic(initialLenHint) 71 } 72 log2Len++ 73 } 74 m := RCUTestMap{p: unsafe.Pointer(newRCUTestMapState(log2Len))} 75 return &m 76 } 77 78 // Load finds a value with the given key. Returns false if not found. 79 func (m *RCUTestMap) Load(key string) (uint64, bool) { 80 hash := testhash(key) 81 table := (*rcuTestMapState)(atomic.LoadPointer(&m.p)) 82 b := int(hash & table.mask) 83 node := (*rcuTestMapNode)(atomic.LoadPointer(&table.table[b])) 84 for node != nil { 85 if node.key == key { 86 return node.value, true 87 } 88 node = (*rcuTestMapNode)(atomic.LoadPointer(&node.next)) 89 } 90 var dummy uint64 91 return dummy, false 92 } 93 94 // store returns false iff the table needs resizing. 95 func (t *rcuTestMapState) store(key string, value uint64) bool { 96 var ( 97 hash = testhash(key) 98 b = int(hash & t.mask) 99 node = (*rcuTestMapNode)(t.table[b]) 100 probeLen = 0 101 prevNode *rcuTestMapNode 102 ) 103 for node != nil { 104 if node.key == key { 105 newNode := *node 106 newNode.value = value 107 if prevNode == nil { 108 atomic.StorePointer(&t.table[b], unsafe.Pointer(&newNode)) 109 } else { 110 atomic.StorePointer(&prevNode.next, unsafe.Pointer(&newNode)) 111 } 112 return true 113 } 114 prevNode = node 115 node = (*rcuTestMapNode)(node.next) 116 probeLen++ 117 if probeLen >= 4 && t.n >= t.maxCapacity { 118 return false 119 } 120 } 121 newNode := rcuTestMapNode{key: key, value: value} 122 if prevNode == nil { 123 atomic.StorePointer(&t.table[b], unsafe.Pointer(&newNode)) 124 } else { 125 atomic.StorePointer(&prevNode.next, unsafe.Pointer(&newNode)) 126 } 127 t.n++ 128 return true 129 } 130 131 // Store stores the value for the given key. If the key is already in the map, 132 // it updates the mapping to the given value. 133 // 134 // Caution: if Store() is going to be called concurrently, it must be serialized 135 // externally. 136 func (m *RCUTestMap) Store(key string, value uint64) { 137 table := (*rcuTestMapState)(atomic.LoadPointer(&m.p)) 138 if table.store(key, value) { 139 return 140 } 141 log2Len := table.log2Len + 1 142 if log2Len > 31 { 143 panic(log2Len) 144 } 145 newTable := newRCUTestMapState(log2Len) 146 // Copy the contents of the old table over to the new table. 147 for _, p := range table.table { 148 node := (*rcuTestMapNode)(p) 149 for node != nil { 150 if !newTable.store(node.key, node.value) { 151 panic(node) 152 } 153 node = (*rcuTestMapNode)(node.next) 154 } 155 } 156 if !newTable.store(key, value) { 157 panic(key) 158 } 159 atomic.StorePointer(&m.p, unsafe.Pointer(newTable)) 160 }