apache/druid · error · IllegalStateException

can't stop.

Error message

can't stop.

What it means

K8sDruidNodeDiscoveryProvider.stop() throws this IllegalStateException when the LifecycleLock says the instance is not in a started state (or already stopping/stopped). Stopping an unstarted provider would attempt to shut down NodeRoleWatcher threads that were never created.

Source

Thrown at extensions-core/kubernetes-extensions/src/main/java/org/apache/druid/k8s/discovery/K8sDruidNodeDiscoveryProvider.java:163

      // This is single-threaded to ensure that all listener calls are executed precisely in the oder of add/remove
      // event occurences.
      listenerExecutor = Execs.scheduledSingleThreaded("K8sDruidNodeDiscoveryProvider-ListenerExecutor");

      LOGGER.info("started");

      lifecycleLock.started();
    }
    finally {
      lifecycleLock.exitStart();
    }
  }

  @LifecycleStop
  public void stop()
  {
    if (!lifecycleLock.canStop()) {
      throw new ISE("can't stop.");
    }

    LOGGER.info("stopping");

    for (NodeRoleWatcher watcher : nodeTypeWatchers.values()) {
      watcher.stop();
    }
    listenerExecutor.shutdownNow();

    LOGGER.info("stopped");
  }

  @VisibleForTesting
  static class NodeRoleWatcher implements DruidNodeDiscovery
  {
    private static final Logger LOGGER = new Logger(NodeRoleWatcher.class);

    private final PodInfo podInfo;

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Only call stop() after a confirmed successful start()
  2. In tests, guard teardown: track whether start succeeded before stopping
  3. Catch/ignore IllegalStateException in cleanup code if the start may have failed
  4. Create a fresh instance rather than stopping an already-stopped provider

Example fix

// before
provider.stop(); // may throw if never started
// after
try {
  provider.stop();
} catch (IllegalStateException e) {
  // already stopped or never started
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!started) { return; } // nothing to stop

Try / catch

try { provider.stop(); } catch (IllegalStateException e) { /* not started or already stopped */ }

Prevention

When it happens

Trigger: Calling stop() before start(); calling stop() twice; calling stop() concurrently with a start that hasn't finished; calling stop() after start() threw before completing successfully.

Common situations: @AfterEach teardown in tests when @BeforeEach start failed; Druid shutdown hook firing after a failed startup; double-close through both Lifecycle and try-with-resources.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/eed1fbd74fcf8c61. Report an issue: GitHub.