apache/druid · error · IllegalStateException

can't start.

Error message

can't start.

What it means

registerListener() guards startup with LifecycleLock.canStart(); if the selector is already started (or concurrently starting/stopping) the lock refuses and an IllegalStateException "can't start." is thrown. Each K8sDruidLeaderSelector instance may only have its listener registered once.

Source

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

  @Override
  public boolean isLeader()
  {
    return leader;
  }

  @Override
  public int localTerm()
  {
    return term;
  }

  @Override
  public void registerListener(DruidLeaderSelector.Listener listener)
  {
    Preconditions.checkArgument(listener != null, "listener is null.");

    if (!lifecycleLock.canStart()) {
      throw new ISE("can't start.");
    }
    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()) {

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Register the listener exactly once per selector instance; create a new K8sDruidLeaderSelector if you need to restart.
  2. Call unregisterListener() and wait for stop to complete before attempting registerListener again.
  3. Guard the call site with isLeader()/state checks rather than relying on registerListener for idempotency.
  4. Serialize lifecycle calls (no concurrent register/unregister from multiple threads).

Example fix

// before
leaderSelector.registerListener(listener); // second call -> ISE
// after: create fresh instance or check lifecycle
if (!started) { leaderSelector.registerListener(listener); started = true; }
Defensive patterns

Strategy: type-guard

Validate before calling

if (!lifecycleLock.canStart()) { /* skip duplicate registration or recreate selector */ }

Type guard

boolean isStartable(LeaderSelector s) {
  try { return s.getCurrentLeader() != null || true; } catch (RuntimeException e) { return false; } // replace with state introspection
}
// practical guard: track registration locally
AtomicBoolean registered = new AtomicBoolean(false);
if (registered.compareAndSet(false, true)) selector.registerListener(listener);

Try / catch

try {
  leaderSelector.registerListener(listener);
} catch (ISE e) {
  LOG.warn("Listener already registered or selector stopping; ignoring");
}

Prevention

When it happens

Trigger: Calling registerListener twice on the same K8sDruidLeaderSelector instance, or calling it concurrently/unregisterListener racing it.

Common situations: Service wiring registering a listener both in init and on a reconfigure callback; tests reusing a selector instance across test methods; lifecycle races in overloaded/stop-start sequences.

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/c861cc5ad7fd7f99. Report an issue: GitHub.