From 0c1ccdfc794208b3aefe4d2712194a8d0e32a2af Mon Sep 17 00:00:00 2001 From: Zichao Lin Date: Mon, 6 Jul 2026 19:54:23 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20=5Femail=5Fpoller?= =?UTF-8?q?=20=E7=BA=BF=E7=A8=8B=E9=9D=99=E9=BB=98=E6=AD=BB=E4=BA=A1?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - IMAP 连接加 30s socket timeout,防止 _send_id_command 等操作永久阻塞 - _email_poller 加顶层 try/except,异常后 sleep 5s 继续循环 - @retry() 加指数退避延迟(1s,2s,4s...),避免重试风暴 --- main.py | 18 +++++++++++------- src/email_client.py | 8 ++++---- src/retry.py | 9 ++++++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/main.py b/main.py index f5a680e..9eb3d90 100644 --- a/main.py +++ b/main.py @@ -45,13 +45,17 @@ def _signal_handler(signum, frame): def _email_poller(cfg): logger.info("邮件轮询线程已启动") while _running: - for acct_idx, mail in poll_accounts(cfg): - if not _running: - return - with _pending_lock: - if mail.uid not in _pending_uids: - _pending_uids.add(mail.uid) - _email_queue.put((acct_idx, mail)) + try: + for acct_idx, mail in poll_accounts(cfg): + if not _running: + return + with _pending_lock: + if mail.uid not in _pending_uids: + _pending_uids.add(mail.uid) + _email_queue.put((acct_idx, mail)) + except Exception as e: + logger.error(f"邮件轮询线程异常: {e}", exc_info=True) + time.sleep(5) if _running: for _ in range(cfg.polling.interval_seconds): if not _running: diff --git a/src/email_client.py b/src/email_client.py index 00caffa..4ec63e5 100644 --- a/src/email_client.py +++ b/src/email_client.py @@ -87,12 +87,12 @@ def _select_mailbox(conn, mailbox: str = "INBOX"): raise RuntimeError(f"无法选择邮箱 {mailbox}: {data}") -def _login_and_prepare(account: EmailAccount): - logger.info("连接 %s:%d", account.imap_server, account.imap_port) +def _login_and_prepare(account: EmailAccount, timeout: int = 30): + logger.info("连接 %s:%d (超时%ds)", account.imap_server, account.imap_port, timeout) if account.use_ssl: - conn = imaplib.IMAP4_SSL(account.imap_server, account.imap_port) + conn = imaplib.IMAP4_SSL(account.imap_server, account.imap_port, timeout=timeout) else: - conn = imaplib.IMAP4(account.imap_server, account.imap_port) + conn = imaplib.IMAP4(account.imap_server, account.imap_port, timeout=timeout) if account.use_starttls: conn.starttls() conn.login(account.username, account.password) diff --git a/src/retry.py b/src/retry.py index 0847b09..71307df 100644 --- a/src/retry.py +++ b/src/retry.py @@ -1,10 +1,11 @@ import logging +import time from functools import wraps logger = logging.getLogger(__name__) -def retry(max_tries=10): +def retry(max_tries=10, base_delay=1, max_delay=60): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): @@ -15,10 +16,12 @@ def retry(max_tries=10): except Exception as e: last_exc = e if attempt < max_tries: + delay = min(base_delay * (2 ** (attempt - 1)), max_delay) logger.warning( - "%s 失败(第%d次),正在重试: %s", - func.__name__, attempt, e, + "%s 失败(第%d次),%d秒后重试: %s", + func.__name__, attempt, delay, e, ) + time.sleep(delay) else: logger.error( "%s 失败 %d 次,放弃: %s",