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/...
This commit is contained in:
@@ -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()
|
cm.Lock()
|
||||||
defer cm.RUnlock()
|
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()
|
cm.Lock()
|
||||||
defer cm.RUnlock()
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user