diff --git a/pkg/gotenberg/supervisor.go b/pkg/gotenberg/supervisor.go index 0c875bcd..801fff73 100644 --- a/pkg/gotenberg/supervisor.go +++ b/pkg/gotenberg/supervisor.go @@ -244,12 +244,18 @@ func (s *processSupervisor) restart() error { s.logger.WarnContext(context.Background(), fmt.Sprintf("stop process before restart: %s", err)) } + // Reset the counter on the attempt, not on its outcome. Leaving it at the + // limit after a failed launch re-triggers maybeRestartAfterTask on every + // subsequent task, producing back-to-back restarts. Recovering a process + // that will not start is ensureHealthy's job: it restarts synchronously + // before running a task, and reports the failure to the caller. + s.reqCounter.Store(0) + err = s.Launch() if err != nil { return fmt.Errorf("restart process: %w", err) } - s.reqCounter.Store(0) s.restartsCounter.Add(1) s.logger.DebugContext(context.Background(), "process successfully restarted") diff --git a/pkg/gotenberg/supervisor_test.go b/pkg/gotenberg/supervisor_test.go index 793f6f75..2d76bc01 100644 --- a/pkg/gotenberg/supervisor_test.go +++ b/pkg/gotenberg/supervisor_test.go @@ -160,6 +160,41 @@ func TestProcessSupervisor_restart(t *testing.T) { } } +// TestProcessSupervisor_restart_ResetsCounterOnFailedLaunch verifies that a +// restart whose launch fails still clears the request counter. Leaving it at +// the limit makes maybeRestartAfterTask re-fire on every subsequent task. +func TestProcessSupervisor_restart_ResetsCounterOnFailedLaunch(t *testing.T) { + logger := slog.New(slog.DiscardHandler) + + const maxReqLimit = 5 + + process := &ProcessMock{ + StartMock: func(_ *slog.Logger) error { return errors.New("start error") }, + StopMock: func(_ *slog.Logger) error { return nil }, + HealthyMock: func(_ *slog.Logger) bool { return true }, + } + + ps := NewProcessSupervisor(logger, "test", process, maxReqLimit, 0, 1, 0).(*processSupervisor) + ps.reqCounter.Store(maxReqLimit) + + err := ps.restart() + if err == nil { + t.Fatal("expected error but got none") + } + + if got := ps.reqCounter.Load(); got != 0 { + t.Fatalf("expected the request counter to be reset but got %d", got) + } + + if got := ps.restartsCounter.Load(); got != 0 { + t.Fatalf("expected the restarts counter to stay at 0 but got %d", got) + } + + if ps.maybeRestartAfterTask(logger) { + t.Fatal("expected no further eager restart to be triggered") + } +} + func TestProcessSupervisor_Healthy(t *testing.T) { for _, tc := range []struct { scenario string