Skip to content

fix: clean up duplicate platform adapters on reload#6102

Open
stablegenius49 wants to merge 1 commit intoAstrBotDevs:masterfrom
stablegenius49:fix/6100-platform-reload-cleanup
Open

fix: clean up duplicate platform adapters on reload#6102
stablegenius49 wants to merge 1 commit intoAstrBotDevs:masterfrom
stablegenius49:fix/6100-platform-reload-cleanup

Conversation

@stablegenius49
Copy link
Contributor

Summary

  • terminate an existing platform instance before reloading the same platform ID
  • make terminate_platform() clean up orphaned instances that still share the same config ID
  • add regression tests for duplicate reloads and orphan cleanup

Testing

  • /opt/homebrew/opt/python@3.11/bin/python3.11 -m ruff format astrbot/core/platform/manager.py tests/unit/test_platform_manager.py
  • /opt/homebrew/opt/python@3.11/bin/python3.11 -m ruff check astrbot/core/platform/manager.py tests/unit/test_platform_manager.py
  • /opt/homebrew/opt/python@3.11/bin/python3.11 -m pytest -q tests/unit/test_platform_manager.py

Fixes #6100.

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 12, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refines the platform management system to prevent issues arising from duplicate or orphaned platform instances. It ensures that when a platform is reloaded, any existing instance with the same ID is properly terminated first. Additionally, the termination process has been improved to proactively identify and shut down any untracked, "orphaned" instances that might still be running for a given platform ID, leading to a more robust and predictable system state.

Highlights

  • Duplicate Platform Handling: Implemented logic to terminate an existing platform instance before reloading a new one with the same ID.
  • Orphaned Instance Cleanup: Enhanced terminate_platform to identify and clean up orphaned platform instances sharing the same configuration ID.
  • Regression Tests: Added new regression tests to validate the correct behavior of duplicate reloads and orphaned instance cleanup.
Changelog
  • astrbot/core/platform/manager.py
    • Modified load_platform to check for existing platform instances with the same ID and terminate them before loading a new one.
    • Refactored terminate_platform to first retrieve the tracked instance, then iterate through all active platform instances to find and terminate any that match the given platform_id, including orphaned ones.
  • tests/unit/test_platform_manager.py
    • Created a new test file to house unit tests for the PlatformManager.
    • Defined DummyAstrBotConfig and DummyPlatform classes to mock dependencies and simulate platform behavior for testing.
    • Added test_load_platform_replaces_existing_same_id to confirm that loading a platform with an existing ID correctly terminates the old instance.
    • Included test_terminate_platform_cleans_orphaned_instances to verify that terminate_platform successfully cleans up both tracked and untracked (orphaned) instances.
Activity
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added the area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. label Mar 12, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses the issue of duplicate platform adapters on reload. The changes in load_platform to terminate existing instances and the improved cleanup logic in terminate_platform are well-implemented. The addition of regression tests is also a great improvement to ensure this behavior is maintained. I have one suggestion to refactor the terminate_platform method for better performance and clarity.

Comment on lines +267 to 290
tracked_inst: Platform | None = None
info = self._inst_map.pop(platform_id, None)
if info:
tracked_inst = info["inst"]

# client_id = self._inst_map.pop(platform_id, None)
info = self._inst_map.pop(platform_id)
client_id = info["client_id"]
inst: Platform = info["inst"]
try:
self.platform_insts.remove(
next(
inst
for inst in self.platform_insts
if inst.client_self_id == client_id
),
)
except Exception:
logger.warning(f"可能未完全移除 {platform_id} 平台适配器")
insts_to_terminate: list[Platform] = []
if tracked_inst is not None:
insts_to_terminate.append(tracked_inst)

for inst in list(self.platform_insts):
if inst in insts_to_terminate:
continue
if getattr(inst, "config", {}).get("id") == platform_id:
insts_to_terminate.append(inst)

if not insts_to_terminate:
return

logger.info(f"正在尝试终止 {platform_id} 平台适配器 ...")

for inst in insts_to_terminate:
while inst in self.platform_insts:
self.platform_insts.remove(inst)
await self._terminate_inst_and_tasks(inst)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The terminate_platform method can be refactored for better performance and readability.

  1. Efficient Instance Collection: Using a set comprehension to collect all relevant instances is more efficient than the current loop with list membership checks.
  2. Efficient Instance Removal: A list comprehension is the idiomatic and performant way to filter self.platform_insts, rather than using remove() in a loop.
  3. Concurrent Termination: Terminating instances concurrently with asyncio.gather will improve performance, as these are independent I/O-bound operations.

Applying these changes will make the function more robust and faster.

        info = self._inst_map.pop(platform_id, None)
        tracked_inst = info["inst"] if info else None

        insts_to_terminate = {
            inst
            for inst in self.platform_insts
            if getattr(inst, "config", {}).get("id") == platform_id
        }
        if tracked_inst:
            insts_to_terminate.add(tracked_inst)

        if not insts_to_terminate:
            return

        logger.info(f"正在尝试终止 {platform_id} 平台适配器 ...")

        self.platform_insts = [
            inst for inst in self.platform_insts if inst not in insts_to_terminate
        ]
        await asyncio.gather(
            *(self._terminate_inst_and_tasks(inst) for inst in insts_to_terminate)
        )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] UI 删除qq官方机器人(websocket)后,仍能调用

2 participants