Tencent/matrix · error · RuntimeException

plugin stop, but plugin has been already destroyed

Error message

plugin stop, but plugin has been already destroyed

What it means

Thrown from Plugin.stop() when the plugin's status is PLUGIN_DESTROYED. A destroyed plugin has already released its listener and resources, so stop() cannot meaningfully run; this catches lifecycle sequences that stop a plugin after destroy (e.g. calling stop from a destroy path that has already torn the plugin down) and fails fast instead of notifying a dead listener.

Solutions

  1. Check plugin.isPluginDestroyed() before calling stop().
  2. Let Matrix's destroy path stop plugins; remove manual stop() calls in teardown.
  3. Use a single centralized shutdown routine to avoid duplicate stop/destroy calls.

Example fix

// before
Matrix.with().destroy();
plugin.stop();
// after
Matrix.with().destroy(); // stops plugins internally
Defensive patterns

Strategy: type-guard

Validate before calling

if (plugin.isPluginStarted() && !plugin.isPluginDestroyed()) {
    plugin.stop();
}

Type guard

boolean canStop(Plugin p) { return p.isPluginStarted() && !p.isPluginDestroyed(); }

Try / catch

try {
    plugin.stop();
} catch (RuntimeException e) {
    Log.w(TAG, "plugin already destroyed", e);
}

Prevention

When it happens

Trigger: Calling plugin.stop() after plugin.destroy(); Matrix destroy path also invokes stop, so a manual stop() after destroy() collides.

Common situations: Teardown code that calls both Matrix.with().destroy() and plugin.stop(); application exit handlers running twice; callbacks firing after destroy.

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/2a13bc01707611cd. Report an issue: GitHub.

Appendix: source

Thrown at matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/plugin/Plugin.java:116

            throw new RuntimeException("plugin start, but plugin has been already destroyed");
        }

        if (isPluginStarted()) {
            throw new RuntimeException("plugin start, but plugin has been already started");
        }

        status = PLUGIN_STARTED;

        if (pluginListener == null) {
            throw new RuntimeException("plugin start, plugin listener is null");
        }
        pluginListener.onStart(this);
    }

    @Override
    public void stop() {
        if (isPluginDestroyed()) {
            throw new RuntimeException("plugin stop, but plugin has been already destroyed");
        }

        if (!isPluginStarted()) {
            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()) {

View on GitHub (pinned to 3b8293bd65)