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
- Register the listener exactly once per selector instance; create a new K8sDruidLeaderSelector if you need to restart.
- Call unregisterListener() and wait for stop to complete before attempting registerListener again.
- Guard the call site with isLeader()/state checks rather than relying on registerListener for idempotency.
- 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
- Register the listener once per selector instance (e.g. in an init-once path).
- Never call registerListener concurrently from multiple threads.
- Unregister and wait before any re-registration; prefer new instances for restarts.
- Avoid re-registering on config reload; reconfigure the listener instead.
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.