{"record":{"id":"b1aa146e041eb911","repo":"micro/go-micro","slug":"errwatcherstopped","errorCode":"ErrWatcherStopped","errorMessage":"watcher stopped","messagePattern":"watcher stopped","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"config/source/source.go","lineNumber":11,"sourceCode":"// Package source is the interface for sources\npackage source\n\nimport (\n\t\"errors\"\n\t\"time\"\n)\n\nvar (\n\t// ErrWatcherStopped is returned when source watcher has been stopped.\n\tErrWatcherStopped = errors.New(\"watcher stopped\")\n)\n\n// Source is the source from which config is loaded.\ntype Source interface {\n\tRead() (*ChangeSet, error)\n\tWrite(*ChangeSet) error\n\tWatch() (Watcher, error)\n\tString() string\n}\n\n// ChangeSet represents a set of changes from a source.\ntype ChangeSet struct {\n\tTimestamp time.Time\n\tChecksum  string\n\tFormat    string\n\tSource    string\n\tData      []byte\n}","sourceCodeStart":1,"sourceCodeEnd":29,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/config/source/source.go#L1-L29","documentation":"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.","triggerScenarios":"Calling Next() on a watcher whose Stop() has already been called, or being blocked in Next() when another goroutine calls Stop().","commonSituations":"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.","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."],"exampleFix":"// before\ncs, err := w.Next()\nif err != nil {\n    log.Printf(\"watch failed: %v\", err)\n    continue // spins forever after stop\n}\n// after\ncs, err := w.Next()\nif errors.Is(err, source.ErrWatcherStopped) {\n    return // clean shutdown\n} else if err != nil {\n    log.Printf(\"watch failed: %v\", err)\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isWatcherStopped(err error) bool { return errors.Is(err, source.ErrWatcherStopped) }","tryCatchPattern":"cs, err := w.Next()\nif errors.Is(err, source.ErrWatcherStopped) {\n    return\n} else if err != nil {\n    log.Printf(\"watch error: %v\", err)\n    return\n}","preventionTips":["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"],"tags":["config","watcher","sentinel-error","shutdown"],"backgroundTag":"watcher-stopped","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}