fix: 修复 _email_poller 线程静默死亡问题
- IMAP 连接加 30s socket timeout,防止 _send_id_command 等操作永久阻塞 - _email_poller 加顶层 try/except,异常后 sleep 5s 继续循环 - @retry() 加指数退避延迟(1s,2s,4s...),避免重试风暴
This commit is contained in:
18
main.py
18
main.py
@@ -45,13 +45,17 @@ def _signal_handler(signum, frame):
|
|||||||
def _email_poller(cfg):
|
def _email_poller(cfg):
|
||||||
logger.info("邮件轮询线程已启动")
|
logger.info("邮件轮询线程已启动")
|
||||||
while _running:
|
while _running:
|
||||||
for acct_idx, mail in poll_accounts(cfg):
|
try:
|
||||||
if not _running:
|
for acct_idx, mail in poll_accounts(cfg):
|
||||||
return
|
if not _running:
|
||||||
with _pending_lock:
|
return
|
||||||
if mail.uid not in _pending_uids:
|
with _pending_lock:
|
||||||
_pending_uids.add(mail.uid)
|
if mail.uid not in _pending_uids:
|
||||||
_email_queue.put((acct_idx, mail))
|
_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:
|
if _running:
|
||||||
for _ in range(cfg.polling.interval_seconds):
|
for _ in range(cfg.polling.interval_seconds):
|
||||||
if not _running:
|
if not _running:
|
||||||
|
|||||||
@@ -87,12 +87,12 @@ def _select_mailbox(conn, mailbox: str = "INBOX"):
|
|||||||
raise RuntimeError(f"无法选择邮箱 {mailbox}: {data}")
|
raise RuntimeError(f"无法选择邮箱 {mailbox}: {data}")
|
||||||
|
|
||||||
|
|
||||||
def _login_and_prepare(account: EmailAccount):
|
def _login_and_prepare(account: EmailAccount, timeout: int = 30):
|
||||||
logger.info("连接 %s:%d", account.imap_server, account.imap_port)
|
logger.info("连接 %s:%d (超时%ds)", account.imap_server, account.imap_port, timeout)
|
||||||
if account.use_ssl:
|
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:
|
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:
|
if account.use_starttls:
|
||||||
conn.starttls()
|
conn.starttls()
|
||||||
conn.login(account.username, account.password)
|
conn.login(account.username, account.password)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def retry(max_tries=10):
|
def retry(max_tries=10, base_delay=1, max_delay=60):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
@@ -15,10 +16,12 @@ def retry(max_tries=10):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_exc = e
|
last_exc = e
|
||||||
if attempt < max_tries:
|
if attempt < max_tries:
|
||||||
|
delay = min(base_delay * (2 ** (attempt - 1)), max_delay)
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"%s 失败(第%d次),正在重试: %s",
|
"%s 失败(第%d次),%d秒后重试: %s",
|
||||||
func.__name__, attempt, e,
|
func.__name__, attempt, delay, e,
|
||||||
)
|
)
|
||||||
|
time.sleep(delay)
|
||||||
else:
|
else:
|
||||||
logger.error(
|
logger.error(
|
||||||
"%s 失败 %d 次,放弃: %s",
|
"%s 失败 %d 次,放弃: %s",
|
||||||
|
|||||||
Reference in New Issue
Block a user