jaegertracing/jaeger · error

aihealth: Start called twice

Error message

aihealth: Start called twice

What it means

Checker.Start launches a background goroutine that periodically reports AI health, and a Checker supports exactly one lifecycle: Start must be called once; a second call panics because c.done is already set. This guards against double-starting timers/cancel channels that would leak goroutines.

Source

Thrown at cmd/jaeger/internal/extension/jaegerquery/internal/jaegerai/aihealth/checker.go:50

	Timeout  time.Duration
	Logger   *zap.Logger

	current atomic.Bool

	cancel context.CancelFunc
	done   chan struct{}
}

// Current returns the most recently observed health state. Initial value is
// false until the first check completes.
func (c *Checker) Current() bool { return c.current.Load() }

// Start launches the checker's background goroutine. The first check runs
// immediately so callers see the truth as soon as possible; subsequent
// checks are spaced by Interval. Start may be called only once per Checker.
func (c *Checker) Start(ctx context.Context) {
	if c.done != nil {
		panic("aihealth: Start called twice")
	}
	c.Logger.Info("Starting AI health checker", zap.Duration("interval", c.Interval), zap.Duration("timeout", c.Timeout))
	ctx, cancel := context.WithCancel(ctx)
	c.cancel = cancel
	c.done = make(chan struct{})
	go c.run(ctx)
}

// Stop signals the background goroutine to exit and waits for it. Safe to
// call multiple times; safe to call before Start (no-op).
func (c *Checker) Stop() {
	if c.done == nil {
		return
	}
	c.cancel()
	<-c.done
}

View on GitHub (pinned to 806f444784)

Solutions

  1. Ensure Start is called exactly once per Checker instance, typically during extension startup only
  2. If a restart is needed, build a new Checker instead of reusing the old one
  3. If you intentionally need re-startability, reset c.done/cancel in a Stop() method before calling Start again

Example fix

// before
checker.Start(ctx)
// later, on reconnect
checker.Start(ctx) // panics
// after
checker.Start(ctx)
// on reconnect
checker = jaegerai.NewChecker(cfg, deps)
checker.Start(ctx)
Defensive patterns

Strategy: validation

Validate before calling

if c.done != nil { return errors.New("checker already started") }

Type guard

func started(c *Checker) bool { return c.done != nil }

Try / catch

defer func() { if r := recover(); r != nil { t.Fatalf("Start called twice: %v", r) } }()

Prevention

When it happens

Trigger: Calling Checker.Start(ctx) twice on the same Checker instance, e.g. invoking Start again after an earlier Start without constructing a fresh Checker.

Common situations: Test setups that share a Checker across subtests; extension hot-reload paths calling Start again; retry logic that re-invokes Start on transient failure instead of creating a new Checker.

Related errors


AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01). Data as JSON: /api/errors/8c467c339a790c46. Report an issue: GitHub.