From 0d7ae150248e09d76615003608a301bfbf2fa3f0 Mon Sep 17 00:00:00 2001 From: Ljubisa Gacevic Date: Sat, 24 Jan 2026 13:03:31 +0100 Subject: [PATCH 1/2] fix(puller): prevent massive log lines from joined errors --- pkg/puller/error_summary_test.go | 72 ++++++++++++++++++++++++++++++++ pkg/puller/puller.go | 40 +++++++++++++++++- 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 pkg/puller/error_summary_test.go diff --git a/pkg/puller/error_summary_test.go b/pkg/puller/error_summary_test.go new file mode 100644 index 00000000000..31022c0e8f8 --- /dev/null +++ b/pkg/puller/error_summary_test.go @@ -0,0 +1,72 @@ +// Copyright 2025 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package puller + +import ( + "errors" + "testing" +) + +func TestCountErrors(t *testing.T) { + t.Parallel() + + t.Run("nil error", func(t *testing.T) { + result := countErrors(nil) + if result != 0 { + t.Errorf("expected 0 for nil error, got %d", result) + } + }) + + t.Run("single error", func(t *testing.T) { + err := errors.New("test error") + result := countErrors(err) + if result != 1 { + t.Errorf("expected 1, got %d", result) + } + }) + + t.Run("two different errors", func(t *testing.T) { + err1 := errors.New("error one") + err2 := errors.New("error two") + joined := errors.Join(err1, err2) + result := countErrors(joined) + + if result != 2 { + t.Errorf("expected 2, got %d", result) + } + }) + + t.Run("many identical errors", func(t *testing.T) { + baseErr := errors.New("storage: not found") + var joined error + for range 100 { + joined = errors.Join(joined, baseErr) + } + + result := countErrors(joined) + + if result != 100 { + t.Errorf("expected 100, got %d", result) + } + }) + + t.Run("mixed repeated errors", func(t *testing.T) { + err1 := errors.New("storage: not found") + err2 := errors.New("invalid stamp") + var joined error + for range 50 { + joined = errors.Join(joined, err1) + } + for range 30 { + joined = errors.Join(joined, err2) + } + + result := countErrors(joined) + + if result != 80 { + t.Errorf("expected 80, got %d", result) + } + }) +} diff --git a/pkg/puller/puller.go b/pkg/puller/puller.go index 0bcdcfae9cb..2084da86fb8 100644 --- a/pkg/puller/puller.go +++ b/pkg/puller/puller.go @@ -33,6 +33,39 @@ const loggerName = "puller" var errCursorsLength = errors.New("cursors length mismatch") +// countErrors counts the total number of errors in a joined error. +// This prevents massive log lines when many errors are joined together. +func countErrors(err error) int { + if err == nil { + return 0 + } + + count := 0 + var walkErrors func(error) + walkErrors = func(e error) { + if e == nil { + return + } + + // Check if this is a joined error (has multiple wrapped errors) + type unwrapper interface { + Unwrap() []error + } + if u, ok := e.(unwrapper); ok { + for _, wrapped := range u.Unwrap() { + walkErrors(wrapped) + } + return + } + + // This is a leaf error + count++ + } + + walkErrors(err) + return count +} + const ( DefaultHistRateWindow = time.Minute * 15 @@ -348,7 +381,12 @@ func (p *Puller) syncPeerBin(parentCtx context.Context, peer *syncPeer, bin uint p.logger.Debug("syncWorker interval failed, quitting", "error", err, "peer_address", address, "bin", bin, "cursor", cursor, "start", start, "topmost", top) return } - p.logger.Debug("syncWorker interval failed", "error", err, "peer_address", address, "bin", bin, "cursor", cursor, "start", start, "topmost", top) + errCount := countErrors(err) + if errCount > 1 { + p.logger.Debug("syncWorker interval failed", "error_count", errCount, "example_error", errors.Unwrap(err), "peer_address", address, "bin", bin, "cursor", cursor, "start", start, "topmost", top) + } else { + p.logger.Debug("syncWorker interval failed", "error", err, "peer_address", address, "bin", bin, "cursor", cursor, "start", start, "topmost", top) + } } _ = p.limiter.WaitN(ctx, count) From 3461738d728a8f4e35aea60717414faf5310dd2c Mon Sep 17 00:00:00 2001 From: Ljubisa Gacevic Date: Wed, 28 Jan 2026 09:23:51 +0100 Subject: [PATCH 2/2] fix(puller): remove log --- pkg/puller/error_summary_test.go | 2 +- pkg/puller/puller.go | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/pkg/puller/error_summary_test.go b/pkg/puller/error_summary_test.go index 31022c0e8f8..400b4d53a13 100644 --- a/pkg/puller/error_summary_test.go +++ b/pkg/puller/error_summary_test.go @@ -1,4 +1,4 @@ -// Copyright 2025 The Swarm Authors. All rights reserved. +// Copyright 2026 The Swarm Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/pkg/puller/puller.go b/pkg/puller/puller.go index 2084da86fb8..9248f82ec7c 100644 --- a/pkg/puller/puller.go +++ b/pkg/puller/puller.go @@ -382,11 +382,7 @@ func (p *Puller) syncPeerBin(parentCtx context.Context, peer *syncPeer, bin uint return } errCount := countErrors(err) - if errCount > 1 { - p.logger.Debug("syncWorker interval failed", "error_count", errCount, "example_error", errors.Unwrap(err), "peer_address", address, "bin", bin, "cursor", cursor, "start", start, "topmost", top) - } else { - p.logger.Debug("syncWorker interval failed", "error", err, "peer_address", address, "bin", bin, "cursor", cursor, "start", start, "topmost", top) - } + p.logger.Debug("syncWorker interval failed", "error_count", errCount, "example_error", errors.Unwrap(err), "peer_address", address, "bin", bin, "cursor", cursor, "start", start, "topmost", top) } _ = p.limiter.WaitN(ctx, count)