apache/druid · error · IllegalStateException

can't start.

Error message

can't start.

What it means

Thrown by CoordinatorBasicAuthenticatorMetadataStorageUpdater.start when lifecycleLock.canStart() returns false. This updater writes authenticator user maps to metadata storage; it must transition once from idle to started. Double-start, start-after-stop, or concurrent start attempts are rejected here as illegal state transitions.

Source

Thrown at extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authentication/db/updater/CoordinatorBasicAuthenticatorMetadataStorageUpdater.java:113

                                  // set a dependency here
  )
  {
    this.exec = Execs.scheduledSingleThreaded("CoordinatorBasicAuthenticatorMetadataStorageUpdater-Exec--%d");
    this.authenticatorMapper = authenticatorMapper;
    this.connector = connector;
    this.connectorConfig = connectorConfig;
    this.commonCacheConfig = commonCacheConfig;
    this.objectMapper = objectMapper;
    this.cacheNotifier = cacheNotifier;
    this.cachedUserMaps = new ConcurrentHashMap<>();
    this.authenticatorPrefixes = new HashSet<>();
  }

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

    if (authenticatorMapper == null || authenticatorMapper.getAuthenticatorMap() == null) {
      return;
    }

    try {
      LOG.info("Starting CoordinatorBasicAuthenticatorMetadataStorageUpdater.");
      BasicAuthUtils.maybeInitialize(
          () -> {
            for (Map.Entry<String, Authenticator> entry : authenticatorMapper.getAuthenticatorMap().entrySet()) {
              Authenticator authenticator = entry.getValue();
              if (authenticator instanceof BasicHTTPAuthenticator) {
                String authenticatorName = entry.getKey();
                authenticatorPrefixes.add(authenticatorName);
                BasicHTTPAuthenticator basicHTTPAuthenticator = (BasicHTTPAuthenticator) authenticator;
                BasicAuthDBConfig dbConfig = basicHTTPAuthenticator.getDbConfig();
                byte[] userMapBytes = getCurrentUserMapBytes(authenticatorName);

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Create a fresh updater instance rather than restarting a stopped one
  2. Confirm the updater's @LifecycleStart handler is registered once and invoked once
  3. Serialize lifecycle transitions (single owner thread/leader invokes start/stop)
  4. Check logs for a prior start/stop that already moved the lifecycle lock

Example fix

// before
updater.start();
// after
if (lifecycleLock.canStart()) { updater.start(); } // or recreate instance after stop
Defensive patterns

Strategy: type-guard

Validate before calling

if (!updaterStarted.compareAndSet(false, true)) { return; } updater.start();

Type guard

boolean canStart(CoordinatorBasicAuthenticatorMetadataStorageUpdater u) { return u != null && !stoppedFlag.get(); }

Try / catch

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

Prevention

When it happens

Trigger: Calling start() twice on the updater; start() after stop() (stopped=true already set); lifecycle races between leadership election callbacks; manually starting in tests after framework start.

Common situations: Misordered lifecycle handler registration; restarting the updater on coordinator leadership change without proper stop; test suites reusing a stopped instance.

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