apache/druid · error · IllegalStateException
can't stop
Error message
can't stop
What it means
ConsulLeaderSelector.unregisterListener() throws this ISE when the lifecycle lock says it cannot stop, i.e. the selector was never started via registerListener() or unregister is called twice/concurrently with registration.
Source
Thrown at extensions-contrib/consul-extensions/src/main/java/org/apache/druid/consul/discovery/ConsulLeaderSelector.java:165
this.sessionKeeperService = Execs.scheduledSingleThreaded("ConsulSessionKeeper-%d");
startLeaderElection();
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");
}
LOGGER.info("Unregistering leader selector for [%s]", lockKey);
stopping = true;
try {
if (leader.get()) {
try {
listener.stopBeingLeader();
}
catch (Exception e) {
LOGGER.error(e, "Exception while stopping being leader");
}
leader.set(false);
}
// Destroying session releases the Consul lock, allowing another node to become leader
if (sessionId != null) {View on GitHub (pinned to 9b90983fd2)
Solutions
- Only call unregisterListener() after registerListener() completed successfully.
- Track registration state (AtomicBoolean) in the owning service before unregistering.
- Create the selector and register the listener in the same startup path so states stay paired.
- If registerListener() failed, let its error handling clean up rather than calling unregisterListener().
Example fix
// before
service.stop() { leaderSelector.unregisterListener(); } // ISE if never registered
// after
if (registered) {
leaderSelector.unregisterListener();
registered = false;
} Defensive patterns
Strategy: validation
Validate before calling
if (registered.compareAndSet(true, false)) { leaderSelector.unregisterListener(); } Prevention
- Only unregister after a successful registerListener().
- Track registration state in the owning service.
- Let failed registrations clean up their own resources.
When it happens
Trigger: Calling unregisterListener() without a prior successful registerListener(), calling it twice, or racing it against registerListener().
Common situations: Teardown code that always unregisters regardless of registration state, a registerListener() that failed earlier (e.g. Consul connection error) leaving the selector unstarted, or duplicate shutdown hooks.
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/a797c7b018003de9.
Report an issue: GitHub.