fix: unregister plugin web APIs on plugin unload - #9915
Conversation
Context.registered_web_apis kept handlers of uninstalled, disabled and reloaded plugins forever, leaking plugin instances via bound method references and leaving ghost routes callable after unload. - Add Context.unregister_web_apis() to drop web APIs owned by a plugin module path prefix (function module and owner class module). - Call it from PluginManager._unbind_plugin() and _cleanup_plugin_state(). - Clear all registered web APIs on reload(all), matching the existing registry clear semantics.
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="astrbot/core/star/star_manager.py" line_range="1941-1942" />
<code_context>
)
+ # Unregister web APIs registered by this plugin to avoid ghost
+ # routes and memory leaks after unloading.
+ removed_web_apis = Context.unregister_web_apis(module_prefix)
+ if removed_web_apis:
+ logger.info(
</code_context>
<issue_to_address>
**issue (broader_impact):** Disabling a plugin through `turn_off_plugin()` does not call `_unbind_plugin()`, so this new cleanup path is never reached and the plugin's registered web APIs remain routable after the plugin is disabled. Requests continue invoking the disabled plugin's handlers instead of returning 404.
**Triggers:** When a loaded plugin is disabled without being uninstalled or reloaded.
**Suggested fix:** Invoke the web API cleanup from the disable path, or route disabling through `_unbind_plugin()` while preserving the plugin metadata needed for re-enabling.
</issue_to_address>Sourcery assessment
Approval pending. 1 finding to address first.
Blocking findings: astrbot/core/star/star_manager.py:1942
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| # routes and memory leaks after unloading. | ||
| removed_web_apis = Context.unregister_web_apis(module_prefix) |
There was a problem hiding this comment.
issue (broader_impact): Disabling a plugin through turn_off_plugin() does not call _unbind_plugin(), so this new cleanup path is never reached and the plugin's registered web APIs remain routable after the plugin is disabled. Requests continue invoking the disabled plugin's handlers instead of returning 404.
Triggers: When a loaded plugin is disabled without being uninstalled or reloaded.
Suggested fix: Invoke the web API cleanup from the disable path, or route disabling through _unbind_plugin() while preserving the plugin metadata needed for re-enabling.
|
Regarding the Sourcery finding about
|
Motivation / 动机
Context.registered_web_apisnever released entries registered by plugins. After a plugin was uninstalled, disabled or reloaded, its registered web API handlers stayed in the list forever:/api/v1/plugins/extensions/<uninstalled-plugin>/...still match the stale handler and execute it, instead of returning 404.register_web_apionly replaces entries with the exact same route and methods; routes removed or changed in a new plugin version stay forever.PluginManager._unbind_plugin()already cleans up event handlers, LLM tools, platform adapters andsys.modules— this PR closes the gap by also releasing registered web APIs.Modifications / 改动点
astrbot/core/star/context.py: addContext.unregister_web_apis(module_path)classmethod that removes all web APIs whose handler is owned by the given plugin module path prefix (checked via the handler function module and, for bound methods, the owner class module).astrbot/core/star/star_manager.py:_unbind_plugin(): unregister web APIs owned by the plugin (shared cleanup path for uninstall, disable and single-plugin reload)._cleanup_plugin_state(): unregister web APIs for plugins that failed to load.reload(all): clear all registered web APIs alongside the existing registry clears, since every plugin is re-loaded afterwards.tests/test_plugin_manager.py: unit tests for ownership-prefixed removal (other plugins and core routes are preserved) and an integration test for_unbind_plugin().This is NOT a breaking change. / 这不是一个破坏性变更。
Screenshots or Test Results / 运行截图或测试结果
Verification steps:
context.register_web_api(...).Removed N registered web API(s) from plugin ....Checklist / 检查清单
😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。
🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in
requirements.txtandpyproject.toml./ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt和pyproject.toml文件相应位置。😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。
Summary by Sourcery
Ensure plugin web APIs are fully removed whenever plugin state is cleaned up so unloaded plugins no longer retain memory or serve ghost routes.
Bug Fixes:
Enhancements:
Tests: