{"record":{"id":"ba92f6ed2c249aed","repo":"Tencent/matrix","slug":"plugin-stop-but-plugin-is-never-started","errorCode":null,"errorMessage":"plugin stop, but plugin is never started","messagePattern":"plugin stop, but plugin is never started","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/plugin/Plugin.java","lineNumber":120,"sourceCode":"            throw new RuntimeException(\"plugin start, but plugin has been already started\");\n        }\n\n        status = PLUGIN_STARTED;\n\n        if (pluginListener == null) {\n            throw new RuntimeException(\"plugin start, plugin listener is null\");\n        }\n        pluginListener.onStart(this);\n    }\n\n    @Override\n    public void stop() {\n        if (isPluginDestroyed()) {\n            throw new RuntimeException(\"plugin stop, but plugin has been already destroyed\");\n        }\n\n        if (!isPluginStarted()) {\n            throw new RuntimeException(\"plugin stop, but plugin is never started\");\n        }\n\n        status = PLUGIN_STOPPED;\n\n        if (pluginListener == null) {\n            throw new RuntimeException(\"plugin stop, plugin listener is null\");\n        }\n        pluginListener.onStop(this);\n    }\n\n    @Override\n    public void destroy() {\n        // stop first\n        if (isPluginStarted()) {\n            stop();\n        }\n        if (isPluginDestroyed()) {\n            throw new RuntimeException(\"plugin destroy, but plugin has been already destroyed\");","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/Tencent/matrix/blob/3b8293bd65d47eeea7caf1f32a3a5d4d5eab60e7/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/plugin/Plugin.java#L102-L138","documentation":"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.","triggerScenarios":"Calling plugin.stop() without a prior plugin.start(); conditional start paths where start was skipped but stop runs unconditionally.","commonSituations":"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.","solutions":["Guard stop with plugin.isPluginStarted() before calling.","Symmetrically pair every stop() with a guaranteed start() in the same lifecycle path.","Track which plugins were started (e.g. a set of started tags) and only stop those."],"exampleFix":"// before\nplugin.stop();\n// after\nif (plugin.isPluginStarted()) {\n    plugin.stop();\n}","handlingStrategy":"type-guard","validationCode":"if (plugin.isPluginStarted()) {\n    plugin.stop();\n}","typeGuard":"boolean canStop(Plugin p) { return p.isPluginStarted(); }","tryCatchPattern":"try {\n    plugin.stop();\n} catch (RuntimeException e) {\n    Log.w(TAG, \"plugin was never started\", e);\n}","preventionTips":["Pair every stop() with a guaranteed start()","Track started plugins before stopping them in teardown","Skip stop() in onDestroy paths for plugins that may never have started"],"tags":["android","plugins","lifecycle","invalid-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"3b8293bd65d47eeea7caf1f32a3a5d4d5eab60e7","analyzedAt":"2026-09-08T08:01:39.722Z","contentChangedAt":"2026-09-08T08:01:39.722Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}