github.com/datachainlab/burrow@v0.25.0/execution/proposal/cache.go (about)

     1  // Copyright 2017 Monax Industries Limited
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package proposal
    16  
    17  import (
    18  	"bytes"
    19  	"crypto/sha256"
    20  	"fmt"
    21  	"sort"
    22  	"sync"
    23  
    24  	"github.com/hyperledger/burrow/txs/payload"
    25  )
    26  
    27  // The Cache helps prevent unnecessary IAVLTree updates and garbage generation.
    28  type Cache struct {
    29  	sync.RWMutex
    30  	backend   Reader
    31  	proposals map[[sha256.Size]byte]*proposalInfo
    32  }
    33  
    34  type proposalInfo struct {
    35  	sync.RWMutex
    36  	ballot  *payload.Ballot
    37  	removed bool
    38  	updated bool
    39  }
    40  
    41  type ProposalHash [sha256.Size]byte
    42  
    43  type ProposalHashArray []ProposalHash
    44  
    45  func (p ProposalHashArray) Len() int {
    46  	return len(p)
    47  }
    48  
    49  func (p ProposalHashArray) Swap(i, j int) {
    50  	p[j], p[i] = p[i], p[j]
    51  }
    52  
    53  func (p ProposalHashArray) Less(i, j int) bool {
    54  	switch bytes.Compare(p[i][:], p[j][:]) {
    55  	case -1:
    56  		return true
    57  	case 0, 1:
    58  		return false
    59  	default:
    60  		panic("bytes.Compare returned invalid value")
    61  	}
    62  }
    63  
    64  var _ Writer = &Cache{}
    65  
    66  // Returns a Cache that wraps an underlying NameRegCacheGetter to use on a cache miss, can write to an
    67  // output Writer via Sync.
    68  // Not goroutine safe, use syncStateCache if you need concurrent access
    69  func NewCache(backend Reader) *Cache {
    70  	return &Cache{
    71  		backend:   backend,
    72  		proposals: make(map[[sha256.Size]byte]*proposalInfo),
    73  	}
    74  }
    75  
    76  func (cache *Cache) GetProposal(proposalHash []byte) (*payload.Ballot, error) {
    77  	proposalInfo, err := cache.get(proposalHash)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	proposalInfo.RLock()
    82  	defer proposalInfo.RUnlock()
    83  	if proposalInfo.removed {
    84  		return nil, nil
    85  	}
    86  	return proposalInfo.ballot, nil
    87  }
    88  
    89  func (cache *Cache) UpdateProposal(proposalHash []byte, ballot *payload.Ballot) error {
    90  	proposalInfo, err := cache.get(proposalHash)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	proposalInfo.Lock()
    95  	defer proposalInfo.Unlock()
    96  	if proposalInfo.removed {
    97  		return fmt.Errorf("UpdateProposal on a removed proposal: %x", proposalHash)
    98  	}
    99  
   100  	proposalInfo.ballot = ballot
   101  	proposalInfo.updated = true
   102  	return nil
   103  }
   104  
   105  func (cache *Cache) RemoveProposal(proposalHash []byte) error {
   106  	proposalInfo, err := cache.get(proposalHash)
   107  	if err != nil {
   108  		return err
   109  	}
   110  	proposalInfo.Lock()
   111  	defer proposalInfo.Unlock()
   112  	if proposalInfo.removed {
   113  		return fmt.Errorf("RemoveProposal on removed proposal: %x", proposalHash)
   114  	}
   115  	proposalInfo.removed = true
   116  	return nil
   117  }
   118  
   119  // Writes whatever is in the cache to the output Writer state. Does not flush the cache, to do that call Reset()
   120  // after Sync or use Flusth if your wish to use the output state as your next backend
   121  func (cache *Cache) Sync(state Writer) error {
   122  	cache.Lock()
   123  	defer cache.Unlock()
   124  	// Determine order for names
   125  	// note names may be of any length less than some limit
   126  	var hashes ProposalHashArray
   127  	for hash := range cache.proposals {
   128  		hashes = append(hashes, hash)
   129  	}
   130  	sort.Stable(hashes)
   131  
   132  	// Update or delete names.
   133  	for _, hash := range hashes {
   134  		proposalInfo := cache.proposals[hash]
   135  		proposalInfo.RLock()
   136  		if proposalInfo.removed {
   137  			err := state.RemoveProposal(hash[:])
   138  			if err != nil {
   139  				proposalInfo.RUnlock()
   140  				return err
   141  			}
   142  		} else if proposalInfo.updated {
   143  			err := state.UpdateProposal(hash[:], proposalInfo.ballot)
   144  			if err != nil {
   145  				proposalInfo.RUnlock()
   146  				return err
   147  			}
   148  		}
   149  		proposalInfo.RUnlock()
   150  	}
   151  	return nil
   152  }
   153  
   154  // Resets the cache to empty initialising the backing map to the same size as the previous iteration.
   155  func (cache *Cache) Reset(backend Reader) {
   156  	cache.Lock()
   157  	defer cache.Unlock()
   158  	cache.backend = backend
   159  	cache.proposals = make(map[[sha256.Size]byte]*proposalInfo)
   160  }
   161  
   162  // Syncs the Cache and Resets it to use Writer as the backend Reader
   163  func (cache *Cache) Flush(output Writer, backend Reader) error {
   164  	err := cache.Sync(output)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	cache.Reset(backend)
   169  	return nil
   170  }
   171  
   172  func (cache *Cache) Backend() Reader {
   173  	return cache.backend
   174  }
   175  
   176  // Get the cache accountInfo item creating it if necessary
   177  func (cache *Cache) get(proposalHash []byte) (*proposalInfo, error) {
   178  	var hash ProposalHash
   179  	copy(hash[:], proposalHash)
   180  	cache.RLock()
   181  	propInfo := cache.proposals[hash]
   182  	cache.RUnlock()
   183  	if propInfo == nil {
   184  		cache.Lock()
   185  		defer cache.Unlock()
   186  		propInfo = cache.proposals[hash]
   187  		if propInfo == nil {
   188  			prop, err := cache.backend.GetProposal(proposalHash)
   189  			if err != nil {
   190  				return nil, err
   191  			}
   192  			propInfo = &proposalInfo{
   193  				ballot: prop,
   194  			}
   195  			cache.proposals[hash] = propInfo
   196  		}
   197  	}
   198  	return propInfo, nil
   199  }