From a1df0c8aa7bc2d536cc2fb5ca41da3d98c662963 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Mon, 20 Apr 2026 10:08:42 +0000 Subject: [PATCH 1/2] pkg/tools/batcher: stop scheduler panicking when b.data is closed externally scheduler()'s defer unconditionally calls close(b.data). If the channel was closed by the caller (or an upstream producer) instead of via the normal Close()-sends-nil path, the receive on b.data returns ok == false, scheduler returns, and the deferred close(b.data) then fires on an already-closed channel: panic: close of closed channel reliably reproducible under the #3653 steps (manually closing b.data while Start() is running). Track whether we observed the external-close via a local `externallyClosed` flag set in the `ok == false` branch. The defer only closes b.data when that flag is false, i.e. when the scheduler exited through the nil-message or ticker paths and still owns the channel. No behaviour change on the graceful Close() path. Fixes #3653 --- pkg/tools/batcher/batcher.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/tools/batcher/batcher.go b/pkg/tools/batcher/batcher.go index 93a31ed8f..bbaa2ca93 100644 --- a/pkg/tools/batcher/batcher.go +++ b/pkg/tools/batcher/batcher.go @@ -151,12 +151,21 @@ func (b *Batcher[T]) Put(ctx context.Context, data *T) error { func (b *Batcher[T]) scheduler() { ticker := time.NewTicker(b.config.interval) + // Track whether b.data was closed by an external caller so the + // cleanup below does not close it a second time. The only routes + // out of this function that leave b.data open are the nil-message + // and ticker paths; the ok == false branch means someone already + // closed the channel, and calling close(b.data) again there would + // panic with "close of closed channel" (#3653). + externallyClosed := false defer func() { ticker.Stop() for _, ch := range b.chArrays { close(ch) } - close(b.data) + if !externallyClosed { + close(b.data) + } b.wait.Done() }() @@ -169,6 +178,7 @@ func (b *Batcher[T]) scheduler() { case data, ok := <-b.data: if !ok { // If the data channel is closed unexpectedly + externallyClosed = true return } if data == nil { From ade68df6d03c420411be128a2e3aa7681d4f6198 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Sat, 2 May 2026 02:21:30 +0000 Subject: [PATCH 2/2] ci: retrigger after transient gha outage Signed-off-by: SAY-5