diff --git a/map.go b/map.go index 8d33d7a..1aa1c16 100644 --- a/map.go +++ b/map.go @@ -481,7 +481,7 @@ func (m *Map) resize(knownTable *mapTable, hint mapResizeHint) { newTable = newMapTable(tableLen << 1) case mapShrinkHint: shrinkThreshold := int64((tableLen * entriesPerMapBucket) / mapShrinkFraction) - if table.sumSize() <= shrinkThreshold { + if tableLen > minMapTableLen && table.sumSize() <= shrinkThreshold { // Shrink the table with factor of 2. atomic.AddInt64(&m.totalShrinks, 1) newTable = newMapTable(tableLen >> 1) diff --git a/map_test.go b/map_test.go index ce4228b..ae92b4b 100644 --- a/map_test.go +++ b/map_test.go @@ -470,6 +470,37 @@ func TestMapStoreThenLoadAndDelete(t *testing.T) { } } +func TestMapStoreThenParallelDelete_DoesNotShrinkBelowMinTableLen(t *testing.T) { + const numEntries = 1000 + m := NewMap() + for i := 0; i < numEntries; i++ { + m.Store(strconv.Itoa(i), i) + } + + cdone := make(chan bool) + go func() { + for i := 0; i < numEntries; i++ { + m.Delete(strconv.Itoa(int(i))) + } + cdone <- true + }() + go func() { + for i := 0; i < numEntries; i++ { + m.Delete(strconv.Itoa(int(i))) + } + cdone <- true + }() + + // Wait for the goroutines to finish. + <-cdone + <-cdone + + stats := CollectMapStats(m) + if stats.RootBuckets < MinMapTableLen { + t.Fatalf("table was too small: %d", stats.RootBuckets) + } +} + func sizeBasedOnRange(m *Map) int { size := 0 m.Range(func(key string, value interface{}) bool { diff --git a/mapof.go b/mapof.go index 596e6d6..3f2c5fe 100644 --- a/mapof.go +++ b/mapof.go @@ -430,7 +430,7 @@ func (m *MapOf[K, V]) resize(knownTable *mapOfTable[K, V], hint mapResizeHint) { newTable = newMapOfTable[K, V](tableLen << 1) case mapShrinkHint: shrinkThreshold := int64((tableLen * entriesPerMapBucket) / mapShrinkFraction) - if table.sumSize() <= shrinkThreshold { + if tableLen > minMapTableLen && table.sumSize() <= shrinkThreshold { // Shrink the table with factor of 2. atomic.AddInt64(&m.totalShrinks, 1) newTable = newMapOfTable[K, V](tableLen >> 1) diff --git a/mapof_test.go b/mapof_test.go index 41a01de..43ea530 100644 --- a/mapof_test.go +++ b/mapof_test.go @@ -515,6 +515,37 @@ func TestMapOfStructStoreThenLoadAndDelete(t *testing.T) { } } +func TestMapOfStoreThenParallelDelete_DoesNotShrinkBelowMinTableLen(t *testing.T) { + const numEntries = 1000 + m := NewMapOf[int, int]() + for i := 0; i < numEntries; i++ { + m.Store(i, i) + } + + cdone := make(chan bool) + go func() { + for i := 0; i < numEntries; i++ { + m.Delete(i) + } + cdone <- true + }() + go func() { + for i := 0; i < numEntries; i++ { + m.Delete(i) + } + cdone <- true + }() + + // Wait for the goroutines to finish. + <-cdone + <-cdone + + stats := CollectMapOfStats(m) + if stats.RootBuckets < MinMapTableLen { + t.Fatalf("table was too small: %d", stats.RootBuckets) + } +} + func sizeBasedOnTypedRange(m *MapOf[string, int]) int { size := 0 m.Range(func(key string, value int) bool {