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

  1. Only call unregisterListener() after registerListener() completed successfully.
  2. Track registration state (AtomicBoolean) in the owning service before unregistering.
  3. Create the selector and register the listener in the same startup path so states stay paired.
  4. 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

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.