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

     1  // Copyright Monax Industries Limited
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package proposal
     5  
     6  import (
     7  	"bytes"
     8  	"crypto/sha256"
     9  	"fmt"
    10  	"sort"
    11  	"sync"
    12  
    13  	"github.com/hyperledger/burrow/txs/payload"
    14  )
    15  
    16  // Cache helps prevent unnecessary IAVLTree updates and garbage generation.
    17  type Cache struct {
    18  	sync.RWMutex
    19  	backend   Reader
    20  	proposals map[[sha256.Size]byte]*proposalInfo
    21  }
    22  
    23  type proposalInfo struct {
    24  	sync.RWMutex
    25  	ballot  *payload.Ballot
    26  	removed bool
    27  	updated bool
    28  }
    29  
    30  type ProposalHash [sha256.Size]byte
    31  
    32  type ProposalHashArray []ProposalHash
    33  
    34  func (p ProposalHashArray) Len() int {
    35  	return len(p)
    36  }
    37  
    38  func (p ProposalHashArray) Swap(i, j int) {
    39  	p[j], p[i] = p[i], p[j]
    40  }
    41  
    42  func (p ProposalHashArray) Less(i, j int) bool {
    43  	switch bytes.Compare(p[i][:], p[j][:]) {
    44  	case -1:
    45  		return true
    46  	case 0, 1:
    47  		return false
    48  	default:
    49  		panic("bytes.Compare returned invalid value")
    50  	}
    51  }
    52  
    53  var _ Writer = &Cache{}
    54  
    55  // Returns a Cache, can write to an output Writer via Sync.
    56  // Not goroutine safe, use syncStateCache if you need concurrent access
    57  func NewCache(backend Reader) *Cache {
    58  	return &Cache{
    59  		backend:   backend,
    60  		proposals: make(map[[sha256.Size]byte]*proposalInfo),
    61  	}
    62  }
    63  
    64  func (cache *Cache) GetProposal(proposalHash []byte) (*payload.Ballot, error) {
    65  	proposalInfo, err := cache.get(proposalHash)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	proposalInfo.RLock()
    70  	defer proposalInfo.RUnlock()
    71  	if proposalInfo.removed {
    72  		return nil, nil
    73  	}
    74  	return proposalInfo.ballot, nil
    75  }
    76  
    77  func (cache *Cache) UpdateProposal(proposalHash []byte, ballot *payload.Ballot) error {
    78  	proposalInfo, err := cache.get(proposalHash)
    79  	if err != nil {
    80  		return err
    81  	}
    82  	proposalInfo.Lock()
    83  	defer proposalInfo.Unlock()
    84  	if proposalInfo.removed {
    85  		return fmt.Errorf("UpdateProposal on a removed proposal: %x", proposalHash)
    86  	}
    87  
    88  	proposalInfo.ballot = ballot
    89  	proposalInfo.updated = true
    90  	return nil
    91  }
    92  
    93  func (cache *Cache) RemoveProposal(proposalHash []byte) error {
    94  	proposalInfo, err := cache.get(proposalHash)
    95  	if err != nil {
    96  		return err
    97  	}
    98  	proposalInfo.Lock()
    99  	defer proposalInfo.Unlock()
   100  	if proposalInfo.removed {
   101  		return fmt.Errorf("RemoveProposal on removed proposal: %x", proposalHash)
   102  	}
   103  	proposalInfo.removed = true
   104  	return nil
   105  }
   106  
   107  // Writes whatever is in the cache to the output Writer state. Does not flush the cache, to do that call Reset()
   108  // after Sync or use Flush if your wish to use the output state as your next backend
   109  func (cache *Cache) Sync(state Writer) error {
   110  	cache.Lock()
   111  	defer cache.Unlock()
   112  	var hashes ProposalHashArray
   113  	for hash := range cache.proposals {
   114  		hashes = append(hashes, hash)
   115  	}
   116  	sort.Stable(hashes)
   117  
   118  	// Update or delete proposals
   119  	for _, hash := range hashes {
   120  		proposalInfo := cache.proposals[hash]
   121  		proposalInfo.RLock()
   122  		if proposalInfo.removed {
   123  			err := state.RemoveProposal(hash[:])
   124  			if err != nil {
   125  				proposalInfo.RUnlock()
   126  				return err
   127  			}
   128  		} else if proposalInfo.updated {
   129  			err := state.UpdateProposal(hash[:], proposalInfo.ballot)
   130  			if err != nil {
   131  				proposalInfo.RUnlock()
   132  				return err
   133  			}
   134  		}
   135  		proposalInfo.RUnlock()
   136  	}
   137  	return nil
   138  }
   139  
   140  // Resets the cache to empty initialising the backing map to the same size as the previous iteration
   141  func (cache *Cache) Reset(backend Reader) {
   142  	cache.Lock()
   143  	defer cache.Unlock()
   144  	cache.backend = backend
   145  	cache.proposals = make(map[[sha256.Size]byte]*proposalInfo)
   146  }
   147  
   148  func (cache *Cache) Backend() Reader {
   149  	return cache.backend
   150  }
   151  
   152  // Get the cache accountInfo item creating it if necessary
   153  func (cache *Cache) get(proposalHash []byte) (*proposalInfo, error) {
   154  	var hash ProposalHash
   155  	copy(hash[:], proposalHash)
   156  	cache.RLock()
   157  	propInfo := cache.proposals[hash]
   158  	cache.RUnlock()
   159  	if propInfo == nil {
   160  		cache.Lock()
   161  		defer cache.Unlock()
   162  		propInfo = cache.proposals[hash]
   163  		if propInfo == nil {
   164  			prop, err := cache.backend.GetProposal(proposalHash)
   165  			if err != nil {
   166  				return nil, err
   167  			}
   168  			propInfo = &proposalInfo{
   169  				ballot: prop,
   170  			}
   171  			cache.proposals[hash] = propInfo
   172  		}
   173  	}
   174  	return propInfo, nil
   175  }