diff --git a/plugins/platforms/feishu/adapter.py b/plugins/platforms/feishu/adapter.py index 14ab109162..041ff06c41 100644 --- a/plugins/platforms/feishu/adapter.py +++ b/plugins/platforms/feishu/adapter.py @@ -4768,10 +4768,18 @@ class FeishuAdapter(BasePlatformAdapter): # same way #83906 did for the other gateway persist paths. The lock # keeps flushes in mutation order (the snapshot inside the worker is # taken under _dedup_lock, but the write itself is not). - async with self._dedup_persist_lock: + async with self._dedup_persist_lock_or_create(): await asyncio.to_thread(self._persist_seen_message_ids) return False + def _dedup_persist_lock_or_create(self) -> asyncio.Lock: + # Tests build bare adapters via object.__new__ and install dedup state + # by hand; create the lock lazily so those fixtures keep working. + lock = getattr(self, "_dedup_persist_lock", None) + if lock is None: + lock = self._dedup_persist_lock = asyncio.Lock() + return lock + # ========================================================================= # Outbound payload construction and send pipeline # ========================================================================= diff --git a/tests/gateway/feishu_helpers.py b/tests/gateway/feishu_helpers.py index 97771daaa3..f9b7822a21 100644 --- a/tests/gateway/feishu_helpers.py +++ b/tests/gateway/feishu_helpers.py @@ -2,6 +2,7 @@ from __future__ import annotations +import asyncio import threading from types import SimpleNamespace from typing import Any, Optional @@ -59,6 +60,7 @@ def install_dedup_state(adapter: Any, seen: Optional[dict] = None) -> None: adapter._seen_message_order = list((seen or {}).keys()) adapter._dedup_cache_size = 100 adapter._dedup_lock = threading.Lock() + adapter._dedup_persist_lock = asyncio.Lock() adapter._dedup_state_path = None adapter._persist_seen_message_ids = lambda: None