Spaces:
Running
Running
fix: 并发计数滞留
Browse files- src/gui_cloud.py +128 -4
- tools/README.md +0 -34
src/gui_cloud.py
CHANGED
|
@@ -60,10 +60,18 @@ def safe_gradio_handler(func):
|
|
| 60 |
Gradio 处理函数的安全包装器
|
| 61 |
|
| 62 |
捕获所有异常并返回友好的错误信息,避免 Gradio 显示默认的"错误"状态
|
|
|
|
| 63 |
"""
|
| 64 |
import functools
|
| 65 |
import traceback
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
@functools.wraps(func)
|
| 68 |
def wrapper(*args, **kwargs):
|
| 69 |
try:
|
|
@@ -73,11 +81,15 @@ def safe_gradio_handler(func):
|
|
| 73 |
error_trace = traceback.format_exc()
|
| 74 |
logger.error(f"处理函数 {func.__name__} 发生异常:\n{error_trace}")
|
| 75 |
|
| 76 |
-
#
|
| 77 |
-
#
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
| 80 |
|
|
|
|
| 81 |
error_msg = f"❌ 系统错误: {str(e)}"
|
| 82 |
error_detail = f"异常类型: {type(e).__name__}\n详情: {str(e)}"
|
| 83 |
|
|
@@ -131,6 +143,110 @@ def create_temp_workspace() -> str:
|
|
| 131 |
return workspace
|
| 132 |
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
def cleanup_workspace(workspace: str):
|
| 135 |
"""清理工作空间"""
|
| 136 |
if workspace and os.path.exists(workspace):
|
|
@@ -1708,6 +1824,14 @@ def create_cloud_ui():
|
|
| 1708 |
|
| 1709 |
def main():
|
| 1710 |
"""云端入口"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1711 |
app = create_cloud_ui()
|
| 1712 |
# 启用队列,魔搭CPU按需分配,无需设置并发上限
|
| 1713 |
app.queue()
|
|
|
|
| 60 |
Gradio 处理函数的安全包装器
|
| 61 |
|
| 62 |
捕获所有异常并返回友好的错误信息,避免 Gradio 显示默认的"错误"状态
|
| 63 |
+
同时确保异常时释放并发计数,防止计数滞留
|
| 64 |
"""
|
| 65 |
import functools
|
| 66 |
import traceback
|
| 67 |
|
| 68 |
+
# 需要管理并发计数的函数列表
|
| 69 |
+
CONCURRENCY_MANAGED_FUNCS = {
|
| 70 |
+
'process_make_voicebank',
|
| 71 |
+
'process_export_voicebank',
|
| 72 |
+
'process_mfa_realign'
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
@functools.wraps(func)
|
| 76 |
def wrapper(*args, **kwargs):
|
| 77 |
try:
|
|
|
|
| 81 |
error_trace = traceback.format_exc()
|
| 82 |
logger.error(f"处理函数 {func.__name__} 发生异常:\n{error_trace}")
|
| 83 |
|
| 84 |
+
# 如果是并发管理的函数,确保释放并发计数
|
| 85 |
+
# 注意:函数内部可能已经调用了 decrement_concurrency(),
|
| 86 |
+
# 但如果异常发生在 increment 之后、decrement 之前,这里需要补救
|
| 87 |
+
# decrement_concurrency() 内部有 max(0, ...) 保护,不会变成负数
|
| 88 |
+
if func.__name__ in CONCURRENCY_MANAGED_FUNCS:
|
| 89 |
+
decrement_concurrency()
|
| 90 |
+
logger.info(f"异常处理:已释放 {func.__name__} 的并发计数")
|
| 91 |
|
| 92 |
+
# 根据函数返回值数量返回错误信息
|
| 93 |
error_msg = f"❌ 系统错误: {str(e)}"
|
| 94 |
error_detail = f"异常类型: {type(e).__name__}\n详情: {str(e)}"
|
| 95 |
|
|
|
|
| 143 |
return workspace
|
| 144 |
|
| 145 |
|
| 146 |
+
def cleanup_gradio_cache(max_age_hours: float = 1.0):
|
| 147 |
+
"""
|
| 148 |
+
清理 Gradio 临时文件缓存
|
| 149 |
+
|
| 150 |
+
参数:
|
| 151 |
+
max_age_hours: 文件最大保留时间(小时),超过此时间的文件将被删除
|
| 152 |
+
"""
|
| 153 |
+
import time
|
| 154 |
+
|
| 155 |
+
gradio_tmp_dir = os.path.join(tempfile.gettempdir(), "gradio")
|
| 156 |
+
if not os.path.exists(gradio_tmp_dir):
|
| 157 |
+
return
|
| 158 |
+
|
| 159 |
+
current_time = time.time()
|
| 160 |
+
max_age_seconds = max_age_hours * 3600
|
| 161 |
+
cleaned_count = 0
|
| 162 |
+
cleaned_size = 0
|
| 163 |
+
|
| 164 |
+
try:
|
| 165 |
+
for root, dirs, files in os.walk(gradio_tmp_dir, topdown=False):
|
| 166 |
+
for name in files:
|
| 167 |
+
file_path = os.path.join(root, name)
|
| 168 |
+
try:
|
| 169 |
+
file_age = current_time - os.path.getmtime(file_path)
|
| 170 |
+
if file_age > max_age_seconds:
|
| 171 |
+
file_size = os.path.getsize(file_path)
|
| 172 |
+
os.remove(file_path)
|
| 173 |
+
cleaned_count += 1
|
| 174 |
+
cleaned_size += file_size
|
| 175 |
+
except Exception:
|
| 176 |
+
pass
|
| 177 |
+
|
| 178 |
+
# 删除空目录
|
| 179 |
+
for name in dirs:
|
| 180 |
+
dir_path = os.path.join(root, name)
|
| 181 |
+
try:
|
| 182 |
+
if not os.listdir(dir_path):
|
| 183 |
+
os.rmdir(dir_path)
|
| 184 |
+
except Exception:
|
| 185 |
+
pass
|
| 186 |
+
|
| 187 |
+
if cleaned_count > 0:
|
| 188 |
+
size_mb = cleaned_size / (1024 * 1024)
|
| 189 |
+
logger.info(f"Gradio 缓存清理: 删除 {cleaned_count} 个文件, 释放 {size_mb:.1f} MB")
|
| 190 |
+
except Exception as e:
|
| 191 |
+
logger.warning(f"Gradio 缓存清理失败: {e}")
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def cleanup_old_jinriki_workspaces(max_age_hours: float = 2.0):
|
| 195 |
+
"""
|
| 196 |
+
清理旧的 jinriki 工作空间
|
| 197 |
+
|
| 198 |
+
参数:
|
| 199 |
+
max_age_hours: 工作空间最大保留时间(小时)
|
| 200 |
+
"""
|
| 201 |
+
import time
|
| 202 |
+
|
| 203 |
+
current_time = time.time()
|
| 204 |
+
max_age_seconds = max_age_hours * 3600
|
| 205 |
+
cleaned_count = 0
|
| 206 |
+
|
| 207 |
+
try:
|
| 208 |
+
for item in os.listdir(CloudConfig.TEMP_BASE):
|
| 209 |
+
if item.startswith("jinriki_"):
|
| 210 |
+
workspace_path = os.path.join(CloudConfig.TEMP_BASE, item)
|
| 211 |
+
if os.path.isdir(workspace_path):
|
| 212 |
+
try:
|
| 213 |
+
dir_age = current_time - os.path.getmtime(workspace_path)
|
| 214 |
+
if dir_age > max_age_seconds:
|
| 215 |
+
shutil.rmtree(workspace_path)
|
| 216 |
+
cleaned_count += 1
|
| 217 |
+
except Exception:
|
| 218 |
+
pass
|
| 219 |
+
|
| 220 |
+
if cleaned_count > 0:
|
| 221 |
+
logger.info(f"工作空间清理: 删除 {cleaned_count} 个旧工作空间")
|
| 222 |
+
except Exception as e:
|
| 223 |
+
logger.warning(f"工作空间清理失败: {e}")
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def start_periodic_cleanup(interval_minutes: int = 30):
|
| 227 |
+
"""
|
| 228 |
+
启动定期清理任务
|
| 229 |
+
|
| 230 |
+
参数:
|
| 231 |
+
interval_minutes: 清理间隔(分钟)
|
| 232 |
+
"""
|
| 233 |
+
import time
|
| 234 |
+
|
| 235 |
+
def cleanup_task():
|
| 236 |
+
while True:
|
| 237 |
+
try:
|
| 238 |
+
time.sleep(interval_minutes * 60)
|
| 239 |
+
logger.info("执行定期清理...")
|
| 240 |
+
cleanup_gradio_cache(max_age_hours=1.0)
|
| 241 |
+
cleanup_old_jinriki_workspaces(max_age_hours=2.0)
|
| 242 |
+
except Exception as e:
|
| 243 |
+
logger.error(f"定期清理任务异常: {e}")
|
| 244 |
+
|
| 245 |
+
cleanup_thread = threading.Thread(target=cleanup_task, daemon=True)
|
| 246 |
+
cleanup_thread.start()
|
| 247 |
+
logger.info(f"定期清理任务已启动,间隔 {interval_minutes} 分钟")
|
| 248 |
+
|
| 249 |
+
|
| 250 |
def cleanup_workspace(workspace: str):
|
| 251 |
"""清理工作空间"""
|
| 252 |
if workspace and os.path.exists(workspace):
|
|
|
|
| 1824 |
|
| 1825 |
def main():
|
| 1826 |
"""云端入口"""
|
| 1827 |
+
# 启动时执行一次清理
|
| 1828 |
+
logger.info("启动时执行缓存清理...")
|
| 1829 |
+
cleanup_gradio_cache(max_age_hours=0.5) # 清理超过30分钟的缓存
|
| 1830 |
+
cleanup_old_jinriki_workspaces(max_age_hours=1.0) # 清理超过1小时的工作空间
|
| 1831 |
+
|
| 1832 |
+
# 启动定期清理任务
|
| 1833 |
+
start_periodic_cleanup(interval_minutes=30)
|
| 1834 |
+
|
| 1835 |
app = create_cloud_ui()
|
| 1836 |
# 启用队列,魔搭CPU按需分配,无需设置并发上限
|
| 1837 |
app.queue()
|
tools/README.md
DELETED
|
@@ -1,34 +0,0 @@
|
|
| 1 |
-
# MFA 引擎下载说明
|
| 2 |
-
|
| 3 |
-
本目录用于存放 Montreal Forced Aligner (MFA) 引擎。
|
| 4 |
-
|
| 5 |
-
## 下载地址
|
| 6 |
-
|
| 7 |
-
| 平台 | 链接 |
|
| 8 |
-
|------|------|
|
| 9 |
-
| 百度网盘 | [待补充] |
|
| 10 |
-
| Google Drive | [待补充] |
|
| 11 |
-
| 123云盘 | [待补充] |
|
| 12 |
-
|
| 13 |
-
## 安装步骤
|
| 14 |
-
|
| 15 |
-
1. 下载 `mfa_engine_win64.zip`
|
| 16 |
-
2. 解压到本目录,确保目录结构如下:
|
| 17 |
-
|
| 18 |
-
```
|
| 19 |
-
tools/
|
| 20 |
-
└── mfa_engine/
|
| 21 |
-
├── python.exe
|
| 22 |
-
├── Scripts/
|
| 23 |
-
│ └── mfa.exe
|
| 24 |
-
├── Lib/
|
| 25 |
-
└── ...
|
| 26 |
-
```
|
| 27 |
-
|
| 28 |
-
3. 验证安装:运行程序后在「模型下载」页面检查 MFA 状态
|
| 29 |
-
|
| 30 |
-
## 注意事项
|
| 31 |
-
|
| 32 |
-
- MFA 引擎仅支持 Windows 64位系统
|
| 33 |
-
- 解压后约占用 2GB 磁盘空间
|
| 34 |
-
- 首次运行可能需要较长时间初始化
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|