{"record":{"id":"67e48eba8580f748","repo":"apache/druid","slug":"exception-while-closing-watch","errorCode":null,"errorMessage":"Exception while closing watch.","messagePattern":"Exception while closing watch\\.","errorType":"exception","errorClass":"RuntimeException (Druid RE)","httpStatus":null,"severity":"warning","filePath":"extensions-core/kubernetes-extensions/src/main/java/org/apache/druid/k8s/discovery/DefaultK8sApiClient.java","lineNumber":298,"sourceCode":"          }\n\n          return false;\n        }\n\n        @Override\n        public Watch.Response<DiscoveryDruidNodeAndResourceVersion> next()\n        {\n          return obj;\n        }\n\n        @Override\n        public void close()\n        {\n          try {\n            watch.close();\n          }\n          catch (IOException ex) {\n            throw new RE(ex, \"Exception while closing watch.\");\n          }\n        }\n      };\n    }\n    catch (ApiException ex) {\n      if (ex.getCode() == 410) {\n        // k8s no longer has history that we need\n        return null;\n      }\n\n      throw new RE(ex, \"Expection in watching pods, code[%d] and error[%s].\", ex.getCode(), ex.getResponseBody());\n    }\n  }\n}\n","sourceCodeStart":280,"sourceCodeEnd":313,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-core/kubernetes-extensions/src/main/java/org/apache/druid/k8s/discovery/DefaultK8sApiClient.java#L280-L313","documentation":"Wraps an IOException from the Kubernetes client's Watch.close() when tearing down a pod watch stream. The watch's underlying HTTP stream could not be cleanly closed; the error is rethrown as a Druid RE so callers of the WatchResult know cleanup failed.","triggerScenarios":"Closing a WatchResult obtained from watchPods when the underlying okhttp/http client throws IOException during shutdown — e.g. the connection was already broken, the API server dropped the stream, or client resources were already released.","commonSituations":"K8s API server or network connection terminated before close; watch already expired (410 Gone previously) and the stale stream errors on close; abrupt pod restarts during watch teardown.","solutions":["Log and continue if the watch is being discarded anyway — the exception only affects cleanup, not data.","Ensure watchPods results are closed promptly; long-lived broken streams are more likely to fail on close.","Check connectivity to the K8s API server and client http timeout settings.","Retry the whole watchPods cycle; a fresh watch usually closes fine."],"exampleFix":"// before\ntry (WatchResult wr = client.watchPods(ns, selector, rv, role)) { ... }\n// after: tolerate close failures\nWatchResult wr = client.watchPods(ns, selector, rv, role);\ntry { ... } finally { try { wr.close(); } catch (Exception e) { LOG.warn(e, \"watch close failed\"); } }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try (WatchResult wr = apiClient.watchPods(ns, selector, rv, role)) {\n  // consume events\n} catch (Exception e) {\n  LOG.warn(e, \"watch close/consume failed; resyncing\");\n}","preventionTips":["Close watches promptly when done instead of abandoning broken streams.","Treat close failures as non-fatal: the watch's purpose is already served.","Re-run watchPods from a fresh resource version after any close error.","Monitor API-server connectivity to reduce half-open streams."],"tags":["kubernetes","watch","ioexception","cleanup"],"backgroundTag":"broken-pipe","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}