{"record":{"id":"f646ee8ba34b8ef9","repo":"apache/druid","slug":"can-t-stop-f646ee","errorCode":null,"errorMessage":"can't stop.","messagePattern":"can't stop\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"server/src/main/java/org/apache/druid/curator/discovery/CuratorDruidNodeDiscoveryProvider.java","lineNumber":150,"sourceCode":"    try {\n      // This is single-threaded to ensure that all listener calls are executed precisely in the order of add/remove\n      // event occurrences.\n      listenerExecutor = Execs.scheduledSingleThreaded(\"CuratorDruidNodeDiscoveryProvider-ListenerExecutor\");\n\n      log.debug(\"Started.\");\n\n      lifecycleLock.started();\n    }\n    finally {\n      lifecycleLock.exitStart();\n    }\n  }\n\n  @LifecycleStop\n  public void stop() throws IOException\n  {\n    if (!lifecycleLock.canStop()) {\n      throw new ISE(\"can't stop.\");\n    }\n\n    log.debug(\"Stopping.\");\n\n    Closer closer = Closer.create();\n    closer.registerAll(nodeRoleWatchers.values());\n    closer.registerAll(nodeDiscoverers);\n\n    CloseableUtils.closeAll(closer, listenerExecutor::shutdownNow);\n  }\n\n  private static class NodeRoleWatcher implements DruidNodeDiscovery, Closeable\n  {\n    private static final Logger log = new Logger(NodeRoleWatcher.class);\n\n    private final CuratorFramework curatorFramework;\n\n    private final NodeRole nodeRole;","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/server/src/main/java/org/apache/druid/curator/discovery/CuratorDruidNodeDiscoveryProvider.java#L132-L168","documentation":"CuratorDruidNodeDiscoveryProvider.stop() is guarded by LifecycleStop and throws ISE(\"can't stop.\") when the lifecycle lock disallows stopping — typically because the provider was never started or is already stopping/stopped.","triggerScenarios":"Calling stop() before start(), calling stop() twice, or concurrent stop during shutdown; observed via the test_stop_interruptsPollingThread path.","commonSituations":"Shutdown hooks firing after an earlier stop, tests tearing down fixtures that never started, Lifecycle stop racing with an error path that already stopped it.","solutions":["Only call stop() after a successful start()","Make stop idempotent at the call site (track a stopped flag or route through the Lifecycle)","In tests, start the provider in setup before stopping in teardown"],"exampleFix":"// before\nprovider.stop(); // provider never started\n// after\nif (started) {\n  provider.stop();\n}","handlingStrategy":"type-guard","validationCode":"if (!lifecycleLock.canStop()) {\n  return; // never started or already stopping\n}","typeGuard":"boolean startedAndStoppable = lifecycleLock.canStop();","tryCatchPattern":"try {\n  provider.stop();\n} catch (IllegalStateException e) {\n  if (\"can't stop.\".equals(e.getMessage())) {\n    log.debug(\"provider was not started; nothing to stop\");\n  } else {\n    throw e;\n  }\n}","preventionTips":["Track start success and only stop if started","In tests, start in setup before stopping in teardown","Avoid duplicate stop paths (Lifecycle + manual stop)"],"tags":["lifecycle","zookeeper","curator","state"],"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"}