apache/druid · error · IllegalStateException

can't start.

Error message

can't start.

What it means

Thrown by CoordinatorPollingBasicAuthenticatorCacheManager.start when lifecycleLock.canStart() returns false. This manager polls the coordinator for authenticator user maps; like all Druid lifecycle components it may only transition from an idle state into started. Double-start, start-after-stop, or concurrent start calls all fail here.

Source

Thrown at extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authentication/db/cache/CoordinatorPollingBasicAuthenticatorCacheManager.java:102

      BasicAuthCommonCacheConfig commonCacheConfig,
      @Smile ObjectMapper objectMapper,
      @Coordinator ServiceClient coordinatorClient
  )
  {
    this.exec = Execs.scheduledSingleThreaded("BasicAuthenticatorCacheManager-Exec--%d");
    this.injector = injector;
    this.commonCacheConfig = commonCacheConfig;
    this.objectMapper = objectMapper;
    this.cachedUserMaps = new ConcurrentHashMap<>();
    this.authenticatorPrefixes = new HashSet<>();
    this.coordinatorClient = coordinatorClient;
  }

  @LifecycleStart
  public void start()
  {
    if (!lifecycleLock.canStart()) {
      throw new ISE("can't start.");
    }

    LOG.info("Starting CoordinatorPollingBasicAuthenticatorCacheManager.");

    try {
      initUserMaps();

      ScheduledExecutors.scheduleWithFixedDelay(
          exec,
          new Duration(commonCacheConfig.getPollingPeriod()),
          new Duration(commonCacheConfig.getPollingPeriod()),
          () -> {
            try {
              long randomDelay = ThreadLocalRandom.current().nextLong(0, commonCacheConfig.getMaxRandomDelay());
              LOG.debug("Inserting cachedUserMaps random polling delay of [%s] ms", randomDelay);
              Thread.sleep(randomDelay);

              LOG.debug("Scheduled user cache poll is running");

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Call stop() and create a fresh instance instead of re-starting a started/stopped manager
  2. Verify the manager is registered once in the lifecycle and started exactly once
  3. Guard callers with a state check before invoking start
  4. In tests, stop the manager in @AfterEach to reset state

Example fix

// before
manager.start(); manager.start(); // second call throws
// after
if (!started) { manager.start(); started = true; }
Defensive patterns

Strategy: type-guard

Validate before calling

boolean startOnce(AtomicBoolean started, CoordinatorPollingBasicAuthenticatorCacheManager m) { return started.compareAndSet(false, true) && (m.start(), true); }

Type guard

boolean isStartable(CoordinatorPollingBasicAuthenticatorCacheManager m) { try { return m != null && !m.started(); } catch (UnsupportedOperationException e) { return true; } } // track your own started flag

Try / catch

try { manager.start(); } catch (ISE e) { LOG.warn("cache manager start rejected (already started/stopped)"); }

Prevention

When it happens

Trigger: start() called twice on the same manager instance; start() after stop(); racing lifecycle threads where one thread already claimed the transition; test code manually starting the manager after the suite lifecycle already did.

Common situations: Incorrect lifecycle registration order; test teardown not stopping the manager between cases; leadership handover code restarting the manager without proper stop first.

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