You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ha...@apache.org on 2026/04/21 10:30:30 UTC

(skywalking-banyandb) 01/01: Fix nil pointer panic in disk monitor during early initialization

This is an automated email from the ASF dual-hosted git repository.

hanahmily pushed a commit to branch fix/disk-monitor-nil-panic
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git

commit 8c7bea8a55d68fac476413498610a3e760bfa39a
Author: Hongtao Gao <ha...@gmail.com>
AuthorDate: Tue Apr 21 10:08:10 2026 +0000

    Fix nil pointer panic in disk monitor during early initialization
    
    The disk monitor's monitorLoop could panic with "invalid memory address
    or nil pointer dereference" when groups are registered in the schema
    repo but their schema hasn't been populated yet (race between group
    creation and async schema loading).
    
    Two fixes:
    - Nil-check group.GetSchema() in findGroupWithOldestSegment before
      accessing Metadata.Name to prevent the panic
    - Move panic recovery from the outer loop into per-tick scope so a
      recovered panic doesn't kill the monitor goroutine permanently
---
 CHANGES.md                               |  1 +
 banyand/internal/storage/disk_monitor.go | 23 +++++++++++++++--------
 2 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 2136c0d02..21a3ba955 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -38,6 +38,7 @@ Release Notes.
 - Fix wrong backup path of schema property.
 - Fix lifecycle migration failure when the target stage has `close: true`.
 - Fix stale sync request blocking watch session channel, causing repeated "channel full, skipping session" errors when a watch stream is in backoff.
+- Fix nil pointer panic in disk monitor when group schema is not yet initialized during early startup, and ensure monitor loop survives recovered panics.
 
 ### Chores
 
diff --git a/banyand/internal/storage/disk_monitor.go b/banyand/internal/storage/disk_monitor.go
index bcaa8550d..15205f182 100644
--- a/banyand/internal/storage/disk_monitor.go
+++ b/banyand/internal/storage/disk_monitor.go
@@ -177,22 +177,25 @@ func (dm *DiskMonitor) Stop() {
 }
 
 func (dm *DiskMonitor) monitorLoop(serviceName string) {
-	defer func() {
-		if r := recover(); r != nil {
-			dm.logger.Error().Interface("panic", r).Msg("disk monitor panic recovered")
-		}
-	}()
-
 	for {
 		select {
 		case <-dm.stopCh:
 			return
 		case <-dm.ticker.C:
-			dm.checkAndCleanup(serviceName)
+			dm.safeCheckAndCleanup(serviceName)
 		}
 	}
 }
 
+func (dm *DiskMonitor) safeCheckAndCleanup(serviceName string) {
+	defer func() {
+		if r := recover(); r != nil {
+			dm.logger.Error().Interface("panic", r).Msg("disk monitor panic recovered")
+		}
+	}()
+	dm.checkAndCleanup(serviceName)
+}
+
 func (dm *DiskMonitor) checkAndCleanup(serviceName string) {
 	// Check disk usage using real-time calculation for responsive forced cleanup
 	diskPercent, err := getRealTimeDiskUsagePercent(dm.service.GetDataPath())
@@ -344,7 +347,11 @@ func (dm *DiskMonitor) findGroupWithOldestSegment(groups []resourceSchema.Group)
 
 	// Query each group's oldest segment end time
 	for _, group := range groups {
-		groupName := group.GetSchema().Metadata.Name
+		schema := group.GetSchema()
+		if schema == nil || schema.Metadata == nil {
+			continue
+		}
+		groupName := schema.Metadata.Name
 		endTime, hasSegments := dm.service.PeekOldestSegmentEndTimeInGroup(groupName)
 
 		// Only consider groups that have segments