{"record":{"id":"bbb8aaa43acb76f4","repo":"apache/druid","slug":"can-t-stop-bbb8aa","errorCode":null,"errorMessage":"can't stop.","messagePattern":"can't stop\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"server/src/main/java/org/apache/druid/query/lookup/LookupReferencesManager.java","lineNumber":237,"sourceCode":"        LOG.error(ex, \"Exception occurred while handling lookup notice [%s].\", notice);\n        LOG.makeAlert(\"Exception occurred while handling lookup notice, with message [%s].\", ex.getMessage()).emit();\n      }\n    }\n\n    takeSnapshot(lookupMap);\n\n    ImmutableMap<String, LookupExtractorFactoryContainer> immutableLookupMap = ImmutableMap.copyOf(lookupMap);\n\n    atomicallyUpdateStateRef(\n        oldState -> new LookupUpdateState(immutableLookupMap, oldState.pendingNotices, ImmutableList.of())\n    );\n  }\n\n  @LifecycleStop\n  public void stop()\n  {\n    if (!lifecycleLock.canStop()) {\n      throw new ISE(\"can't stop.\");\n    }\n\n    LOG.debug(\"LookupExtractorFactoryContainerProvider is stopping.\");\n\n    if (!testMode) {\n      mainThread.interrupt();\n\n      try {\n        mainThread.join();\n      }\n      catch (InterruptedException ex) {\n        throw new ISE(\"failed to stop, mainThread couldn't finish.\");\n      }\n    }\n\n    for (Map.Entry<String, LookupExtractorFactoryContainer> e : stateRef.get().lookupMap.entrySet()) {\n      try {\n        if (e.getValue().getLookupExtractorFactory().close()) {","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/server/src/main/java/org/apache/druid/query/lookup/LookupReferencesManager.java#L219-L255","documentation":"LookupReferencesManager.stop(), annotated @LifecycleStop, throws IllegalStateException 'can't stop.' when lifecycleLock.canStop() is false — i.e., the manager was never started, is still starting, or was already stopped. The lock guarantees start/stop pairs are ordered and single-shot.","triggerScenarios":"Calling stop() before start() completed (or at all), calling stop() twice, or a shutdown hook racing with another component that already stopped the manager.","commonSituations":"Service shutdown ordering issues in embedded/test setups; tests (like testRealModeWithMainThread) that stop the manager then attempt a second stop; graceful-shutdown hooks firing twice.","solutions":["Ensure stop() is only called after a successful start() and only once; rely on the Druid Lifecycle for shutdown ordering.","Guard your shutdown code with a started flag or check lifecycle state before calling stop().","In tests, do not reuse a stopped instance; build a new one for the next start/stop cycle.","If stop races during JVM shutdown, catch/ignore the IllegalStateException since teardown is already in progress."],"exampleFix":"// before\nmanager.stop();\nmanager.stop(); // ISE: can't stop.\n// after\nif (!stopped) {\n  manager.stop();\n  stopped = true;\n}","handlingStrategy":"validation","validationCode":"// only stop if previously started and not already stopped\nif (started && !stopped) { manager.stop(); stopped = true; }","typeGuard":null,"tryCatchPattern":"try {\n  manager.stop();\n} catch (IllegalStateException e) {\n  // not started or already stopped: safe to ignore during shutdown\n}","preventionTips":["Pair every start() with exactly one stop()","Route shutdown through the Druid Lifecycle rather than ad-hoc calls","Make stop() idempotent in caller code with a flag","In tests, recreate the manager instead of restarting it"],"tags":["lifecycle","lookup","shutdown"],"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"}