github.com/git-amp/amp-sdk-go@v0.7.5/stdlib/symbol/memory_table/table.go (about) 1 package memory_table 2 3 import ( 4 "bytes" 5 "sync" 6 "sync/atomic" 7 8 "github.com/git-amp/amp-sdk-go/stdlib/bufs" 9 "github.com/git-amp/amp-sdk-go/stdlib/symbol" 10 ) 11 12 func createTable(opts TableOpts) (symbol.Table, error) { 13 if opts.Issuer == nil { 14 opts.Issuer = symbol.NewVolatileIssuer(opts.IssuerInitsAt) 15 } else { 16 opts.Issuer.AddRef() 17 } 18 19 st := &symbolTable{ 20 opts: opts, 21 curBufPoolIdx: -1, 22 valueCache: make(map[uint64]kvEntry, opts.WorkingSizeHint), 23 tokenCache: make(map[symbol.ID]kvEntry, opts.WorkingSizeHint), 24 } 25 26 st.refCount.Store(1) 27 return st, nil 28 } 29 30 func (st *symbolTable) Issuer() symbol.Issuer { 31 return st.opts.Issuer 32 } 33 34 func (st *symbolTable) AddRef() { 35 st.refCount.Add(1) 36 } 37 38 func (st *symbolTable) Close() error { 39 if st.refCount.Add(-1) > 0 { 40 return nil 41 } 42 return st.close() 43 } 44 45 func (st *symbolTable) close() error { 46 err := st.opts.Issuer.Close() 47 st.opts.Issuer = nil 48 49 st.valueCache = nil 50 st.tokenCache = nil 51 st.bufPools = nil 52 return err 53 } 54 55 type kvEntry struct { 56 symID symbol.ID 57 poolIdx int32 58 poolOfs int32 59 len int32 60 } 61 62 func (st *symbolTable) equals(kv *kvEntry, buf []byte) bool { 63 sz := int32(len(buf)) 64 if sz != kv.len { 65 return false 66 } 67 return bytes.Equal(st.bufPools[kv.poolIdx][kv.poolOfs:kv.poolOfs+sz], buf) 68 } 69 70 func (st *symbolTable) bufForEntry(kv kvEntry) []byte { 71 if kv.symID == 0 { 72 return nil 73 } 74 return st.bufPools[kv.poolIdx][kv.poolOfs : kv.poolOfs+kv.len] 75 } 76 77 // symbolTable implements symbol.Table 78 type symbolTable struct { 79 opts TableOpts 80 refCount atomic.Int32 81 valueCacheMu sync.RWMutex // Protects valueCache 82 valueCache map[uint64]kvEntry // Maps a entry value hash to a kvEntry 83 tokenCacheMu sync.RWMutex // Protects tokenCache 84 tokenCache map[symbol.ID]kvEntry // Maps an ID ("token") to an entry 85 curBufPool []byte 86 curBufPoolSz int32 87 curBufPoolIdx int32 88 bufPools [][]byte 89 } 90 91 func (st *symbolTable) getIDFromCache(buf []byte) symbol.ID { 92 hash := bufs.HashBuf(buf) 93 94 st.valueCacheMu.RLock() 95 defer st.valueCacheMu.RUnlock() 96 97 kv, found := st.valueCache[hash] 98 for found { 99 if st.equals(&kv, buf) { 100 return kv.symID 101 } 102 hash++ 103 kv, found = st.valueCache[hash] 104 } 105 106 return 0 107 } 108 109 func (st *symbolTable) allocAndBindToID(buf []byte, bindID symbol.ID) kvEntry { 110 hash := bufs.HashBuf(buf) 111 112 st.valueCacheMu.Lock() 113 defer st.valueCacheMu.Unlock() 114 115 kv, found := st.valueCache[hash] 116 for found { 117 if st.equals(&kv, buf) { 118 break 119 } 120 hash++ 121 kv, found = st.valueCache[hash] 122 } 123 124 // No-op if already present 125 if found && kv.symID == bindID { 126 return kv 127 } 128 129 // At this point we know [hash] will be the destination element 130 // Add a copy of the buf in our backing buf (in the heap). 131 // If we run out of space in our pool, we start a new pool 132 kv.symID = bindID 133 { 134 kv.len = int32(len(buf)) 135 if int(st.curBufPoolSz+kv.len) > cap(st.curBufPool) { 136 allocSz := max(st.opts.PoolSz, kv.len) 137 st.curBufPool = make([]byte, allocSz) 138 st.curBufPoolSz = 0 139 st.curBufPoolIdx++ 140 st.bufPools = append(st.bufPools, st.curBufPool) 141 } 142 kv.poolIdx = st.curBufPoolIdx 143 kv.poolOfs = st.curBufPoolSz 144 copy(st.curBufPool[kv.poolOfs:kv.poolOfs+kv.len], buf) 145 st.curBufPoolSz += kv.len 146 } 147 148 // Place the now-backed copy at the open hash spot and return the alloced value 149 st.valueCache[hash] = kv 150 151 st.tokenCacheMu.Lock() 152 st.tokenCache[kv.symID] = kv 153 st.tokenCacheMu.Unlock() 154 155 return kv 156 } 157 158 func (st *symbolTable) GetSymbolID(val []byte, autoIssue bool) symbol.ID { 159 symID := st.getIDFromCache(val) 160 if symID != 0 { 161 return symID 162 } 163 164 symID = st.getsetValueIDPair(val, 0, autoIssue) 165 return symID 166 } 167 168 func (st *symbolTable) SetSymbolID(val []byte, symID symbol.ID) symbol.ID { 169 // If symID == 0, then behave like GetSymbolID(val, true) 170 return st.getsetValueIDPair(val, symID, symID == 0) 171 } 172 173 // getsetValueIDPair loads and returns the ID for the given value, and/or writes the ID and value assignment to the db, 174 // also updating the cache in the process. 175 // 176 // if symID == 0: 177 // if the given value has an existing value-ID association: 178 // the existing ID is cached and returned (mapID is ignored). 179 // if the given value does NOT have an existing value-ID association: 180 // if mapID == false, the call has no effect and 0 is returned. 181 // if mapID == true, a new ID is issued and new value-to-ID and ID-to-value assignments are written, 182 // 183 // if symID != 0: 184 // if mapID == false, a new value-to-ID assignment is (over)written and any existing ID-to-value assignment remains. 185 // if mapID == true, both value-to-ID and ID-to-value assignments are (over)written. 186 func (st *symbolTable) getsetValueIDPair(val []byte, symID symbol.ID, mapID bool) symbol.ID { 187 188 // The empty string is always mapped to ID 0 189 if len(val) == 0 { 190 return 0 191 } 192 193 if symID == 0 && mapID { 194 symID, _ = st.opts.Issuer.IssueNextID() 195 } 196 197 // Update the cache 198 if symID != 0 { 199 st.allocAndBindToID(val, symID) 200 } 201 return symID 202 } 203 204 func (st *symbolTable) GetSymbol(symID symbol.ID, io []byte) []byte { 205 if symID == 0 { 206 return nil 207 } 208 209 st.tokenCacheMu.RLock() 210 kv := st.tokenCache[symID] 211 st.tokenCacheMu.RUnlock() 212 213 // At this point, if symID wasn't found, kv will be zero and causing nil to be returned 214 symBuf := st.bufForEntry(kv) 215 if symBuf == nil { 216 return nil 217 } 218 return append(io, symBuf...) 219 } 220 221 func max(a, b int32) int32 { 222 if a > b { 223 return a 224 } else { 225 return b 226 } 227 }