{"record":{"id":"cee54c0c80eaf966","repo":"apache/dubbo","slug":"extensionloader-is-destroyed-type","errorCode":null,"errorMessage":"ExtensionLoader is destroyed: ${type}","messagePattern":"ExtensionLoader is destroyed: (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"critical","filePath":"dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java","lineNumber":283,"sourceCode":"\n        // destroy wrapped extension instance\n        for (Holder<Object> holder : cachedInstances.values()) {\n            Object wrappedInstance = holder.get();\n            if (wrappedInstance instanceof Disposable) {\n                Disposable disposable = (Disposable) wrappedInstance;\n                try {\n                    disposable.destroy();\n                } catch (Exception e) {\n                    logger.error(COMMON_ERROR_LOAD_EXTENSION, \"\", \"\", \"Error destroying extension \" + disposable, e);\n                }\n            }\n        }\n        cachedInstances.clear();\n    }\n\n    private void checkDestroyed() {\n        if (destroyed.get()) {\n            throw new IllegalStateException(\"ExtensionLoader is destroyed: \" + type);\n        }\n    }\n\n    public String getExtensionName(T extensionInstance) {\n        return getExtensionName(extensionInstance.getClass());\n    }\n\n    public String getExtensionName(Class<?> extensionClass) {\n        getExtensionClasses(); // load class\n        return cachedNames.get(extensionClass);\n    }\n\n    /**\n     * This is equivalent to {@code getActivateExtension(url, key, null)}\n     *\n     * @param url url\n     * @param key url parameter key which used to get extension point names\n     * @return extension list which are activated.","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/apache/dubbo/blob/3a3043227f5571d25eb2889de5bca22f2914843b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java#L265-L301","documentation":"Thrown by ExtensionLoader.checkDestroyed() when any method on an ExtensionLoader is called after its destroy() method was invoked. ExtensionLoader instances are destroyed when their parent ExtensionDirector.destroy() is called, which cascades to all managed loaders. The message includes the SPI type so you can identify which extension loader was accessed post-destruction.","triggerScenarios":"Calling any ExtensionLoader method (getExtension, getAdaptiveExtension, hasExtension, getSupportedExtensions, addExtension, replaceExtension) after the owning ExtensionDirector was destroyed. Usually a cached/stale ExtensionLoader reference survives scope teardown and is accessed by a late callback.","commonSituations":"A cached ExtensionLoader field or singleton is accessed during shutdown by a cleanup callback. Static initializers that capture an ExtensionLoader reference and are re-invoked during redeploy. A registry or protocol component was destroyed but a reference to its loader persists in a thread-local or cache.","solutions":["Trace the type name in the message to identify which SPI extension loader was accessed post-destroy.","Find the stale reference: search for cached ExtensionLoader fields or statics that outlive the scope.","Clear all cached ExtensionLoader references in your own shutdown hooks before calling dubboBootstrap.stop().","Ensure the code path that triggers the late access is gated by the application/module lifecycle state."],"exampleFix":"// before — stale cached loader accessed after shutdown\nprivate static final ExtensionLoader<Protocol> LOADER =\n    director.getExtensionLoader(Protocol.class);\n// ... later after director.destroy():\nLOADER.getExtension(\"dubbo\"); // throws\n\n// after — obtain loader fresh or check lifecycle\nif (!director.isDestroyed()) { // hypothetical guard\n    director.getExtensionLoader(Protocol.class).getExtension(\"dubbo\");\n}","handlingStrategy":"try-catch","validationCode":"// ExtensionLoader has no public isDestroyed() method.\n// Gate access externally by tracking the owning scope's lifecycle.\nif (!scopeModelDestroyed.get()) {\n    T ext = loader.getExtension(name);\n}","typeGuard":null,"tryCatchPattern":"try {\n    T ext = loader.getExtension(name);\n} catch (IllegalStateException e) {\n    if (e.getMessage().startsWith(\"ExtensionLoader is destroyed\")) {\n        log.warn(\"ExtensionLoader for {} is destroyed — returning null\", type, e);\n        return null;\n    }\n    throw e;\n}","preventionTips":["Do not cache ExtensionLoader references in static fields that outlive the scope model.","Clear cached loader references in your shutdown hooks before destroying the Dubbo scope.","Ensure cleanup callbacks and finalizers do not access extension loaders after shutdown."],"tags":["dubbo-spi","lifecycle","shutdown","resource-management","stale-reference"],"backgroundTag":null,"analyzedSha":"3a3043227f5571d25eb2889de5bca22f2914843b","analyzedAt":"2026-08-14T00:43:19.853Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}