micro/go-micro · info
watcher stopped
Error message
watcher stopped
What it means
The in-memory registry watcher returns this when Next() is called after the watcher has been stopped: the exit channel is closed, so the select falls into the <-m.exit branch and returns 'watcher stopped' instead of a result.
Source
Thrown at registry/memory_watcher.go:23
)
type memWatcher struct {
wo WatchOptions
res chan *Result
exit chan bool
id string
}
func (m *memWatcher) Next() (*Result, error) {
for {
select {
case r := <-m.res:
if len(m.wo.Service) > 0 && m.wo.Service != r.Service.Name {
continue
}
return r, nil
case <-m.exit:
return nil, errors.New("watcher stopped")
}
}
}
func (m *memWatcher) Stop() {
select {
case <-m.exit:
return
default:
close(m.exit)
}
}
View on GitHub (pinned to 24529f1404)
Solutions
- Treat the error as a normal shutdown signal: compare with registry.ErrWatcherStopped and exit the loop
- Ensure Stop() is called only after the Next() consumer loop has finished
- Run the consumer loop in a goroutine whose lifecycle is bounded by watcher Stop
Example fix
// before
for {
r, err := w.Next()
if err != nil { log.Fatal(err) } // treats normal stop as fatal
}
// after
for {
r, err := w.Next()
if err != nil {
if err == registry.ErrWatcherStopped { return } // clean shutdown
return err
}
handle(r)
} Defensive patterns
Strategy: type-guard
Validate before calling
// check before use
select {
case <-w.stopped:
return // watcher already stopped; don't call Next
default:
} Type guard
func isWatcherStopped(err error) bool {
return errors.Is(err, registry.ErrWatcherStopped)
} Try / catch
r, err := w.Next()
if isWatcherStopped(err) {
return // clean shutdown, not a failure
}
if err != nil { return err } Prevention
- Compare errors against registry.ErrWatcherStopped rather than string matching
- Coordinate Stop() with consumer goroutines via WaitGroup/done channel
- In tests, always Stop watchers with defer to avoid leaked goroutines
When it happens
Trigger: Calling memWatcher.Next() after memWatcher.Stop() (or after the owning registry was unsubscribed/stopped); racing a Next() loop against Stop() during shutdown.
Common situations: Tests that stop the registry/watcher while a consumer goroutine is still iterating; service shutdown sequences where watcher teardown happens before the event loop exits; memory registry used in unit tests with leaked watcher goroutines.
Related errors
- watcher stopped
- watcher stopped
- could not get next
- agent: checkpointed run is terminal with status
- cannot init while connected
AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01).
Data as JSON: /api/errors/bc9295b10897c33b.
Report an issue: GitHub.