micro/go-micro · info
watcher stopped
Error message
watcher stopped
What it means
The memory loader's watcher runs a loop selecting on its exit channel and the updates channel. When Stop() closes w.exit, the next Next() call returns "watcher stopped" to signal the watcher has been terminated and will deliver no further change sets. It is the documented end-of-life signal for a watcher, similar to io.EOF for streams.
Source
Thrown at config/loader/memory/memory.go:419
cs := &source.ChangeSet{
Data: v.Bytes(),
Format: w.reader.String(),
Source: "memory",
Timestamp: time.Now(),
}
cs.Checksum = cs.Sum()
return &loader.Snapshot{
ChangeSet: cs,
Version: w.getVersion(),
}
}
for {
select {
case <-w.exit:
return nil, errors.New("watcher stopped")
case uv := <-w.updates:
if uv.version <= w.getVersion() {
continue
}
v := uv.value
w.version.Store(uv.version)
if bytes.Equal(w.value.Bytes(), v.Bytes()) {
continue
}
return update(v), nil
}
}
}View on GitHub (pinned to 24529f1404)
Solutions
- Treat errors.New("watcher stopped") (compare err.Error()) as a normal shutdown signal and exit the watch loop cleanly.
- Ensure only one goroutine owns the watcher lifecycle: the same owner should stop it and stop reading Next().
- Do not call Next() after Stop(); restructure to select on a done channel alongside Next().
- If you need watching again, create a new Watcher via loader.Watch instead of reusing the stopped one.
Example fix
// before
for {
v, err := w.Next()
if err != nil { log.Error(err); return } // logs normal stops as errors
}
// after
for {
v, err := w.Next()
if err != nil {
if err.Error() == "watcher stopped" { return } // clean shutdown
log.Error(err); return
}
} Defensive patterns
Strategy: try-catch
Try / catch
for {
v, err := w.Next()
if err != nil {
if err.Error() == "watcher stopped" {
return nil // clean shutdown, not a failure
}
return err
}
handle(v)
} Prevention
- Have one owner goroutine for each watcher's lifecycle.
- Compare the error string (or use errors.Is-style matching) to treat stops as normal.
- Never call Next after Stop; use a done channel to coordinate.
- Create a fresh Watcher via loader.Watch if watching must resume.
When it happens
Trigger: Calling Next() on a loader.Watcher after watcher.Stop() was called (closing w.exit), or continuing to drain the watcher in a loop after another goroutine stopped it during shutdown.
Common situations: Graceful-shutdown code that stops config watchers while a background goroutine is still blocked in Next(); treating "watcher stopped" as a real failure and logging it as an error; double-stopping or restarting watchers on config reload.
Related errors
- watcher is disabled
- noopWatcher stopped
- ErrWatcherStopped
- agent: checkpointed run is terminal with status
- not connected
AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01).
Data as JSON: /api/errors/4cea60b256c2abce.
Report an issue: GitHub.