From 670e98d2793a7c861204468aa13668ddc70fb34f Mon Sep 17 00:00:00 2001 From: cfdaily Date: Tue, 26 May 2026 08:20:24 +0800 Subject: [PATCH] auto-sync: 2026-05-26 08:20:24 --- src/daemon/counter.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/daemon/counter.py b/src/daemon/counter.py index 4ecca6d..21238a2 100644 --- a/src/daemon/counter.py +++ b/src/daemon/counter.py @@ -1,28 +1,34 @@ -"""ActiveAgentCounter — 并发控制 +"""ActiveAgentCounter — 并发控制 + 冷却机制 全局上限 + per-agent 串行,asyncio Semaphore 实现。 延迟创建 Semaphore(兼容 Python 3.9 无 event loop 时的构造)。 +v2.7.2:新增 cooldown 机制(429/API 错误后冷却期)。 """ from __future__ import annotations import asyncio import logging +import time from typing import Dict, Optional logger = logging.getLogger("moziplus-v2.counter") class ActiveAgentCounter: - """异步并发计数器""" + """异步并发计数器 + 冷却机制""" - def __init__(self, max_global: int = 5, max_per_agent: int = 1): + def __init__(self, max_global: int = 5, max_per_agent: int = 1, + default_cooldown_seconds: float = 120.0): self._max_global = max_global self._max_per_agent = max_per_agent + self._default_cooldown_seconds = default_cooldown_seconds self._global_sem: Optional[asyncio.Semaphore] = None self._per_agent: Dict[str, asyncio.Semaphore] = {} self._active: Dict[str, int] = {} self._global_active: int = 0 + # v2.7.2:冷却机制 + self._cooldown_until: Dict[str, float] = {} # agent_id → 冷却到期时间戳 def _get_global_sem(self) -> asyncio.Semaphore: if self._global_sem is None: @@ -34,7 +40,25 @@ class ActiveAgentCounter: self._per_agent[agent_id] = asyncio.Semaphore(self._max_per_agent) return self._per_agent[agent_id] + def is_cooling_down(self, agent_id: str) -> bool: + """检查 agent 是否在冷却期""" + until = self._cooldown_until.get(agent_id) + if until and time.time() < until: + return True + # 冷却期已过,清理 + self._cooldown_until.pop(agent_id, None) + return False + + def set_cooldown(self, agent_id: str, seconds: Optional[float] = None) -> None: + """设置冷却期(默认 120 秒)""" + cd = seconds if seconds is not None else self._default_cooldown_seconds + self._cooldown_until[agent_id] = time.time() + cd + logger.info("Cooldown set for %s: %.0fs (until %.0f)", + agent_id, cd, self._cooldown_until[agent_id]) + async def can_acquire(self, agent_id: str) -> bool: + if self.is_cooling_down(agent_id): + return False if self._global_active >= self._max_global: return False active = self._active.get(agent_id, 0)