apache/dubbo · critical · IllegalStateException

ExtensionDirector is destroyed

Error message

ExtensionDirector is destroyed

What it means

Thrown by ExtensionDirector.checkDestroyed() when any method is called on an ExtensionDirector whose destroy() method has already been invoked. ExtensionDirector is tied to a ScopeModel (application, module) and when that scope is shut down, the director and all its child ExtensionLoaders are destroyed. Any subsequent access is illegal.

Source

Thrown at dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionDirector.java:156

        return parent;
    }

    public void removeAllCachedLoader() {}

    public void destroy() {
        if (destroyed.compareAndSet(false, true)) {
            for (ExtensionLoader<?> extensionLoader : extensionLoadersMap.values()) {
                extensionLoader.destroy();
            }
            extensionLoadersMap.clear();
            extensionScopeMap.clear();
            extensionPostProcessors.clear();
        }
    }

    private void checkDestroyed() {
        if (destroyed.get()) {
            throw new IllegalStateException("ExtensionDirector is destroyed");
        }
    }
}

View on GitHub (pinned to 3a3043227f)

Solutions

  1. Inspect the stack trace: identify the caller that triggers extension access after shutdown.
  2. Ensure background threads and scheduled tasks are stopped before the ScopeModel/ExtensionDirector is destroyed.
  3. If using DubboBootstrap, ensure all consumers/providers are fully deregistered before application exit.
  4. Guard the call site with a check against the scope model's lifecycle state, or use a shutdown latch.

Example fix

// before — background task accesses extensions after shutdown
scheduledExecutor.scheduleAtFixedRate(() -> {
    director.getExtensionLoader(MySpi.class).getExtension("impl");
}, 0, 1, TimeUnit.SECONDS);

// after — stop the task before shutdown
ScheduledFuture<?> future = scheduledExecutor.scheduleAtFixedRate(...);
// during shutdown:
future.cancel(false);
scheduledExecutor.shutdown();
dubboBootstrap.stop();
Defensive patterns

Strategy: try-catch

Validate before calling

// ExtensionDirector has no public isDestroyed() method.
// Track lifecycle externally or wrap access in a try-catch.
// If you control the ScopeModel, gate access with your own shutdown flag.
if (!applicationShuttingDown.get()) {
    ExtensionLoader<T> loader = director.getExtensionLoader(type);
}

Try / catch

try {
    ExtensionLoader<T> loader = director.getExtensionLoader(type);
} catch (IllegalStateException e) {
    if (e.getMessage().equals("ExtensionDirector is destroyed")) {
        log.warn("ExtensionDirector already destroyed — skipping extension access", e);
        return null; // or fallback
    }
    throw e;
}

Prevention

When it happens

Trigger: Application shutdown sequence destroys the ScopeModel and its ExtensionDirector, but a background thread, timer, or lazy-init callback subsequently tries to resolve or access an extension. Also triggered by manual calls to director.destroy() followed by continued usage. Common in hot-reload, redeploy, or graceful-shutdown scenarios.

Common situations: Graceful shutdown where a lingering RPC callback or scheduled task triggers extension lookup after shutdown. Integration tests that destroy the application context but continue assertions. A custom lifecycle hook calls destroy() too early. Framework restart in a container (e.g., Tomcat undeploy/redeploy) leaves stale references.

Related errors


AI-assisted analysis of apache/dubbo@3a3043227f (2026-08-14). Data as JSON: /api/errors/3d5f189c9740054b. Report an issue: GitHub.