apache/druid · error · IllegalStateException
can't start.
Error message
can't start.
What it means
K8sDruidNodeDiscoveryProvider.start() throws this IllegalStateException when the internal LifecycleLock refuses the start request. The lock only allows a start when the instance is currently in the stopped/not-started state and no start is already in progress. This guards against initializing Kubernetes watchers twice, which would leak executor threads and duplicate node announcements.
Source
Thrown at extensions-core/kubernetes-extensions/src/main/java/org/apache/druid/k8s/discovery/K8sDruidNodeDiscoveryProvider.java:140
podInfo,
discoveryConfig,
k8sApiClient,
watcherErrorRetryWaitMS
);
if (startAfterCreation) {
nodeRoleWatcher.start();
}
LOGGER.info("Created NodeRoleWatcher for role[%s].", nType);
return nodeRoleWatcher;
}
);
}
@LifecycleStart
public void start()
{
if (!lifecycleLock.canStart()) {
throw new ISE("can't start.");
}
try {
LOGGER.info("starting");
// This is single-threaded to ensure that all listener calls are executed precisely in the oder of add/remove
// event occurences.
listenerExecutor = Execs.scheduledSingleThreaded("K8sDruidNodeDiscoveryProvider-ListenerExecutor");
LOGGER.info("started");
lifecycleLock.started();
}
finally {
lifecycleLock.exitStart();
}
}
View on GitHub (pinned to 9b90983fd2)
Solutions
- Ensure start() is called exactly once per instance, e.g. from a Druid Lifecycle entry point only
- In tests, stop the provider in an @AfterEach/cleanup and create a fresh instance per test
- Check that the prior start() did not throw before lifecycleLock.succeededStart() completed - if it did, the instance is unusable; construct a new one
- If restart is needed, create a new K8sDruidNodeDiscoveryProvider instead of re-calling start() on a stopped instance
Example fix
// before
provider.start();
provider.start(); // throws ISE("can't start.")
// after
if (!started) {
provider.start();
started = true;
} Defensive patterns
Strategy: validation
Validate before calling
if (providerStarted) { throw new IllegalStateException("provider already started"); } Try / catch
try { provider.start(); started = true; } catch (IllegalStateException e) { log.warn("start refused: %s", e.getMessage()); } Prevention
- Call start() exactly once, from a single lifecycle owner
- Pair every start() with stop() in test setup/teardown
- Never restart a stopped instance; build a new one
When it happens
Trigger: Calling start() twice on the same provider instance; calling start() concurrently from two threads so both pass between checks; calling start() after stop() (lock does not allow restart); a prior start() threw mid-way leaving the lock in a bad state.
Common situations: Druid extension lifecycle double-injects the provider as both a Lifecycle.Start and a service; tests calling start() in @BeforeEach without stopping in @AfterEach; misconfigured Guice multibindings instantiating two providers where one lifecycle manages both.
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/8a1b40f03f76c441.
Report an issue: GitHub.