Skip to content

Commit 6b70b36

Browse files
committed
clean-up mutexes
1 parent bc1b221 commit 6b70b36

File tree

2 files changed

+24
-92
lines changed

2 files changed

+24
-92
lines changed

block/internal/cache/generic_cache.go

Lines changed: 21 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,24 @@ const (
3131

3232
// Cache is a generic cache that maintains items that are seen and hard confirmed.
3333
// Uses bounded LRU caches to prevent unbounded memory growth.
34+
// The underlying LRU caches are thread-safe. Mutexes are only used where
35+
// compound operations require atomicity.
3436
type Cache[T any] struct {
35-
// itemsByHeight stores items keyed by uint64 height
37+
// itemsByHeight stores items keyed by uint64 height.
38+
// Mutex needed for atomic get-and-remove in getNextItem.
3639
itemsByHeight *lru.Cache[uint64, *T]
37-
itemsByHeightMu sync.RWMutex
40+
itemsByHeightMu sync.Mutex
3841

3942
// hashes tracks whether a given hash has been seen
40-
hashes *lru.Cache[string, bool]
41-
hashesMu sync.RWMutex
43+
hashes *lru.Cache[string, bool]
4244

4345
// daIncluded tracks the DA inclusion height for a given hash
44-
daIncluded *lru.Cache[string, uint64]
45-
daIncludedMu sync.RWMutex
46+
daIncluded *lru.Cache[string, uint64]
4647

47-
// hashByHeight tracks the hash associated with each height for pruning
48+
// hashByHeight tracks the hash associated with each height for pruning.
49+
// Mutex needed for atomic operations in deleteAllForHeight.
4850
hashByHeight *lru.Cache[uint64, string]
49-
hashByHeightMu sync.RWMutex
51+
hashByHeightMu sync.Mutex
5052

5153
// maxDAHeight tracks the maximum DA height seen
5254
maxDAHeight *atomic.Uint64
@@ -111,9 +113,6 @@ func NewCacheWithConfig[T any](config CacheConfig) (*Cache[T], error) {
111113
// getItem returns an item from the cache by height.
112114
// Returns nil if not found or type mismatch.
113115
func (c *Cache[T]) getItem(height uint64) *T {
114-
c.itemsByHeightMu.RLock()
115-
defer c.itemsByHeightMu.RUnlock()
116-
117116
item, ok := c.itemsByHeight.Get(height)
118117
if !ok {
119118
return nil
@@ -123,9 +122,6 @@ func (c *Cache[T]) getItem(height uint64) *T {
123122

124123
// setItem sets an item in the cache by height
125124
func (c *Cache[T]) setItem(height uint64, item *T) {
126-
c.itemsByHeightMu.Lock()
127-
defer c.itemsByHeightMu.Unlock()
128-
129125
c.itemsByHeight.Add(height, item)
130126
}
131127

@@ -145,9 +141,6 @@ func (c *Cache[T]) getNextItem(height uint64) *T {
145141

146142
// isSeen returns true if the hash has been seen
147143
func (c *Cache[T]) isSeen(hash string) bool {
148-
c.hashesMu.RLock()
149-
defer c.hashesMu.RUnlock()
150-
151144
seen, ok := c.hashes.Get(hash)
152145
if !ok {
153146
return false
@@ -157,20 +150,12 @@ func (c *Cache[T]) isSeen(hash string) bool {
157150

158151
// setSeen sets the hash as seen and tracks its height for pruning
159152
func (c *Cache[T]) setSeen(hash string, height uint64) {
160-
c.hashesMu.Lock()
161153
c.hashes.Add(hash, true)
162-
c.hashesMu.Unlock()
163-
164-
c.hashByHeightMu.Lock()
165154
c.hashByHeight.Add(height, hash)
166-
c.hashByHeightMu.Unlock()
167155
}
168156

169157
// getDAIncluded returns the DA height if the hash has been DA-included, otherwise it returns 0.
170158
func (c *Cache[T]) getDAIncluded(hash string) (uint64, bool) {
171-
c.daIncludedMu.RLock()
172-
defer c.daIncludedMu.RUnlock()
173-
174159
daHeight, ok := c.daIncluded.Get(hash)
175160
if !ok {
176161
return 0, false
@@ -180,13 +165,8 @@ func (c *Cache[T]) getDAIncluded(hash string) (uint64, bool) {
180165

181166
// setDAIncluded sets the hash as DA-included with the given DA height and tracks block height for pruning
182167
func (c *Cache[T]) setDAIncluded(hash string, daHeight uint64, blockHeight uint64) {
183-
c.daIncludedMu.Lock()
184168
c.daIncluded.Add(hash, daHeight)
185-
c.daIncludedMu.Unlock()
186-
187-
c.hashByHeightMu.Lock()
188169
c.hashByHeight.Add(blockHeight, hash)
189-
c.hashByHeightMu.Unlock()
190170

191171
// Update max DA height if necessary
192172
for range 1_000 {
@@ -202,9 +182,6 @@ func (c *Cache[T]) setDAIncluded(hash string, daHeight uint64, blockHeight uint6
202182

203183
// removeDAIncluded removes the DA-included status of the hash
204184
func (c *Cache[T]) removeDAIncluded(hash string) {
205-
c.daIncludedMu.Lock()
206-
defer c.daIncludedMu.Unlock()
207-
208185
c.daIncluded.Remove(hash)
209186
}
210187

@@ -216,30 +193,24 @@ func (c *Cache[T]) daHeight() uint64 {
216193

217194
// removeSeen removes a hash from the seen cache.
218195
func (c *Cache[T]) removeSeen(hash string) {
219-
c.hashesMu.Lock()
220-
defer c.hashesMu.Unlock()
221196
c.hashes.Remove(hash)
222197
}
223198

224199
// forEachHash iterates over all hashes in the seen cache and calls the provided function.
225200
// If the function returns false, iteration stops.
201+
// Note: iteration is best-effort; concurrent modifications may not be reflected.
226202
func (c *Cache[T]) forEachHash(fn func(hash string) bool) {
227-
c.hashesMu.RLock()
228203
keys := c.hashes.Keys()
229-
c.hashesMu.RUnlock()
230-
231204
for _, hash := range keys {
232205
if !fn(hash) {
233206
break
234207
}
235208
}
236209
}
237210

238-
// deleteAllForHeight removes all items and their associated data from the cache at the given height
211+
// deleteAllForHeight removes all items and their associated data from the cache at the given height.
239212
func (c *Cache[T]) deleteAllForHeight(height uint64) {
240-
c.itemsByHeightMu.Lock()
241213
c.itemsByHeight.Remove(height)
242-
c.itemsByHeightMu.Unlock()
243214

244215
c.hashByHeightMu.Lock()
245216
hash, ok := c.hashByHeight.Get(height)
@@ -249,9 +220,7 @@ func (c *Cache[T]) deleteAllForHeight(height uint64) {
249220
c.hashByHeightMu.Unlock()
250221

251222
if ok {
252-
c.hashesMu.Lock()
253223
c.hashes.Remove(hash)
254-
c.hashesMu.Unlock()
255224
// c.daIncluded.Remove(hash) // we actually do not want to delete the DA-included status here
256225
}
257226
}
@@ -307,62 +276,55 @@ func (c *Cache[T]) SaveToDisk(folderPath string) error {
307276
return fmt.Errorf("failed to create directory %s: %w", folderPath, err)
308277
}
309278
var wg errgroup.Group
310-
// prepare items maps
279+
280+
// save items by height
311281
wg.Go(func() error {
312282
itemsByHeightMap := make(map[uint64]*T)
313-
314-
c.itemsByHeightMu.RLock()
315283
keys := c.itemsByHeight.Keys()
316284
for _, k := range keys {
317285
if v, ok := c.itemsByHeight.Peek(k); ok {
318286
itemsByHeightMap[k] = v
319287
}
320288
}
321-
c.itemsByHeightMu.RUnlock()
322289

323290
if err := saveMapGob(filepath.Join(folderPath, itemsByHeightFilename), itemsByHeightMap); err != nil {
324291
return fmt.Errorf("save %s: %w", itemsByHeightFilename, err)
325292
}
326293
return nil
327294
})
328295

329-
// prepare hashes map
296+
// save hashes
330297
wg.Go(func() error {
331298
hashesToSave := make(map[string]bool)
332-
333-
c.hashesMu.RLock()
334299
keys := c.hashes.Keys()
335300
for _, k := range keys {
336301
if v, ok := c.hashes.Peek(k); ok {
337302
hashesToSave[k] = v
338303
}
339304
}
340-
c.hashesMu.RUnlock()
341305

342306
if err := saveMapGob(filepath.Join(folderPath, hashesFilename), hashesToSave); err != nil {
343307
return fmt.Errorf("save %s: %w", hashesFilename, err)
344308
}
345309
return nil
346310
})
347311

348-
// prepare daIncluded map
312+
// save daIncluded
349313
wg.Go(func() error {
350314
daIncludedToSave := make(map[string]uint64)
351-
352-
c.daIncludedMu.RLock()
353315
keys := c.daIncluded.Keys()
354316
for _, k := range keys {
355317
if v, ok := c.daIncluded.Peek(k); ok {
356318
daIncludedToSave[k] = v
357319
}
358320
}
359-
c.daIncludedMu.RUnlock()
360321

361322
if err := saveMapGob(filepath.Join(folderPath, daIncludedFilename), daIncludedToSave); err != nil {
362323
return fmt.Errorf("save %s: %w", daIncludedFilename, err)
363324
}
364325
return nil
365326
})
327+
366328
return wg.Wait()
367329
}
368330

@@ -372,39 +334,37 @@ func (c *Cache[T]) SaveToDisk(folderPath string) error {
372334
// are registered with the gob package if necessary (e.g., using gob.Register).
373335
func (c *Cache[T]) LoadFromDisk(folderPath string) error {
374336
var wg errgroup.Group
337+
375338
// load items by height
376339
wg.Go(func() error {
377340
itemsByHeightMap, err := loadMapGob[uint64, *T](filepath.Join(folderPath, itemsByHeightFilename))
378341
if err != nil {
379342
return fmt.Errorf("failed to load %s : %w", itemsByHeightFilename, err)
380343
}
381-
c.itemsByHeightMu.Lock()
382344
for k, v := range itemsByHeightMap {
383345
c.itemsByHeight.Add(k, v)
384346
}
385-
c.itemsByHeightMu.Unlock()
386347
return nil
387348
})
349+
388350
// load hashes
389351
wg.Go(func() error {
390352
hashesMap, err := loadMapGob[string, bool](filepath.Join(folderPath, hashesFilename))
391353
if err != nil {
392354
return fmt.Errorf("failed to load %s : %w", hashesFilename, err)
393355
}
394-
c.hashesMu.Lock()
395356
for k, v := range hashesMap {
396357
c.hashes.Add(k, v)
397358
}
398-
c.hashesMu.Unlock()
399359
return nil
400360
})
361+
401362
// load daIncluded
402363
wg.Go(func() error {
403364
daIncludedMap, err := loadMapGob[string, uint64](filepath.Join(folderPath, daIncludedFilename))
404365
if err != nil {
405366
return fmt.Errorf("failed to load %s : %w", daIncludedFilename, err)
406367
}
407-
c.daIncludedMu.Lock()
408368
for k, v := range daIncludedMap {
409369
c.daIncluded.Add(k, v)
410370
// Update max DA height during load
@@ -413,8 +373,8 @@ func (c *Cache[T]) LoadFromDisk(folderPath string) error {
413373
c.maxDAHeight.Store(v)
414374
}
415375
}
416-
c.daIncludedMu.Unlock()
417376
return nil
418377
})
378+
419379
return wg.Wait()
420380
}

pkg/store/cached_store.go

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package store
22

33
import (
44
"context"
5-
"sync"
65

76
lru "github.com/hashicorp/golang-lru/v2"
87

@@ -20,15 +19,12 @@ const (
2019
)
2120

2221
// CachedStore wraps a Store with LRU caching for frequently accessed data.
22+
// The underlying LRU cache is thread-safe, so no additional synchronization is needed.
2323
type CachedStore struct {
2424
Store
2525

26-
headerCache *lru.Cache[uint64, *types.SignedHeader]
27-
headerCacheMu sync.RWMutex
28-
29-
// Optional: cache for block data (headers + data together)
30-
blockDataCache *lru.Cache[uint64, *blockDataEntry]
31-
blockDataCacheMu sync.RWMutex
26+
headerCache *lru.Cache[uint64, *types.SignedHeader]
27+
blockDataCache *lru.Cache[uint64, *blockDataEntry]
3228
}
3329

3430
type blockDataEntry struct {
@@ -93,12 +89,9 @@ func NewCachedStore(store Store, opts ...CachedStoreOption) (*CachedStore, error
9389
// GetHeader returns the header at the given height, using the cache if available.
9490
func (cs *CachedStore) GetHeader(ctx context.Context, height uint64) (*types.SignedHeader, error) {
9591
// Try cache first
96-
cs.headerCacheMu.RLock()
9792
if header, ok := cs.headerCache.Get(height); ok {
98-
cs.headerCacheMu.RUnlock()
9993
return header, nil
10094
}
101-
cs.headerCacheMu.RUnlock()
10295

10396
// Cache miss, fetch from underlying store
10497
header, err := cs.Store.GetHeader(ctx, height)
@@ -107,22 +100,17 @@ func (cs *CachedStore) GetHeader(ctx context.Context, height uint64) (*types.Sig
107100
}
108101

109102
// Add to cache
110-
cs.headerCacheMu.Lock()
111103
cs.headerCache.Add(height, header)
112-
cs.headerCacheMu.Unlock()
113104

114105
return header, nil
115106
}
116107

117108
// GetBlockData returns block header and data at given height, using cache if available.
118109
func (cs *CachedStore) GetBlockData(ctx context.Context, height uint64) (*types.SignedHeader, *types.Data, error) {
119110
// Try cache first
120-
cs.blockDataCacheMu.RLock()
121111
if entry, ok := cs.blockDataCache.Get(height); ok {
122-
cs.blockDataCacheMu.RUnlock()
123112
return entry.header, entry.data, nil
124113
}
125-
cs.blockDataCacheMu.RUnlock()
126114

127115
// Cache miss, fetch from underlying store
128116
header, data, err := cs.Store.GetBlockData(ctx, height)
@@ -131,42 +119,26 @@ func (cs *CachedStore) GetBlockData(ctx context.Context, height uint64) (*types.
131119
}
132120

133121
// Add to cache
134-
cs.blockDataCacheMu.Lock()
135122
cs.blockDataCache.Add(height, &blockDataEntry{header: header, data: data})
136-
cs.blockDataCacheMu.Unlock()
137123

138124
// Also add header to header cache
139-
cs.headerCacheMu.Lock()
140125
cs.headerCache.Add(height, header)
141-
cs.headerCacheMu.Unlock()
142126

143127
return header, data, nil
144128
}
145129

146130
// InvalidateRange removes headers in the given range from the cache.
147131
func (cs *CachedStore) InvalidateRange(fromHeight, toHeight uint64) {
148-
cs.headerCacheMu.Lock()
149132
for h := fromHeight; h <= toHeight; h++ {
150133
cs.headerCache.Remove(h)
151-
}
152-
cs.headerCacheMu.Unlock()
153-
154-
cs.blockDataCacheMu.Lock()
155-
for h := fromHeight; h <= toHeight; h++ {
156134
cs.blockDataCache.Remove(h)
157135
}
158-
cs.blockDataCacheMu.Unlock()
159136
}
160137

161138
// ClearCache clears all cached entries.
162139
func (cs *CachedStore) ClearCache() {
163-
cs.headerCacheMu.Lock()
164140
cs.headerCache.Purge()
165-
cs.headerCacheMu.Unlock()
166-
167-
cs.blockDataCacheMu.Lock()
168141
cs.blockDataCache.Purge()
169-
cs.blockDataCacheMu.Unlock()
170142
}
171143

172144
// Rollback wraps the underlying store's Rollback and invalidates affected cache entries.

0 commit comments

Comments
 (0)