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
- Ensure Start is called exactly once per Checker instance, typically during extension startup only
- If a restart is needed, build a new Checker instead of reusing the old one
- 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
- Start the checker only in one place (extension Start), guarded by sync.Once
- Construct a fresh Checker for any restart scenario
- Write a lifecycle test asserting Start is called once per instance
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.