{"record":{"id":"ab6f38c343fb479b","repo":"apache/druid","slug":"lifecycle-has-already-started","errorCode":null,"errorMessage":"Lifecycle has already started.","messagePattern":"Lifecycle has already started\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"server/src/main/java/org/apache/druid/client/HttpServerInventoryView.java","lineNumber":285,"sourceCode":"        inventorySyncExecutor.shutdownNow();\n      }\n      if (monitoringExecutor != null) {\n        monitoringExecutor.shutdownNow();\n      }\n\n      log.info(\"Stopped executor[%s].\", execNamePrefix);\n    }\n  }\n\n  @Override\n  public void registerSegmentCallback(\n      Executor exec,\n      SegmentCallback callback,\n      Predicate<Pair<DruidServerMetadata, DataSegment>> filter\n  )\n  {\n    if (lifecycleLock.isStarted()) {\n      throw new ISE(\"Lifecycle has already started.\");\n    }\n\n    SegmentCallback filteringSegmentCallback = new FilteringSegmentCallback(callback, filter);\n    segmentCallbacks.put(filteringSegmentCallback, exec);\n    segmentPredicates.put(filteringSegmentCallback, filter);\n\n    updateFinalPredicate();\n  }\n\n  @Override\n  public void registerServerCallback(Executor exec, ServerCallback callback)\n  {\n    if (lifecycleLock.isStarted()) {\n      throw new ISE(\"Lifecycle has already started.\");\n    }\n\n    serverCallbacks.put(callback, exec);\n  }","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/server/src/main/java/org/apache/druid/client/HttpServerInventoryView.java#L267-L303","documentation":"registerSegmentCallback(Executor, SegmentCallback, Predicate) must be invoked before the server inventory view starts. Once lifecycleLock.isStarted() is true, registering new callbacks is rejected with ISE because the registration maps feed the sync executor only at startup.","triggerScenarios":"Calling the filtered registerSegmentCallback after start() has run (or after the enclosing Lifecycle started the view).","commonSituations":"Broker extension plugins adding segment filters lazily after service startup; race between a management thread and lifecycle startup; tests that add callbacks in @BeforeEach while a shared instance was already started.","solutions":["Register all callbacks before calling start() or before the Lifecycle begins","Restructure so callbacks are known at construction time and passed via configuration/DI","Use a fresh HttpServerInventoryView instance if registration must happen later","If dynamic registration is needed, wrap the callback to change its behavior rather than re-registering"],"exampleFix":"// before\nview.start();\nview.registerSegmentCallback(exec, cb, filter); // ISE\n// after\nview.registerSegmentCallback(exec, cb, filter);\nview.start();","handlingStrategy":"validation","validationCode":"// register only before start\nif (!isStarted) view.registerSegmentCallback(exec, cb, filter);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Register all callbacks during initialization before Lifecycle.start","Pass callbacks via constructor/DI rather than late registration","Keep a startup checklist ordering: register -> start"],"tags":["lifecycle","illegal-state","callbacks"],"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-17T15:17:12.973Z"}