fix(thread-safety): 已为

templates.py
 中的 Jinja2 环境缓存添加线程安全保护,具体改动如下:

 使用双重检查锁定(Double-Checked Locking)保护缓存访问
读取缓存时加锁,避免多线程同时读取时的数据竞争
创建新环境后再次加锁检查,防止高并发下重复创建相同模板的环境

原代码中 _envs 字典会通过 asyncio.to_thread(self._get_env_sync) 被多个线程同时访问和修改,存在数据竞争风险。现在通过 threading.Lock 确保了线程安全。
This commit is contained in:
SXP-Simon
2026-01-07 00:35:40 +08:00
parent 3840e84b93
commit 022dd77ea9
+16 -5
View File
@@ -5,6 +5,7 @@ HTML模板模块
import asyncio
import os
import threading
from jinja2 import Environment, FileSystemLoader, select_autoescape
@@ -19,16 +20,19 @@ class HTMLTemplates:
self.config_manager = config_manager
# 设置模板根目录
self.base_dir = os.path.join(os.path.dirname(__file__), "templates")
# 缓存不同模板的Jinja2环境
# 缓存不同模板的Jinja2环境(多线程安全)
self._envs = {}
self._env_lock = threading.Lock()
def _get_env_sync(self) -> Environment:
"""获取当前配置的模板环境(同步版本,供 asyncio.to_thread 调用)"""
template_name = self.config_manager.get_report_template()
# 如果环境已缓存且配置未变
if template_name in self._envs:
return self._envs[template_name]
# 如果环境已缓存且配置未变(使用锁保证多线程安全)
with self._env_lock:
env = self._envs.get(template_name)
if env is not None:
return env
template_dir = os.path.join(self.base_dir, template_name)
if not os.path.exists(template_dir):
@@ -41,7 +45,14 @@ class HTMLTemplates:
trim_blocks=True,
lstrip_blocks=True,
)
self._envs[template_name] = env
# 使用双重检查锁定,避免在高并发下重复创建相同 template_name 的 env
with self._env_lock:
existing = self._envs.get(template_name)
if existing is not None:
return existing
self._envs[template_name] = env
return env
async def _get_env_async(self) -> Environment: