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

  1. Ensure start() is called exactly once per instance, e.g. from a Druid Lifecycle entry point only
  2. In tests, stop the provider in an @AfterEach/cleanup and create a fresh instance per test
  3. Check that the prior start() did not throw before lifecycleLock.succeededStart() completed - if it did, the instance is unusable; construct a new one
  4. 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

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.