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  }