github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/execution/names/cache.go (about)

     1  // Copyright Monax Industries Limited
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package names
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"sync"
    10  )
    11  
    12  // The Cache helps prevent unnecessary IAVLTree updates and garbage generation.
    13  type Cache struct {
    14  	sync.RWMutex
    15  	backend Reader
    16  	names   map[string]*nameInfo
    17  }
    18  
    19  type nameInfo struct {
    20  	sync.RWMutex
    21  	entry   *Entry
    22  	removed bool
    23  	updated bool
    24  }
    25  
    26  var _ Writer = &Cache{}
    27  
    28  // Returns a Cache that wraps an underlying NameRegCacheGetter to use on a cache miss, can write to an
    29  // output Writer via Sync. Not goroutine safe, use syncStateCache if you need concurrent access
    30  func NewCache(backend Reader) *Cache {
    31  	return &Cache{
    32  		backend: backend,
    33  		names:   make(map[string]*nameInfo),
    34  	}
    35  }
    36  
    37  func (cache *Cache) GetName(name string) (*Entry, error) {
    38  	nameInfo, err := cache.get(name)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	nameInfo.RLock()
    43  	defer nameInfo.RUnlock()
    44  	if nameInfo.removed {
    45  		return nil, nil
    46  	}
    47  	return nameInfo.entry, nil
    48  }
    49  
    50  func (cache *Cache) UpdateName(entry *Entry) error {
    51  	nameInfo, err := cache.get(entry.Name)
    52  	if err != nil {
    53  		return err
    54  	}
    55  	nameInfo.Lock()
    56  	defer nameInfo.Unlock()
    57  	if nameInfo.removed {
    58  		return fmt.Errorf("UpdateName on a removed name: %s", nameInfo.entry.Name)
    59  	}
    60  
    61  	nameInfo.entry = entry
    62  	nameInfo.updated = true
    63  	return nil
    64  }
    65  
    66  func (cache *Cache) RemoveName(name string) error {
    67  	nameInfo, err := cache.get(name)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	nameInfo.Lock()
    72  	defer nameInfo.Unlock()
    73  	if nameInfo.removed {
    74  		return fmt.Errorf("RemoveName on removed name: %s", name)
    75  	}
    76  	nameInfo.removed = true
    77  	return nil
    78  }
    79  
    80  // Writes whatever is in the cache to the output Writer state. Does not flush the cache, to do that call Reset()
    81  // after Sync or use Flush if your wish to use the output state as your next backend
    82  func (cache *Cache) Sync(state Writer) error {
    83  	cache.Lock()
    84  	defer cache.Unlock()
    85  	// Determine order for names
    86  	// note names may be of any length less than some limit
    87  	var names sort.StringSlice
    88  	for nameStr := range cache.names {
    89  		names = append(names, nameStr)
    90  	}
    91  	sort.Stable(names)
    92  
    93  	// Update or delete names
    94  	for _, name := range names {
    95  		nameInfo := cache.names[name]
    96  		nameInfo.RLock()
    97  		if nameInfo.removed {
    98  			err := state.RemoveName(name)
    99  			if err != nil {
   100  				nameInfo.RUnlock()
   101  				return err
   102  			}
   103  		} else if nameInfo.updated {
   104  			err := state.UpdateName(nameInfo.entry)
   105  			if err != nil {
   106  				nameInfo.RUnlock()
   107  				return err
   108  			}
   109  		}
   110  		nameInfo.RUnlock()
   111  	}
   112  	return nil
   113  }
   114  
   115  // Resets the cache to empty initialising the backing map to the same size as the previous iteration
   116  func (cache *Cache) Reset(backend Reader) {
   117  	cache.Lock()
   118  	defer cache.Unlock()
   119  	cache.backend = backend
   120  	cache.names = make(map[string]*nameInfo)
   121  }
   122  
   123  func (cache *Cache) Backend() Reader {
   124  	return cache.backend
   125  }
   126  
   127  // Get the cache accountInfo item creating it if necessary
   128  func (cache *Cache) get(name string) (*nameInfo, error) {
   129  	cache.RLock()
   130  	nmeInfo := cache.names[name]
   131  	cache.RUnlock()
   132  	if nmeInfo == nil {
   133  		cache.Lock()
   134  		defer cache.Unlock()
   135  		nmeInfo = cache.names[name]
   136  		if nmeInfo == nil {
   137  			entry, err := cache.backend.GetName(name)
   138  			if err != nil {
   139  				return nil, err
   140  			}
   141  			nmeInfo = &nameInfo{
   142  				entry: entry,
   143  			}
   144  			cache.names[name] = nmeInfo
   145  		}
   146  	}
   147  	return nmeInfo, nil
   148  }