github.com/grailbio/base@v0.0.11/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  }