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

  1. Check isPluginDestroyed() before calling destroy()
  2. Ensure only one code path owns plugin teardown (e.g. Matrix.onDestroy only)
  3. Track destroyed plugins in a Set and skip already-destroyed instances
  4. 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

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


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)