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

     1  // Copyright Monax Industries Limited
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package registry
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  
    10  	"github.com/hyperledger/burrow/crypto"
    11  )
    12  
    13  // Cache helps prevent unnecessary IAVLTree updates and garbage generation.
    14  type Cache struct {
    15  	sync.RWMutex
    16  	backend  Reader
    17  	registry map[crypto.Address]*nodeInfo
    18  	stats    NodeStats
    19  }
    20  
    21  type nodeInfo struct {
    22  	sync.RWMutex
    23  	node    *NodeIdentity
    24  	removed bool
    25  	updated bool
    26  }
    27  
    28  var _ Writer = &Cache{}
    29  
    30  // NewCache returns a Cache which can write to an output Writer via Sync.
    31  // Not goroutine safe, use syncStateCache if you need concurrent access
    32  func NewCache(backend Reader) *Cache {
    33  	return &Cache{
    34  		backend:  backend,
    35  		registry: make(map[crypto.Address]*nodeInfo),
    36  		stats:    NewNodeStats(),
    37  	}
    38  }
    39  
    40  func (cache *Cache) GetNodeByID(id crypto.Address) (*NodeIdentity, error) {
    41  	info, err := cache.get(id)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	info.RLock()
    46  	defer info.RUnlock()
    47  	if info.removed {
    48  		return nil, nil
    49  	}
    50  	return info.node, nil
    51  }
    52  
    53  func (cache *Cache) GetNodeIDsByAddress(net string) ([]crypto.Address, error) {
    54  	return cache.stats.GetAddresses(net), nil
    55  }
    56  
    57  func (cache *Cache) GetNumPeers() int {
    58  	return len(cache.registry)
    59  }
    60  
    61  func (cache *Cache) UpdateNode(id crypto.Address, node *NodeIdentity) error {
    62  	info, err := cache.get(id)
    63  	if err != nil {
    64  		return err
    65  	}
    66  	info.Lock()
    67  	defer info.Unlock()
    68  	if info.removed {
    69  		return fmt.Errorf("UpdateNode on a removed node: %x", id)
    70  	}
    71  
    72  	info.node = node
    73  	info.updated = true
    74  	cache.stats.Remove(info.node)
    75  	cache.stats.Insert(node.GetNetworkAddress(), id)
    76  	return nil
    77  }
    78  
    79  func (cache *Cache) RemoveNode(id crypto.Address) error {
    80  	info, err := cache.get(id)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	info.Lock()
    85  	defer info.Unlock()
    86  	if info.removed {
    87  		return fmt.Errorf("RemoveNode on removed node: %x", id)
    88  	}
    89  	cache.stats.Remove(info.node)
    90  	info.removed = true
    91  	return nil
    92  }
    93  
    94  // Sync writes whatever is in the cache to the output state. Does not flush the cache, to do that call Reset()
    95  // after Sync or use Flush if your wish to use the output state as your next backend
    96  func (cache *Cache) Sync(state Writer) error {
    97  	cache.Lock()
    98  	defer cache.Unlock()
    99  
   100  	for id, info := range cache.registry {
   101  		info.RLock()
   102  		if info.removed {
   103  			err := state.RemoveNode(id)
   104  			if err != nil {
   105  				info.RUnlock()
   106  				return err
   107  			}
   108  		} else if info.updated {
   109  			err := state.UpdateNode(id, info.node)
   110  			if err != nil {
   111  				info.RUnlock()
   112  				return err
   113  			}
   114  		}
   115  		info.RUnlock()
   116  	}
   117  	return nil
   118  }
   119  
   120  // Reset the cache to empty initialising the backing map to the same size as the previous iteration
   121  func (cache *Cache) Reset(backend Reader) {
   122  	cache.Lock()
   123  	defer cache.Unlock()
   124  	cache.backend = backend
   125  	cache.registry = make(map[crypto.Address]*nodeInfo)
   126  }
   127  
   128  func (cache *Cache) Backend() Reader {
   129  	return cache.backend
   130  }
   131  
   132  func (cache *Cache) get(id crypto.Address) (*nodeInfo, error) {
   133  	cache.RLock()
   134  	info := cache.registry[id]
   135  	cache.RUnlock()
   136  	if info == nil {
   137  		cache.Lock()
   138  		defer cache.Unlock()
   139  		info = cache.registry[id]
   140  		if info == nil {
   141  			node, err := cache.backend.GetNodeByID(id)
   142  			if err != nil {
   143  				return nil, err
   144  			}
   145  			info = &nodeInfo{
   146  				node: node,
   147  			}
   148  			cache.registry[id] = info
   149  		}
   150  	}
   151  	return info, nil
   152  }