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
- Only call stop() after a confirmed successful start()
- In tests, guard teardown: track whether start succeeded before stopping
- Catch/ignore IllegalStateException in cleanup code if the start may have failed
- 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
- Only stop after a confirmed successful start
- Track lifecycle state with a boolean before delegating
- Avoid double-close via both Lifecycle registration and manual stop
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.