{"record":{"id":"8a1b40f03f76c441","repo":"apache/druid","slug":"can-t-start-8a1b40","errorCode":null,"errorMessage":"can't start.","messagePattern":"can't start\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"extensions-core/kubernetes-extensions/src/main/java/org/apache/druid/k8s/discovery/K8sDruidNodeDiscoveryProvider.java","lineNumber":140,"sourceCode":"              podInfo,\n              discoveryConfig,\n              k8sApiClient,\n              watcherErrorRetryWaitMS\n          );\n          if (startAfterCreation) {\n            nodeRoleWatcher.start();\n          }\n          LOGGER.info(\"Created NodeRoleWatcher for role[%s].\", nType);\n          return nodeRoleWatcher;\n        }\n    );\n  }\n\n  @LifecycleStart\n  public void start()\n  {\n    if (!lifecycleLock.canStart()) {\n      throw new ISE(\"can't start.\");\n    }\n\n    try {\n      LOGGER.info(\"starting\");\n\n      // This is single-threaded to ensure that all listener calls are executed precisely in the oder of add/remove\n      // event occurences.\n      listenerExecutor = Execs.scheduledSingleThreaded(\"K8sDruidNodeDiscoveryProvider-ListenerExecutor\");\n\n      LOGGER.info(\"started\");\n\n      lifecycleLock.started();\n    }\n    finally {\n      lifecycleLock.exitStart();\n    }\n  }\n","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-core/kubernetes-extensions/src/main/java/org/apache/druid/k8s/discovery/K8sDruidNodeDiscoveryProvider.java#L122-L158","documentation":"K8sDruidNodeDiscoveryProvider.start() throws this IllegalStateException when the internal LifecycleLock refuses the start request. The lock only allows a start when the instance is currently in the stopped/not-started state and no start is already in progress. This guards against initializing Kubernetes watchers twice, which would leak executor threads and duplicate node announcements.","triggerScenarios":"Calling start() twice on the same provider instance; calling start() concurrently from two threads so both pass between checks; calling start() after stop() (lock does not allow restart); a prior start() threw mid-way leaving the lock in a bad state.","commonSituations":"Druid extension lifecycle double-injects the provider as both a Lifecycle.Start and a service; tests calling start() in @BeforeEach without stopping in @AfterEach; misconfigured Guice multibindings instantiating two providers where one lifecycle manages both.","solutions":["Ensure start() is called exactly once per instance, e.g. from a Druid Lifecycle entry point only","In tests, stop the provider in an @AfterEach/cleanup and create a fresh instance per test","Check that the prior start() did not throw before lifecycleLock.succeededStart() completed - if it did, the instance is unusable; construct a new one","If restart is needed, create a new K8sDruidNodeDiscoveryProvider instead of re-calling start() on a stopped instance"],"exampleFix":"// before\nprovider.start();\nprovider.start(); // throws ISE(\"can't start.\")\n// after\nif (!started) {\n  provider.start();\n  started = true;\n}","handlingStrategy":"validation","validationCode":"if (providerStarted) { throw new IllegalStateException(\"provider already started\"); }","typeGuard":null,"tryCatchPattern":"try { provider.start(); started = true; } catch (IllegalStateException e) { log.warn(\"start refused: %s\", e.getMessage()); }","preventionTips":["Call start() exactly once, from a single lifecycle owner","Pair every start() with stop() in test setup/teardown","Never restart a stopped instance; build a new one"],"tags":["lifecycle","illegal-state","kubernetes","double-start"],"backgroundTag":"invalid-state-transition","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}