From b0a4647d87981172531a1660e25f4288b6350b52 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 6 Apr 2026 12:26:38 -0700 Subject: [PATCH] fix: prevent stack overflow in ECBalanceTask.reportProgress (#8949) * fix: prevent stack overflow in ECBalanceTask.reportProgress Add re-entry guard to reportProgress() to prevent infinite recursion. The progressCallback invoked by ReportProgressWithStage can re-enter reportProgress, causing a stack overflow that crashes the worker process (goroutine stack exceeds 1GB limit after ~22M frames). * fix: use atomics for progress and re-entry guard to avoid data races Address review feedback: GetProgress() can be called from a different goroutine while reportProgress is updating the value. Use atomic operations for both the progress field (via Float64bits/Float64frombits) and the reporting re-entry guard (via CompareAndSwap). --- weed/worker/tasks/ec_balance/ec_balance_task.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/weed/worker/tasks/ec_balance/ec_balance_task.go b/weed/worker/tasks/ec_balance/ec_balance_task.go index 2b0729535d..904b6db8e2 100644 --- a/weed/worker/tasks/ec_balance/ec_balance_task.go +++ b/weed/worker/tasks/ec_balance/ec_balance_task.go @@ -3,6 +3,8 @@ package ec_balance import ( "context" "fmt" + "math" + "sync/atomic" "time" "github.com/seaweedfs/seaweedfs/weed/glog" @@ -22,7 +24,8 @@ type ECBalanceTask struct { volumeID uint32 collection string grpcDialOption grpc.DialOption - progress float64 + progress uint64 // atomic; stores float64 bits via math.Float64bits + reporting int32 // atomic; re-entry guard to prevent recursive reportProgress calls } // NewECBalanceTask creates a new EC balance task instance @@ -206,12 +209,16 @@ func (t *ECBalanceTask) EstimateTime(params *worker_pb.TaskParams) time.Duration // GetProgress returns current progress func (t *ECBalanceTask) GetProgress() float64 { - return t.progress + return math.Float64frombits(atomic.LoadUint64(&t.progress)) } // reportProgress updates the stored progress and reports it via the callback func (t *ECBalanceTask) reportProgress(progress float64, stage string) { - t.progress = progress + if !atomic.CompareAndSwapInt32(&t.reporting, 0, 1) { + return + } + defer atomic.StoreInt32(&t.reporting, 0) + atomic.StoreUint64(&t.progress, math.Float64bits(progress)) t.ReportProgressWithStage(progress, stage) glog.Infof("EC balance volume %d: [%.2f] %s", t.volumeID, progress, stage) }