@@ -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.
3436type 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.
113115func (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
125124func (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
147143func (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
159152func (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.
170158func (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
182167func (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
204184func (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.
218195func (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.
226202func (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.
239212func (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).
373335func (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}
0 commit comments