Tencent/matrix · error · RuntimeException

plugin stop, but plugin is never started

Error message

plugin stop, but plugin is never started

What it means

Thrown from Plugin.stop() when the plugin has never reached PLUGIN_STARTED state. stop() must pair with a prior successful start(); stopping an unstarted plugin would emit an unbalanced onStop callback to the plugin listener, so the guard rejects it. Usually indicates calling stop before start or after a failed start.

Solutions

  1. Guard stop with plugin.isPluginStarted() before calling.
  2. Symmetrically pair every stop() with a guaranteed start() in the same lifecycle path.
  3. Track which plugins were started (e.g. a set of started tags) and only stop those.

Example fix

// before
plugin.stop();
// after
if (plugin.isPluginStarted()) {
    plugin.stop();
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (plugin.isPluginStarted()) {
    plugin.stop();
}

Type guard

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

Try / catch

try {
    plugin.stop();
} catch (RuntimeException e) {
    Log.w(TAG, "plugin was never started", e);
}

Prevention

When it happens

Trigger: Calling plugin.stop() without a prior plugin.start(); conditional start paths where start was skipped but stop runs unconditionally.

Common situations: onDestroy/onLowMemory handlers stopping plugins that were never started; feature flags that skip start but not the paired stop; asymmetric start/stop calls in lifecycle code.

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

Appendix: source

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

            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()) {
            stop();
        }
        if (isPluginDestroyed()) {
            throw new RuntimeException("plugin destroy, but plugin has been already destroyed");

View on GitHub (pinned to 3b8293bd65)