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

  1. Compare with errors.Is(err, source.ErrWatcherStopped) and break out of the watch loop instead of logging it as a failure.
  2. Ensure each watcher's Stop() is called exactly once (use sync.Once) to avoid double-close panics.
  3. Stop the watcher only after its consumer has exited, or design the consumer to exit on this sentinel.
  4. 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

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.