Browse Source

fix: CompactMap race condition causing runtime panic (#8029)

Fixed critical race condition in CompactMap where Set(), Delete(), and
Get() methods had issues with concurrent map access.

Root cause: segmentForKey() can create new map segments, which modifies
the cm.segments map. Calling this under a read lock caused concurrent
map write panics when multiple goroutines accessed the map simultaneously
(e.g., during VolumeNeedleStatus gRPC calls).

Changes:
- Set() method: Changed RLock/RUnlock to Lock/Unlock
- Delete() method: Changed RLock/RUnlock to Lock/Unlock, optimized to
  avoid creating empty segments when key doesn't exist
- Get() method: Removed segmentForKey() call to avoid race condition,
  now checks segment existence directly and returns early if segment
  doesn't exist (optimization: avoids unnecessary segment creation)

This fix resolves the runtime/maps.fatal panic that occurred under
concurrent load.

Tested with race detector: go test -v -race ./weed/storage/needle_map/...
fix-s3-configure-consistency
Chris Lu 22 hours ago
committed by GitHub
parent
commit
ba74185700
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 20
      weed/storage/needle_map/compact_map.go

20
weed/storage/needle_map/compact_map.go

@ -236,8 +236,8 @@ func (cm *CompactMap) segmentForKey(key types.NeedleId) *CompactMapSegment {
// Set inserts/updates a NeedleValue. // Set inserts/updates a NeedleValue.
// If the operation is an update, returns the overwritten value's previous offset and size. // If the operation is an update, returns the overwritten value's previous offset and size.
func (cm *CompactMap) Set(key types.NeedleId, offset types.Offset, size types.Size) (oldOffset types.Offset, oldSize types.Size) { func (cm *CompactMap) Set(key types.NeedleId, offset types.Offset, size types.Size) (oldOffset types.Offset, oldSize types.Size) {
cm.RLock()
defer cm.RUnlock()
cm.Lock()
defer cm.Unlock()
cs := cm.segmentForKey(key) cs := cm.segmentForKey(key)
return cs.set(key, offset, size) return cs.set(key, offset, size)
@ -248,7 +248,11 @@ func (cm *CompactMap) Get(key types.NeedleId) (*NeedleValue, bool) {
cm.RLock() cm.RLock()
defer cm.RUnlock() defer cm.RUnlock()
cs := cm.segmentForKey(key)
chunk := Chunk(key / SegmentChunkSize)
cs, ok := cm.segments[chunk]
if !ok {
return nil, false
}
if cnv, found := cs.get(key); found { if cnv, found := cs.get(key); found {
nv := cnv.NeedleValue(cs.chunk) nv := cnv.NeedleValue(cs.chunk)
return &nv, true return &nv, true
@ -258,10 +262,14 @@ func (cm *CompactMap) Get(key types.NeedleId) (*NeedleValue, bool) {
// Delete deletes a map entry by key. Returns the entries' previous Size, if available. // Delete deletes a map entry by key. Returns the entries' previous Size, if available.
func (cm *CompactMap) Delete(key types.NeedleId) types.Size { func (cm *CompactMap) Delete(key types.NeedleId) types.Size {
cm.RLock()
defer cm.RUnlock()
cm.Lock()
defer cm.Unlock()
cs := cm.segmentForKey(key)
chunk := Chunk(key / SegmentChunkSize)
cs, ok := cm.segments[chunk]
if !ok {
return types.Size(0)
}
return cs.delete(key) return cs.delete(key)
} }

Loading…
Cancel
Save