TNOT commited on
Commit
e5af422
·
1 Parent(s): 97817a2

fix: 并发计数滞留

Browse files
Files changed (2) hide show
  1. src/gui_cloud.py +128 -4
  2. 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
- annotations = getattr(func, '__annotations__', {})
79
- return_type = annotations.get('return', None)
 
 
 
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
- - 首次运行可能需要较长时间初始化