{"record":{"id":"8c3def731b444ad2","repo":"apache/druid","slug":"destroy-method-returned-false-for-lookup-s-s","errorCode":null,"errorMessage":"destroy method returned false for lookup [%s]:[%s]","messagePattern":"destroy method returned false for lookup \\[(.+?)\\]:\\[(.+?)\\]","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"server/src/main/java/org/apache/druid/query/lookup/LookupReferencesManager.java","lineNumber":610,"sourceCode":"\n  private LookupUpdateState atomicallyUpdateStateRef(Function<LookupUpdateState, LookupUpdateState> fn)\n  {\n    while (true) {\n      LookupUpdateState old = stateRef.get();\n      LookupUpdateState newState = fn.apply(old);\n      if (stateRef.compareAndSet(old, newState)) {\n        return newState;\n      }\n    }\n  }\n\n  private void dropContainer(LookupExtractorFactoryContainer container, String lookupName)\n  {\n    if (container != null) {\n      LOG.debug(\"Removed lookup [%s] with spec [%s].\", lookupName, container);\n\n      if (!container.getLookupExtractorFactory().destroy()) {\n        throw new ISE(\n            \"destroy method returned false for lookup [%s]:[%s]\",\n            lookupName,\n            container\n        );\n      }\n    }\n  }\n\n  @VisibleForTesting\n  interface Notice\n  {\n    void handle(Map<String, LookupExtractorFactoryContainer> lookupMap, LookupReferencesManager manager)\n        throws Exception;\n  }\n\n  private static class LoadNotice implements Notice\n  {\n    private final String lookupName;","sourceCodeStart":592,"sourceCodeEnd":628,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/server/src/main/java/org/apache/druid/query/lookup/LookupReferencesManager.java#L592-L628","documentation":"dropContainer() removes a lookup: after logging, it calls the container's LookupExtractorFactory.destroy(). If destroy() returns false the lookup could not be torn down cleanly, so an IllegalStateException is thrown naming the lookup and container.","triggerScenarios":"handle() processing a DropNotice calls dropContainer(); the factory's destroy() returns false, e.g., the factory refuses to destroy because it is still started/in-use or its backing resources can't be released.","commonSituations":"Lookups still referenced/started when dropped; external store connection failure preventing resource cleanup; factory implementations returning false on non-fatal cleanup issues.","solutions":["Ensure the factory is stopped/closed appropriately before destroy, or that destroy() is idempotent in the factory implementation.","Check factory logs for the underlying reason destroy() returned false.","Retry the drop after resources are released (the notice will be reprocessed or reissued).","If a custom factory, fix destroy() to release resources or return true when already destroyed."],"exampleFix":"// before: custom factory destroy always false while started\npublic boolean destroy() { if (started) { return false; } ... }\n// after: stop then destroy\npublic boolean destroy() { if (started) { stop(); } resources.release(); return true; }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { manager.remove(name, container); } catch (IllegalStateException e) { if (e.getMessage().startsWith(\"destroy method returned false\")) { LOG.warn(\"lookup %s not cleanly destroyed; will retry\", name); retryDropLater(name, container); } else { throw e; } }","preventionTips":["Ensure factories implement idempotent destroy()","Verify backing-store connectivity so cleanup can complete","Avoid dropping lookups while their factories are mid-start","Inspect factory logs for the false return's cause"],"tags":["lookup","cleanup-failure","resource-leak"],"backgroundTag":"resource-cleanup-failed","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"}