micro/go-micro · info
ErrWatcherStopped
ErrWatcherStopped
Error message
watcher stopped
What it means
ErrWatcherStopped is the package-level sentinel error indicating a source watcher has been stopped and can deliver no further ChangeSets. Watchers return it from Next() after Stop() so callers can distinguish a normal shutdown from a real failure.
Source
Thrown at config/source/source.go:11
// Package source is the interface for sources
package source
import (
"errors"
"time"
)
var (
// ErrWatcherStopped is returned when source watcher has been stopped.
ErrWatcherStopped = errors.New("watcher stopped")
)
// Source is the source from which config is loaded.
type Source interface {
Read() (*ChangeSet, error)
Write(*ChangeSet) error
Watch() (Watcher, error)
String() string
}
// ChangeSet represents a set of changes from a source.
type ChangeSet struct {
Timestamp time.Time
Checksum string
Format string
Source string
Data []byte
}View on GitHub (pinned to 24529f1404)
Solutions
- Compare with errors.Is(err, source.ErrWatcherStopped) and break out of the watch loop instead of logging it as a failure.
- Ensure each watcher's Stop() is called exactly once (use sync.Once) to avoid double-close panics.
- Stop the watcher only after its consumer has exited, or design the consumer to exit on this sentinel.
- Guard the shutdown path in tests by waiting for Next() to return ErrWatcherStopped before cleanup completes.
Example fix
// before
cs, err := w.Next()
if err != nil {
log.Printf("watch failed: %v", err)
continue // spins forever after stop
}
// after
cs, err := w.Next()
if errors.Is(err, source.ErrWatcherStopped) {
return // clean shutdown
} else if err != nil {
log.Printf("watch failed: %v", err)
} Defensive patterns
Strategy: type-guard
Type guard
func isWatcherStopped(err error) bool { return errors.Is(err, source.ErrWatcherStopped) } Try / catch
cs, err := w.Next()
if errors.Is(err, source.ErrWatcherStopped) {
return
} else if err != nil {
log.Printf("watch error: %v", err)
return
} Prevention
- Compare against the sentinel with errors.Is, never string matching
- Call Stop() exactly once per watcher via sync.Once
- Exit consumer goroutines on the sentinel rather than retrying
When it happens
Trigger: Calling Next() on a watcher whose Stop() has already been called, or being blocked in Next() when another goroutine calls Stop().
Common situations: Graceful shutdown sequences in services watching config; test code (e.g. TestEnvvar_WatchNextNoOpsUntilStop) verifying a watcher stays quiet until Stop; consumer goroutines racing with shutdown.
Related errors
AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01).
Data as JSON: /api/errors/b1aa146e041eb911.
Report an issue: GitHub.