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).
This commit is contained in:
Chris Lu authored and GitHub committed 2026-04-06 12:26:38 -07:00
1 parent 83a632669a
commit b0a4647d87
1 file changed
+10 -3
@@ -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)
}