{"record":{"id":"9ee724db98ad6e93","repo":"apache/druid","slug":"can-t-start-9ee724","errorCode":null,"errorMessage":"can't start.","messagePattern":"can't start\\.","errorType":"exception","errorClass":"ISE","httpStatus":null,"severity":"error","filePath":"extensions-core/druid-catalog/src/main/java/org/apache/druid/catalog/sync/CatalogUpdateReceiver.java","lineNumber":69,"sourceCode":"  private final LifecycleLock lifecycleLock = new LifecycleLock();\n  private final ScheduledExecutorService exec;\n\n  @Inject\n  public CatalogUpdateReceiver(\n      final CachedMetadataCatalog cachedCatalog,\n      final CatalogClientConfig clientConfig\n  )\n  {\n    this.cachedCatalog = cachedCatalog;\n    this.config = clientConfig;\n    this.exec = Execs.scheduledSingleThreaded(\"CatalogUpdateReceiver-Exec--%d\");\n  }\n\n  @LifecycleStart\n  public void start()\n  {\n    if (!lifecycleLock.canStart()) {\n      throw new ISE(\"can't start.\");\n    }\n\n    try {\n      final Duration delay = new Duration(config.getPollingPeriod());\n      final long maxDelayFuzz = config.getMaxRandomDelay();\n      ScheduledExecutors.scheduleWithFixedDelay(\n          exec,\n          delay,\n          delay,\n          () -> {\n            try {\n              long randomDelay = ThreadLocalRandom.current().nextLong(0, maxDelayFuzz);\n              Thread.sleep(randomDelay);\n              LOG.debug(\"Scheduled catalog refresh running\");\n              resync();\n              LOG.debug(\"Scheduled catalog refresh is done\");\n            }\n            catch (Throwable t) {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-core/druid-catalog/src/main/java/org/apache/druid/catalog/sync/CatalogUpdateReceiver.java#L51-L87","documentation":"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.","triggerScenarios":"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.","commonSituations":"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().","solutions":["Start the receiver once via a Lifecycle (addHandler) instead of manual invocation so state transitions are managed.","Ensure stop() completes (awaitStopped) before attempting start again, or construct a new receiver instance.","Guard test code so it doesn't start the receiver both directly and through the lifecycle.","Catch ISE and treat it as 'already started' if idempotent start is desired."],"exampleFix":"// before\nreceiver.start();\nreceiver.start(); // ISE: can't start.\n\n// after\nif (!started) {\n  receiver.start();\n  started = true;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  receiver.start();\n} catch (ISE e) {\n  if (\"can't start.\".equals(e.getMessage())) {\n    // already started or stopped; ignore or log\n  }\n}","preventionTips":["Manage start/stop through a Lifecycle instead of manual calls.","Call start exactly once per receiver instance.","In tests, avoid double-starting via both direct call and lifecycle hook.","Await stop completion before restarting."],"tags":["lifecycle","state","startup"],"backgroundTag":"invalid-state-transition","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}