Tencent/matrix · error · RuntimeException
plugin destroy, but plugin has been already destroyed
Error message
plugin destroy, but plugin has been already destroyed
What it means
Plugin.destroy() throws this when the plugin's status is already PLUGIN_DESTROYED. Destroy is meant to be a terminal, one-time lifecycle operation; calling it twice indicates a lifecycle management bug, so Matrix throws instead of silently double-destroying.
Solutions
- Check isPluginDestroyed() before calling destroy()
- Ensure only one code path owns plugin teardown (e.g. Matrix.onDestroy only)
- Track destroyed plugins in a Set and skip already-destroyed instances
- Wrap destroy() in try-catch for RuntimeException during shutdown where failure should not crash the app
Example fix
// before
plugin.destroy();
plugin.destroy(); // throws
// after
if (!plugin.isPluginDestroyed()) {
plugin.destroy();
} Defensive patterns
Strategy: validation
Validate before calling
if (plugin.isPluginDestroyed()) {
return; // already torn down
}
plugin.destroy(); Type guard
if (plugin != null && !plugin.isPluginDestroyed()) { plugin.destroy(); } Try / catch
try {
plugin.destroy();
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("already destroyed")) {
Log.d(TAG, "plugin already destroyed, ignoring");
} else { throw e; }
} Prevention
- Centralize plugin teardown in a single owner (Matrix.onDestroy)
- Track destroyed instances in a Set before destroying
- Avoid calling destroy from multiple shutdown hooks
When it happens
Trigger: Calling plugin.destroy() a second time on the same instance, e.g. both Matrix.onDestroy()/destroy() and application-level cleanup code destroy the same plugin, or destroy() is invoked after a previous destroy.
Common situations: Double teardown paths (Activity.onDestroy plus Application termination hook); retry logic calling destroy again after a failure; holding stale plugin references in a registry and destroying them in multiple shutdown handlers.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- plugin stop, plugin listener is null
- plugin destroy, plugin listener is null
- plugin start, but plugin has been already destroyed
- plugin start, but plugin has been already started
- plugin start, plugin listener is null
AI-assisted analysis of Tencent/matrix@3b8293bd65 (2026-09-08).
Data as JSON: /api/errors/409923b3555397a8.
Report an issue: GitHub.
Appendix: source
Thrown at matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/plugin/Plugin.java:138
throw new RuntimeException("plugin stop, but plugin is never started");
}
status = PLUGIN_STOPPED;
if (pluginListener == null) {
throw new RuntimeException("plugin stop, plugin listener is null");
}
pluginListener.onStop(this);
}
@Override
public void destroy() {
// stop first
if (isPluginStarted()) {
stop();
}
if (isPluginDestroyed()) {
throw new RuntimeException("plugin destroy, but plugin has been already destroyed");
}
status = PLUGIN_DESTROYED;
if (pluginListener == null) {
throw new RuntimeException("plugin destroy, plugin listener is null");
}
pluginListener.onDestroy(this);
}
@Override
public String getTag() {
return getClass().getName();
}
@Override
public void onForeground(boolean isForeground) {
}View on GitHub (pinned to 3b8293bd65)