apache/druid · warning · IllegalStateException
can't stop.
Error message
can't stop.
What it means
unregisterListener() requires LifecycleLock.canStop(); if the selector was never started, is already stopped, or is mid-startup, an IllegalStateException "can't stop." is thrown. It enforces balanced start/stop of the leader elector.
Source
Thrown at extensions-core/kubernetes-extensions/src/main/java/org/apache/druid/k8s/discovery/K8sDruidLeaderSelector.java:153
}
try {
this.listener = listener;
startLeaderElector(leaderLatch);
lifecycleLock.started();
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
finally {
lifecycleLock.exitStart();
}
}
@Override
public void unregisterListener()
{
if (!lifecycleLock.canStop()) {
throw new ISE("can't stop.");
}
closeLeaderLatchQuietly();
}
private void closeLeaderLatchQuietly()
{
CloseableUtils.closeAndSuppressExceptions(
leaderLatch,
e -> LOGGER.warn("Exception caught while cleaning up leader latch")
);
}
}
View on GitHub (pinned to 9b90983fd2)
Solutions
- Track whether the selector is started; only call unregisterListener after a successful registerListener.
- Make cleanup idempotent by catching/ignoring ISE in shutdown paths.
- Wait for any in-flight registerListener to finish before stopping.
- If a start failed, recreate the selector rather than stopping it.
Example fix
// before
@Override public void shutdown() { leaderSelector.unregisterListener(); } // throws if never started
// after
@Override public void shutdown() { try { leaderSelector.unregisterListener(); } catch (IllegalStateException e) { LOG.debug("not started"); } } Defensive patterns
Strategy: try-catch
Validate before calling
// only stop if you started boolean started = false; // at register: started = true; at unregister: if (!started) return;
Try / catch
try {
leaderSelector.unregisterListener();
} catch (IllegalStateException e) {
LOG.debug(e, "Selector was not started; nothing to stop");
} Prevention
- Keep start/stop balanced in the same component; guard with a boolean or lifecycle manager.
- Make shutdown hooks idempotent.
- Do not unregister after a failed registerListener; discard the instance instead.
- Serialize lifecycle calls across threads.
When it happens
Trigger: Calling unregisterListener on a K8sDruidLeaderSelector that never had registerListener succeed, or calling it twice / concurrently with a start in progress.
Common situations: Shutdown hooks firing after an earlier stop; cleanup paths running when listener registration failed earlier; double-stop in tests.
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/3b2b1bdc71f6fe2d.
Report an issue: GitHub.