{"record":{"id":"bc9295b10897c33b","repo":"micro/go-micro","slug":"watcher-stopped-bc9295","errorCode":null,"errorMessage":"watcher stopped","messagePattern":"watcher stopped","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"registry/memory_watcher.go","lineNumber":23,"sourceCode":")\n\ntype memWatcher struct {\n\two   WatchOptions\n\tres  chan *Result\n\texit chan bool\n\tid   string\n}\n\nfunc (m *memWatcher) Next() (*Result, error) {\n\tfor {\n\t\tselect {\n\t\tcase r := <-m.res:\n\t\t\tif len(m.wo.Service) > 0 && m.wo.Service != r.Service.Name {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn r, nil\n\t\tcase <-m.exit:\n\t\t\treturn nil, errors.New(\"watcher stopped\")\n\t\t}\n\t}\n}\n\nfunc (m *memWatcher) Stop() {\n\tselect {\n\tcase <-m.exit:\n\t\treturn\n\tdefault:\n\t\tclose(m.exit)\n\t}\n}\n","sourceCodeStart":5,"sourceCodeEnd":36,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/registry/memory_watcher.go#L5-L36","documentation":"The in-memory registry watcher returns this when Next() is called after the watcher has been stopped: the exit channel is closed, so the select falls into the <-m.exit branch and returns 'watcher stopped' instead of a result.","triggerScenarios":"Calling memWatcher.Next() after memWatcher.Stop() (or after the owning registry was unsubscribed/stopped); racing a Next() loop against Stop() during shutdown.","commonSituations":"Tests that stop the registry/watcher while a consumer goroutine is still iterating; service shutdown sequences where watcher teardown happens before the event loop exits; memory registry used in unit tests with leaked watcher goroutines.","solutions":["Treat the error as a normal shutdown signal: compare with registry.ErrWatcherStopped and exit the loop","Ensure Stop() is called only after the Next() consumer loop has finished","Run the consumer loop in a goroutine whose lifecycle is bounded by watcher Stop"],"exampleFix":"// before\nfor {\n  r, err := w.Next()\n  if err != nil { log.Fatal(err) } // treats normal stop as fatal\n}\n// after\nfor {\n  r, err := w.Next()\n  if err != nil {\n    if err == registry.ErrWatcherStopped { return } // clean shutdown\n    return err\n  }\n  handle(r)\n}","handlingStrategy":"type-guard","validationCode":"// check before use\nselect {\ncase <-w.stopped:\n  return // watcher already stopped; don't call Next\ndefault:\n}","typeGuard":"func isWatcherStopped(err error) bool {\n  return errors.Is(err, registry.ErrWatcherStopped)\n}","tryCatchPattern":"r, err := w.Next()\nif isWatcherStopped(err) {\n  return // clean shutdown, not a failure\n}\nif err != nil { return err }","preventionTips":["Compare errors against registry.ErrWatcherStopped rather than string matching","Coordinate Stop() with consumer goroutines via WaitGroup/done channel","In tests, always Stop watchers with defer to avoid leaked goroutines"],"tags":["memory-registry","watcher","lifecycle","registry"],"backgroundTag":"watcher-stopped","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}