Skip to content

Commit 88156ba

Browse files
committed
fix iterator race + possible race in checkreloading
1 parent edaa5db commit 88156ba

File tree

1 file changed

+70
-90
lines changed

1 file changed

+70
-90
lines changed

x/staking/cache/cache.go

Lines changed: 70 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type Entry[V ~[]E, E any] struct {
2929

3030
dirty atomic.Bool
3131
full atomic.Bool
32+
count atomic.Uint64 // track number of entries only if max > 0
3233

3334
max uint
3435

@@ -122,6 +123,15 @@ func (e *Entry[V, E]) setEntryUnsafe(ctx context.Context, cdc codec.BinaryCodec,
122123
store := e.storeService.OpenMemoryStore(ctx)
123124
storeKey := e.getStoreKey(key)
124125

126+
exists := false
127+
if e.max > 0 {
128+
existingBz, err := store.Get(storeKey)
129+
if err != nil {
130+
return err
131+
}
132+
exists = existingBz != nil
133+
}
134+
125135
bz, err := marshal(cdc, e.cacheType, value)
126136
if err != nil {
127137
return err
@@ -131,12 +141,10 @@ func (e *Entry[V, E]) setEntryUnsafe(ctx context.Context, cdc codec.BinaryCodec,
131141
return err
132142
}
133143

134-
if e.max > 0 {
135-
count, err := e.countEntries(ctx)
136-
if err != nil {
137-
return err
138-
}
139-
if count >= e.max {
144+
// Only increment counter if this is a new key
145+
if e.max > 0 && !exists {
146+
newCount := e.count.Add(1)
147+
if newCount >= uint64(e.max) {
140148
e.full.Store(true)
141149
}
142150
}
@@ -151,16 +159,23 @@ func (e *Entry[V, E]) deleteEntry(ctx context.Context, key string) error {
151159
store := e.storeService.OpenMemoryStore(ctx)
152160
storeKey := e.getStoreKey(key)
153161

154-
if err := store.Delete(storeKey); err != nil {
155-
return err
156-
}
157-
162+
exists := false
158163
if e.max > 0 {
159-
count, err := e.countEntries(ctx)
164+
existingBz, err := store.Get(storeKey)
160165
if err != nil {
161166
return err
162167
}
163-
if count < e.max {
168+
exists = existingBz != nil
169+
}
170+
171+
if err := store.Delete(storeKey); err != nil {
172+
return err
173+
}
174+
175+
// Only decrement counter if the key actually existed
176+
if e.max > 0 && exists {
177+
newCount := e.count.Add(^uint64(0)) // Subtract 1 using two's complement
178+
if newCount < uint64(e.max) {
164179
e.full.Store(false)
165180
}
166181
}
@@ -184,27 +199,11 @@ func (e *Entry[V, E]) clearUnsafe(ctx context.Context) error {
184199
}
185200
}
186201

202+
e.count.Store(0)
187203
e.full.Store(false)
188204
return nil
189205
}
190206

191-
func (e *Entry[V, E]) countEntries(ctx context.Context) (uint, error) {
192-
store := e.storeService.OpenMemoryStore(ctx)
193-
prefix := e.getPrefix()
194-
iter, err := store.Iterator(prefix, storetypes.PrefixEndBytes(prefix))
195-
if err != nil {
196-
return 0, err
197-
}
198-
defer iter.Close()
199-
200-
count := uint(0)
201-
for ; iter.Valid(); iter.Next() {
202-
count++
203-
}
204-
205-
return count, nil
206-
}
207-
208207
func (e *Entry[V, E]) getStoreKey(key string) []byte {
209208
prefix := e.getPrefix()
210209
return append(prefix, []byte(key)...)
@@ -316,34 +315,29 @@ func NewCache(
316315
// Unbonding Validators Queue
317316

318317
func (c *ValidatorsQueueCache) checkReloadUnbondingValidatorsQueue(ctx context.Context) error {
318+
c.unbondingValidatorsQueue.mu.Lock()
319+
defer c.unbondingValidatorsQueue.mu.Unlock()
320+
319321
if c.unbondingValidatorsQueue.dirty.Load() {
320322
c.logger(ctx).Info("Unbonding validators queue is dirty. Reinitializing cache from store.")
321-
if err := c.loadUnbondingValidatorsQueue(ctx); err != nil {
323+
data, err := c.unbondingValidatorsQueue.loadFromStore(ctx)
324+
if err != nil {
322325
return err
323326
}
324-
}
325-
return nil
326-
}
327327

328-
func (c *ValidatorsQueueCache) loadUnbondingValidatorsQueue(ctx context.Context) error {
329-
data, err := c.unbondingValidatorsQueue.loadFromStore(ctx)
330-
if err != nil {
331-
return err
332-
}
333-
c.unbondingValidatorsQueue.mu.Lock()
334-
defer c.unbondingValidatorsQueue.mu.Unlock()
335-
336-
if err := c.unbondingValidatorsQueue.clearUnsafe(ctx); err != nil {
337-
return err
338-
}
339-
340-
for key, value := range data {
341-
if err := c.unbondingValidatorsQueue.setEntryUnsafe(ctx, c.cdc, key, value); err != nil {
328+
if err := c.unbondingValidatorsQueue.clearUnsafe(ctx); err != nil {
342329
return err
343330
}
331+
332+
for key, value := range data {
333+
if err := c.unbondingValidatorsQueue.setEntryUnsafe(ctx, c.cdc, key, value); err != nil {
334+
return err
335+
}
336+
}
337+
338+
c.unbondingValidatorsQueue.dirty.Store(false)
344339
}
345340

346-
c.unbondingValidatorsQueue.dirty.Store(false)
347341
return nil
348342
}
349343

@@ -382,35 +376,28 @@ func (c *ValidatorsQueueCache) DeleteUnbondingValidatorQueueEntry(ctx context.Co
382376
// Unbonding Delegations
383377

384378
func (c *ValidatorsQueueCache) checkReloadUnbondingDelegationsQueue(ctx context.Context) error {
379+
c.unbondingDelegationsQueue.mu.Lock()
380+
defer c.unbondingDelegationsQueue.mu.Unlock()
381+
385382
if c.unbondingDelegationsQueue.dirty.Load() {
386383
c.logger(ctx).Info("Unbonding delegations queue is dirty. Reinitializing cache from store.")
387-
if err := c.loadUnbondingDelegationsQueue(ctx); err != nil {
384+
385+
data, err := c.unbondingDelegationsQueue.loadFromStore(ctx)
386+
if err != nil {
388387
return err
389388
}
390-
}
391-
return nil
392-
}
393389

394-
func (c *ValidatorsQueueCache) loadUnbondingDelegationsQueue(ctx context.Context) error {
395-
data, err := c.unbondingDelegationsQueue.loadFromStore(ctx)
396-
if err != nil {
397-
return err
398-
}
399-
400-
c.unbondingDelegationsQueue.mu.Lock()
401-
defer c.unbondingDelegationsQueue.mu.Unlock()
402-
403-
if err := c.unbondingDelegationsQueue.clearUnsafe(ctx); err != nil {
404-
return err
405-
}
406-
407-
for key, value := range data {
408-
if err := c.unbondingDelegationsQueue.setEntryUnsafe(ctx, c.cdc, key, value); err != nil {
390+
if err := c.unbondingDelegationsQueue.clearUnsafe(ctx); err != nil {
409391
return err
410392
}
411-
}
412393

413-
c.unbondingDelegationsQueue.dirty.Store(false)
394+
for key, value := range data {
395+
if err := c.unbondingDelegationsQueue.setEntryUnsafe(ctx, c.cdc, key, value); err != nil {
396+
return err
397+
}
398+
}
399+
c.unbondingDelegationsQueue.dirty.Store(false)
400+
}
414401
return nil
415402
}
416403

@@ -449,35 +436,28 @@ func (c *ValidatorsQueueCache) DeleteUnbondingDelegationQueueEntry(ctx context.C
449436
// Redelegations Queue
450437

451438
func (c *ValidatorsQueueCache) checkReloadRedelegationsQueue(ctx context.Context) error {
439+
c.redelegationsQueue.mu.Lock()
440+
defer c.redelegationsQueue.mu.Unlock()
441+
452442
if c.redelegationsQueue.dirty.Load() {
453443
c.logger(ctx).Info("Redelegations queue is dirty. Reinitializing cache from store.")
454-
if err := c.loadRedelegationsQueue(ctx); err != nil {
444+
data, err := c.redelegationsQueue.loadFromStore(ctx)
445+
if err != nil {
455446
return err
456447
}
457-
}
458-
return nil
459-
}
460448

461-
func (c *ValidatorsQueueCache) loadRedelegationsQueue(ctx context.Context) error {
462-
data, err := c.redelegationsQueue.loadFromStore(ctx)
463-
if err != nil {
464-
return err
465-
}
466-
467-
c.redelegationsQueue.mu.Lock()
468-
defer c.redelegationsQueue.mu.Unlock()
469-
470-
if err := c.redelegationsQueue.clearUnsafe(ctx); err != nil {
471-
return err
472-
}
473-
474-
for key, value := range data {
475-
if err := c.redelegationsQueue.setEntryUnsafe(ctx, c.cdc, key, value); err != nil {
449+
if err := c.redelegationsQueue.clearUnsafe(ctx); err != nil {
476450
return err
477451
}
478-
}
479452

480-
c.redelegationsQueue.dirty.Store(false)
453+
for key, value := range data {
454+
if err := c.redelegationsQueue.setEntryUnsafe(ctx, c.cdc, key, value); err != nil {
455+
return err
456+
}
457+
}
458+
459+
c.redelegationsQueue.dirty.Store(false)
460+
}
481461
return nil
482462
}
483463

0 commit comments

Comments
 (0)