apache/druid · error · ISE

can't start.

Error message

can't start.

What it means

CatalogUpdateReceiver.start() uses a LifecycleLock; if canStart() returns false the receiver is in a state where starting is illegal (already started, stopped, or shut down), so it throws ISE("can't start."). This is Druid's standard lifecycle guard against double start or start-after-stop.

Source

Thrown at extensions-core/druid-catalog/src/main/java/org/apache/druid/catalog/sync/CatalogUpdateReceiver.java:69

  private final LifecycleLock lifecycleLock = new LifecycleLock();
  private final ScheduledExecutorService exec;

  @Inject
  public CatalogUpdateReceiver(
      final CachedMetadataCatalog cachedCatalog,
      final CatalogClientConfig clientConfig
  )
  {
    this.cachedCatalog = cachedCatalog;
    this.config = clientConfig;
    this.exec = Execs.scheduledSingleThreaded("CatalogUpdateReceiver-Exec--%d");
  }

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

    try {
      final Duration delay = new Duration(config.getPollingPeriod());
      final long maxDelayFuzz = config.getMaxRandomDelay();
      ScheduledExecutors.scheduleWithFixedDelay(
          exec,
          delay,
          delay,
          () -> {
            try {
              long randomDelay = ThreadLocalRandom.current().nextLong(0, maxDelayFuzz);
              Thread.sleep(randomDelay);
              LOG.debug("Scheduled catalog refresh running");
              resync();
              LOG.debug("Scheduled catalog refresh is done");
            }
            catch (Throwable t) {

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Start the receiver once via a Lifecycle (addHandler) instead of manual invocation so state transitions are managed.
  2. Ensure stop() completes (awaitStopped) before attempting start again, or construct a new receiver instance.
  3. Guard test code so it doesn't start the receiver both directly and through the lifecycle.
  4. Catch ISE and treat it as 'already started' if idempotent start is desired.

Example fix

// before
receiver.start();
receiver.start(); // ISE: can't start.

// after
if (!started) {
  receiver.start();
  started = true;
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  receiver.start();
} catch (ISE e) {
  if ("can't start.".equals(e.getMessage())) {
    // already started or stopped; ignore or log
  }
}

Prevention

When it happens

Trigger: Calling start() twice on the same receiver; calling start() after stop() or after lifecycle shutdown was initiated; concurrent start/stop from different threads without lifecycle coordination.

Common situations: Test code manually invoking start() alongside a Lifecycle that also starts it; restarting a service component without recreating the receiver; race between two threads calling start().

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/9ee724db98ad6e93. Report an issue: GitHub.