| """ |
| 线程安全的账号池 —— 最空闲优先调度 + RAII Guard。 |
| """ |
|
|
| import logging |
| import threading |
| import time |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| class AccountPool: |
| """线程安全的账号池,实现最空闲优先(most-idle-first)选择策略。""" |
|
|
| def __init__(self): |
| self._lock = threading.Lock() |
| self._accounts = [] |
|
|
| def load(self, accounts: list): |
| """用配置中的账号列表初始化池(startup 时调用)。""" |
| with self._lock: |
| self._accounts = [] |
| for acct in accounts: |
| self._accounts.append({ |
| **acct, |
| "_busy": False, |
| "_last_released": 0.0, |
| }) |
| logger.info(f"[AccountPool] 加载了 {len(self._accounts)} 个账号") |
|
|
| def acquire(self, exclude_ids=None): |
| """最空闲优先选择一个非忙碌账号。 |
| |
| 返回 (account_dict, guard) 或 (None, None)。 |
| guard 在 `with` / `__exit__` 时自动释放账号。 |
| """ |
| if exclude_ids is None: |
| exclude_ids = [] |
|
|
| with self._lock: |
| now = time.time() |
| best = None |
| for acct in self._accounts: |
| if acct["_busy"]: |
| continue |
| acc_id = acct.get("email", "").strip() or acct.get("mobile", "").strip() |
| if acc_id and acc_id in exclude_ids: |
| continue |
| idle_time = now - acct.get("_last_released", 0) |
| if best is None or idle_time > best[0]: |
| best = (idle_time, acct) |
|
|
| if best is None: |
| return None, None |
|
|
| acct = best[1] |
| acct["_busy"] = True |
| guard = AccountGuard(acct, self) |
| logger.info(f"[AccountPool] 分配账号: {self._identifier(acct)}") |
| return acct, guard |
|
|
| def release(self, account): |
| """将账号放回池中(由 AccountGuard 或手动调用)。""" |
| with self._lock: |
| account["_busy"] = False |
| account["_last_released"] = time.time() |
| logger.info(f"[AccountPool] 释放账号: {self._identifier(account)}") |
|
|
| def all_accounts(self): |
| """返回所有账号的浅拷贝(用于健康检查等遍历)。""" |
| with self._lock: |
| return list(self._accounts) |
|
|
| def available_count(self): |
| """返回当前空闲账号数。""" |
| with self._lock: |
| return sum(1 for a in self._accounts if not a["_busy"]) |
|
|
| @staticmethod |
| def _identifier(account): |
| return account.get("email", "").strip() or account.get("mobile", "").strip() |
|
|
|
|
| class AccountGuard: |
| """RAII 账号守卫 —— 离开作用域时自动释放账号。""" |
|
|
| def __init__(self, account, pool: AccountPool): |
| self.account = account |
| self._pool = pool |
|
|
| def __enter__(self): |
| return self.account |
|
|
| def __exit__(self, exc_type, exc_val, exc_tb): |
| self._pool.release(self.account) |
| return False |
|
|
|
|
| |
| account_pool = AccountPool() |
|
|