micro/go-micro · info · ErrWatcherStopped

watcher stopped

Error message

watcher stopped

What it means

registry.ErrWatcherStopped is the sentinel returned by watcher Next() implementations (e.g. the memory watcher) when the watcher has been stopped. It lets callers distinguish a clean, expected shutdown of the watch stream from a real failure.

Source

Thrown at registry/registry.go:12

// Package registry is an interface for service discovery
package registry

import (
	"errors"
)

var (
	// Not found error when GetService is called.
	ErrNotFound = errors.New("service not found")
	// Watcher stopped error when watcher is stopped.
	ErrWatcherStopped = errors.New("watcher stopped")
)

// The registry provides an interface for service discovery
// and an abstraction over varying implementations
// {consul, etcd, zookeeper, ...}.
type Registry interface {
	Init(...Option) error
	Options() Options
	Register(*Service, ...RegisterOption) error
	Deregister(*Service, ...DeregisterOption) error
	GetService(string, ...GetOption) ([]*Service, error)
	ListServices(...ListOption) ([]*Service, error)
	Watch(...WatchOption) (Watcher, error)
	String() string
}

type Service struct {
	Name      string            `json:"name"`

View on GitHub (pinned to 24529f1404)

Solutions

  1. Compare the returned error to registry.ErrWatcherStopped and exit the watch loop cleanly instead of logging an error
  2. Only call Stop() after all Next() consumers finished, or run Next in a goroutine that exits on this sentinel
  3. Use the sentinel to implement idempotent shutdown logic

Example fix

// before
r, err := w.Next()
if err != nil { panic(err) }
// after
r, err := w.Next()
if err != nil {
  if errors.Is(err, registry.ErrWatcherStopped) { return nil } // expected
  return err
}
Defensive patterns

Strategy: type-guard

Type guard

func isWatcherStopped(err error) bool {
  return errors.Is(err, registry.ErrWatcherStopped)
}

Try / catch

r, err := w.Next()
if errors.Is(err, registry.ErrWatcherStopped) {
  return nil // expected during shutdown
}
if err != nil { return err }

Prevention

When it happens

Trigger: Calling Next() on any watcher whose Stop() has been invoked; the memory watcher's exit channel closed; wrappers that translate internal stop conditions to the sentinel.

Common situations: Graceful shutdown flows where the consumer loop is still iterating when Stop() is called; tests that stop watchers to unblock Next (e.g. TestEnvvar_WatchNextNoOpsUntilStop); leaked watchers stopped by cleanup code.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/ff5ae708e065891f. Report an issue: GitHub.