{"record":{"id":"eabca24c6bf0faa9","repo":"go-kratos/kratos","slug":"iterator-closed","errorCode":null,"errorMessage":"iterator closed","messagePattern":"iterator closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"contrib/registry/kubernetes/registry.go","lineNumber":415,"sourceCode":"\t\t\t\t}\n\t\t\t}\n\t\t\taddr := protocol + \"://\" + net.JoinHostPort(podIP, strconv.Itoa(int(port)))\n\t\t\tendpoints = append(endpoints, addr)\n\t\t}\n\t}\n\treturn &registry.ServiceInstance{\n\t\tID:        podLabels[LabelsKeyServiceID],\n\t\tName:      podLabels[LabelsKeyServiceName],\n\t\tVersion:   podLabels[LabelsKeyServiceVersion],\n\t\tMetadata:  metadata,\n\t\tEndpoints: endpoints,\n\t}, nil\n}\n\n// //////////// Error Definition ////////////\n\n// ErrIteratorClosed defines the error that the iterator is closed\nvar ErrIteratorClosed = errors.New(\"iterator closed\")\n\n// ErrorHandleResource defines the error that cannot handle K8S resources normally\ntype ErrorHandleResource struct {\n\tNamespace string\n\tName      string\n\tReason    error\n}\n\n// Error implements the error interface\nfunc (err *ErrorHandleResource) Error() string {\n\treturn fmt.Sprintf(\"failed to handle resource(namespace=%s, name=%s): %s\",\n\t\terr.Namespace, err.Name, err.Reason)\n}\n","sourceCodeStart":397,"sourceCodeEnd":429,"githubUrl":"https://github.com/go-kratos/kratos/blob/668db92c2c001e9552594ba5a8aede8456af6d7e/contrib/registry/kubernetes/registry.go#L397-L429","documentation":"ErrIteratorClosed (contrib/registry/kubernetes/registry.go:415) is returned by Iterator.Next (registry.go:306) when its stopCh has been closed. Iterator is the channel-based change stream used by the kubernetes registry's watcher; Stop() closes stopCh (idempotently, via the select/default guard), and any subsequent or in-flight Next() unblocks through the '<-iter.stopCh' case and returns this sentinel. It signals orderly termination, not a fault.","triggerScenarios":"Calling iter.Next() after iter.Stop(); calling Next concurrently with Stop so the stopCh case wins the select; the owning watcher being stopped by kratos during app shutdown which cascades into the iterator's stopCh. Any Next on a closed iterator yields exactly this error and nothing else.","commonSituations":"Service shutdown sequences where the consumer goroutine of the instance channel is still looping after the producer was stopped; missing synchronization between 'stop the watcher' and 'drain the loop'; tests that stop the iterator and then assert on one more Next call.","solutions":["Treat ErrIteratorClosed as a normal end-of-stream signal: break the consumption loop, do not log it as an error","Ensure only one side owns lifecycle: the goroutine reading Next() should also observe Stop() and exit","Match with errors.Is(err, kubernetes.ErrIteratorClosed) rather than string comparison"],"exampleFix":"// before\nfor {\n    ins, err := iter.Next()\n    if err != nil { log.Error(\"watch failed\", err) } // noisy on shutdown\n}\n\n// after\nfor {\n    ins, err := iter.Next()\n    if errors.Is(err, kubernetes.ErrIteratorClosed) {\n        return nil // clean shutdown\n    }\n    if err != nil { return err }\n    update(ins)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"for {\n    ins, err := iter.Next()\n    if errors.Is(err, kubernetes.ErrIteratorClosed) {\n        return nil // clean termination\n    }\n    if err != nil {\n        return err\n    }\n    handle(ins)\n}","preventionTips":["Model ErrIteratorClosed as end-of-stream, never as a fault to log loudly","Single-owner lifecycle: the goroutine consuming Next() should also react to Stop()","Order shutdown as: stop producer, drain consumer, then close channels"],"tags":["registry","kubernetes","watcher","sentinel-error","lifecycle"],"backgroundTag":null,"analyzedSha":"668db92c2c001e9552594ba5a8aede8456af6d7e","analyzedAt":"2026-08-16T02:07:20.704Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}