{"record":{"id":"2a13bc01707611cd","repo":"Tencent/matrix","slug":"plugin-stop-but-plugin-has-been-already-destroyed","errorCode":null,"errorMessage":"plugin stop, but plugin has been already destroyed","messagePattern":"plugin stop, but plugin has been already destroyed","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":116,"sourceCode":"            throw new RuntimeException(\"plugin start, but plugin has been already destroyed\");\n        }\n\n        if (isPluginStarted()) {\n            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()) {","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/Tencent/matrix/blob/3b8293bd65d47eeea7caf1f32a3a5d4d5eab60e7/matrix/matrix-android/matrix-android-lib/src/main/java/com/tencent/matrix/plugin/Plugin.java#L98-L134","documentation":"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.","triggerScenarios":"Calling plugin.stop() after plugin.destroy(); Matrix destroy path also invokes stop, so a manual stop() after destroy() collides.","commonSituations":"Teardown code that calls both Matrix.with().destroy() and plugin.stop(); application exit handlers running twice; callbacks firing after destroy.","solutions":["Check plugin.isPluginDestroyed() before calling stop().","Let Matrix's destroy path stop plugins; remove manual stop() calls in teardown.","Use a single centralized shutdown routine to avoid duplicate stop/destroy calls."],"exampleFix":"// before\nMatrix.with().destroy();\nplugin.stop();\n// after\nMatrix.with().destroy(); // stops plugins internally","handlingStrategy":"type-guard","validationCode":"if (plugin.isPluginStarted() && !plugin.isPluginDestroyed()) {\n    plugin.stop();\n}","typeGuard":"boolean canStop(Plugin p) { return p.isPluginStarted() && !p.isPluginDestroyed(); }","tryCatchPattern":"try {\n    plugin.stop();\n} catch (RuntimeException e) {\n    Log.w(TAG, \"plugin already destroyed\", e);\n}","preventionTips":["Do not call stop() after destroy()","Centralize shutdown logic so stop/destroy is called once","Check isPluginDestroyed() before lifecycle calls"],"tags":["android","plugins","lifecycle"],"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"}