github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/store/cache/store.go (about)

     1  package cache
     2  
     3  import (
     4  	"bytes"
     5  	"container/list"
     6  	"sort"
     7  	"sync"
     8  
     9  	dbm "github.com/gnolang/gno/tm2/pkg/db"
    10  	"github.com/gnolang/gno/tm2/pkg/std"
    11  
    12  	"github.com/gnolang/gno/tm2/pkg/store/types"
    13  )
    14  
    15  // If value is nil but deleted is false, it means the parent doesn't have the
    16  // key.  (No need to delete upon Write())
    17  type cValue struct {
    18  	value   []byte
    19  	deleted bool
    20  	dirty   bool
    21  }
    22  
    23  // cacheStore wraps an in-memory cache around an underlying types.Store.
    24  type cacheStore struct {
    25  	mtx           sync.Mutex
    26  	cache         map[string]*cValue
    27  	unsortedCache map[string]struct{}
    28  	sortedCache   *list.List // always ascending sorted
    29  	parent        types.Store
    30  }
    31  
    32  var _ types.Store = (*cacheStore)(nil)
    33  
    34  func New(parent types.Store) *cacheStore {
    35  	return &cacheStore{
    36  		cache:         make(map[string]*cValue),
    37  		unsortedCache: make(map[string]struct{}),
    38  		sortedCache:   list.New(),
    39  		parent:        parent,
    40  	}
    41  }
    42  
    43  // Implements types.Store.
    44  func (store *cacheStore) Get(key []byte) (value []byte) {
    45  	store.mtx.Lock()
    46  	defer store.mtx.Unlock()
    47  	types.AssertValidKey(key)
    48  
    49  	cacheValue, ok := store.cache[string(key)]
    50  	if !ok {
    51  		value = store.parent.Get(key)
    52  		store.setCacheValue(key, value, false, false)
    53  	} else {
    54  		value = cacheValue.value
    55  	}
    56  
    57  	return value
    58  }
    59  
    60  // Implements types.Store.
    61  func (store *cacheStore) Set(key []byte, value []byte) {
    62  	store.mtx.Lock()
    63  	defer store.mtx.Unlock()
    64  	types.AssertValidKey(key)
    65  	types.AssertValidValue(value)
    66  
    67  	store.setCacheValue(key, value, false, true)
    68  }
    69  
    70  // Implements types.Store.
    71  func (store *cacheStore) Has(key []byte) bool {
    72  	value := store.Get(key)
    73  	return value != nil
    74  }
    75  
    76  // Implements types.Store.
    77  func (store *cacheStore) Delete(key []byte) {
    78  	store.mtx.Lock()
    79  	defer store.mtx.Unlock()
    80  	types.AssertValidKey(key)
    81  
    82  	store.setCacheValue(key, nil, true, true)
    83  }
    84  
    85  // Implements types.Store.
    86  func (store *cacheStore) Write() {
    87  	store.mtx.Lock()
    88  	defer store.mtx.Unlock()
    89  
    90  	// We need a copy of all of the keys.
    91  	// Not the best, but probably not a bottleneck depending.
    92  	keys := make([]string, 0, len(store.cache))
    93  	for key, dbValue := range store.cache {
    94  		if dbValue.dirty {
    95  			keys = append(keys, key)
    96  		}
    97  	}
    98  
    99  	sort.Strings(keys)
   100  
   101  	// TODO: Consider allowing usage of Batch, which would allow the write to
   102  	// at least happen atomically.
   103  	for _, key := range keys {
   104  		cacheValue := store.cache[key]
   105  		if cacheValue.deleted {
   106  			store.parent.Delete([]byte(key))
   107  		} else if cacheValue.value == nil {
   108  			// Skip, it already doesn't exist in parent.
   109  		} else {
   110  			store.parent.Set([]byte(key), cacheValue.value)
   111  		}
   112  	}
   113  
   114  	// Clear the cache
   115  	store.cache = make(map[string]*cValue)
   116  	store.unsortedCache = make(map[string]struct{})
   117  	store.sortedCache = list.New()
   118  }
   119  
   120  // ----------------------------------------
   121  // To cache-wrap this Store further.
   122  
   123  // Implements Store.
   124  func (store *cacheStore) CacheWrap() types.Store {
   125  	return New(store)
   126  }
   127  
   128  // ----------------------------------------
   129  // Iteration
   130  
   131  // Implements types.Store.
   132  func (store *cacheStore) Iterator(start, end []byte) types.Iterator {
   133  	return store.iterator(start, end, true)
   134  }
   135  
   136  // Implements types.Store.
   137  func (store *cacheStore) ReverseIterator(start, end []byte) types.Iterator {
   138  	return store.iterator(start, end, false)
   139  }
   140  
   141  func (store *cacheStore) iterator(start, end []byte, ascending bool) types.Iterator {
   142  	store.mtx.Lock()
   143  	defer store.mtx.Unlock()
   144  
   145  	var parent, cache types.Iterator
   146  
   147  	if ascending {
   148  		parent = store.parent.Iterator(start, end)
   149  	} else {
   150  		parent = store.parent.ReverseIterator(start, end)
   151  	}
   152  
   153  	store.dirtyItems(start, end)
   154  	cache = newMemIterator(start, end, store.sortedCache, ascending)
   155  
   156  	return newCacheMergeIterator(parent, cache, ascending)
   157  }
   158  
   159  // Constructs a slice of dirty items, to use w/ memIterator.
   160  func (store *cacheStore) dirtyItems(start, end []byte) {
   161  	unsorted := make([]*std.KVPair, 0)
   162  
   163  	for key := range store.unsortedCache {
   164  		cacheValue := store.cache[key]
   165  		if dbm.IsKeyInDomain([]byte(key), start, end) {
   166  			unsorted = append(unsorted, &std.KVPair{Key: []byte(key), Value: cacheValue.value})
   167  			delete(store.unsortedCache, key)
   168  		}
   169  	}
   170  
   171  	sort.Slice(unsorted, func(i, j int) bool {
   172  		return bytes.Compare(unsorted[i].Key, unsorted[j].Key) < 0
   173  	})
   174  
   175  	// #nosec G602
   176  	for e := store.sortedCache.Front(); e != nil && len(unsorted) != 0; {
   177  		uitem := unsorted[0]
   178  		sitem := e.Value.(*std.KVPair)
   179  		comp := bytes.Compare(uitem.Key, sitem.Key)
   180  		switch comp {
   181  		case -1:
   182  			unsorted = unsorted[1:]
   183  			store.sortedCache.InsertBefore(uitem, e)
   184  		case 1:
   185  			e = e.Next()
   186  		case 0:
   187  			unsorted = unsorted[1:]
   188  			e.Value = uitem
   189  			e = e.Next()
   190  		}
   191  	}
   192  
   193  	for _, kvp := range unsorted {
   194  		store.sortedCache.PushBack(kvp)
   195  	}
   196  }
   197  
   198  // ----------------------------------------
   199  // etc
   200  
   201  // Only entrypoint to mutate store.cache.
   202  func (store *cacheStore) setCacheValue(key, value []byte, deleted bool, dirty bool) {
   203  	store.cache[string(key)] = &cValue{
   204  		value:   value,
   205  		deleted: deleted,
   206  		dirty:   dirty,
   207  	}
   208  	if dirty {
   209  		store.unsortedCache[string(key)] = struct{}{}
   210  	}
   211  }