diff --git a/pkg/manager/internal.go b/pkg/manager/internal.go index a16f354a1b..396dbcd569 100644 --- a/pkg/manager/internal.go +++ b/pkg/manager/internal.go @@ -485,8 +485,8 @@ func (cm *controllerManager) engageStopProcedure(stopComplete <-chan struct{}) e cm.internalCancel() }) select { - case err, ok := <-cm.errChan: - if ok { + case err := <-cm.errChan: + if !errors.Is(err, context.Canceled) { cm.logger.Error(err, "error received after stop sequence was engaged") } case <-stopComplete: diff --git a/pkg/manager/manager_test.go b/pkg/manager/manager_test.go index a88ccca00f..9526f8ab10 100644 --- a/pkg/manager/manager_test.go +++ b/pkg/manager/manager_test.go @@ -30,6 +30,7 @@ import ( "time" "github.com/go-logr/logr" + "github.com/go-logr/logr/funcr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/prometheus/client_golang/prometheus" @@ -1044,6 +1045,54 @@ var _ = Describe("manger.Manager", func() { }))).NotTo(Succeed()) }) + It("should not return runnables context.Canceled errors", func() { + Expect(options.Logger).To(BeZero(), "this test overrides Logger") + + var log struct { + sync.Mutex + messages []string + } + options.Logger = funcr.NewJSON(func(object string) { + log.Lock() + log.messages = append(log.messages, object) + log.Unlock() + }, funcr.Options{}) + + m, err := New(cfg, options) + Expect(err).NotTo(HaveOccurred()) + for _, cb := range callbacks { + cb(m) + } + + // Runnables may return ctx.Err() as shown in some [context.Context] examples. + started := make(chan struct{}) + Expect(m.Add(RunnableFunc(func(ctx context.Context) error { + close(started) + <-ctx.Done() + return ctx.Err() + }))).To(Succeed()) + + stopped := make(chan error) + ctx, cancel := context.WithCancel(context.Background()) + go func() { + stopped <- m.Start(ctx) + }() + + // Wait for runnables to start, signal the manager, and wait for it. + <-started + cancel() + Expect(<-stopped).To(Succeed()) + + // TODO: Diagnose why LeaderElection races after Start() returns. + if options.LeaderElection { + time.Sleep(time.Second) + } + + Expect(log.messages).To(Not(ContainElement( + ContainSubstring(context.Canceled.Error()), + ))) + }) + It("should return both runnables and stop errors when both error", func() { m, err := New(cfg, options) Expect(err).NotTo(HaveOccurred())