Upload route returns cache path before cross-filesystem move completes

当系统临时目录与 Gradio 上传/缓存目录位于不同文件系统或不同 Windows 卷(例如 TEMP 在 C:、上传目录在 D:)时, /gradio_api/upload 会先返回 content-addressed 缓存路径,再在后台执行跨文件系统拷贝,导致客户端在该路径尚未就绪时读取就触发

快速结论:当系统临时目录与 Gradio 上传/缓存目录位于不同文件系统或不同 Windows 卷(例如 TEMP 在 C:、上传目录在 D:)时,/gradio_api/upload 会先返回 content-addressed 缓存路径,再在后台执行跨文件系统拷贝,导致客户端在该路径尚未就绪时读取就触发错误。优先排查临时目录与上传目录是否跨卷,以及是否使用了最新版本。

适用环境:Issue 中确认的环境包括 Gradio 5.50.0 / 6.20.0、Python 3.12.13 / 3.13.5、Starlette 0.52.1 / 0.52.1+、FaceFusion 以及 Windows 跨卷场景(TEMP/TMP 在 C:,上传目录在 D:);候选修复基于 Gradio main 提交 01a06729c94916c2859886dca2971b4857c09741。其他 Python、CUDA、显卡信息 Issue 未提供。

最快修复方案:暂无确认的一步修复方案。Issue 的修复补丁尚未合并进已发布版本,官方 release 中无法直接升级消除;可优先尝试将 Gradio 上传目录与系统临时目录放在同一文件系统/卷,以规避跨文件系统回退路径。若使用主分支或候选补丁,可参考 SAUMustapure:fix/upload-atomic 对比

注意事项:将临时目录与上传目录置于同一卷只是规避手段,并非 Issue 中已验证的代码级修复;该改动的正式补丁尚未合入。采用候选补丁属于未发布变更,跨文件系统上传会同步拷贝,会增加延迟(Issue 中对比显示上传响应时间从 0.971 s 增至 0.985 s),但可避免部分拷贝或文件被占用时的读取失败。

问题场景

用户在 Windows 上运行 Gradio 应用(Issue 中为 FaceFusion UI),将系统临时目录(TEMP/TMP)放在 C:,把 Gradio 上传目录放在 D:。当通过 /gradio_api/upload 上传较大文件(例如 507 MB MP4)后,客户端立即对该缓存路径发起 Range 请求(浏览器播放/预览场景常见),可能读取到尚未完成跨文件系统移动的文件,导致 FileResponse 打开文件失败。

报错原文

Upload route returns cache path before cross-filesystem move completes
starlette.responses.FileResponse._handle_single_range -> anyio.open_file(path, "rb") -> PermissionError
Immediate 1 KiB Range GET: HTTP 403 (baseline) / HTTP 206 (candidate fix)

原因分析

最可能的原因是上传路由的发布时序问题:主上传路由调用 upload_fn(..., force_move=False)。当 os.rename() 跨文件系统失败时,upload_fn() 返回源/目标列表,路由把 move_uploaded_files_to_cache() 注册为 Starlette 后台任务,但响应已立即暴露目标缓存路径。客户端可能在目标文件缺失、只被部分拷贝、或仍被拷贝进程占用时请求该路径。于是出现两种表现:早期读通常得到 HTTP 403,时序更巧时(如大文件 Range 请求)会命中 anyio.open_filePermissionError,在 FaceFusion 场景中甚至导致整个服务进程退出。Issue 指出该提前发布是确定性的,而具体崩溃报告是时序相关的。

环境排查

  • 确认 Gradio 版本:Issue 在 5.50.0 观察到崩溃,在 6.20.0 仍可确定性地复现返回早于发布的问题。
  • 确认 Python 版本:Issue 涉及 3.12.13 与 3.13.5。
  • 确认 Starlette 版本:Issue 涉及 0.52.1。
  • 确认 Windows 盘符布局:TEMP/TMP 是否与 Gradio 上传目录(GRADIO_TEMP_DIR)位于不同卷,这是触发跨文件系统回退的关键。
  • 确认上传文件大小与客户端是否在响应后立即发起 Range 请求。
  • 确认是否使用了 force_move=False 的调用路径,以及静态服务器路径 gradio/static_server.py 是否同样丢弃了待处理移动列表。

解决步骤

  1. 先确认问题是否由跨卷引发:在 Windows 上检查 TEMPTMPGRADIO_TEMP_DIR(或默认上传目录)是否属于同一盘符/文件系统。
  2. 可优先尝试规避:将 Gradio 上传目录与系统临时目录放到同一文件系统/卷,或者在应用启动前把 TEMP/TMP 指向上传目录所在卷,使 os.rename() 走同文件系统重命名路径,从而不再进入跨文件系统后台拷贝分支。
  3. 若希望从代码层修复,可试用 Issue 中候选补丁思路:在跨文件系统回退时,把上传文件先拷贝到上传/缓存目录内的临时文件,执行 fsync 并关闭,再用 os.replace() 原子发布,最后才返回目标路径。
  4. 若要评估候选补丁,可参考对比链接创建分支并运行 Issue 提到的测试:test/test_publish_atomic.py(原子发布 helper 的单元测试)与 test/test_upload_integration.py(monkeypatch os.rename 模拟跨文件系统失败,断言返回路径立即可读)。
  5. 等待官方合入:Issue 已关闭,但补丁是否进入某正式版本请以官方 release 说明为准,本文不推断版本号。

验证方法

复现侧:在跨卷环境中上传文件后,立即对返回的缓存路径发起 1 KiB Range GET;修复前通常得到 HTTP 403(或偶发 PermissionError),修复后应返回 HTTP 206,且目标文件在响应时已存在且大小完整(Issue 中 32 MiB 探测为 33,554,432 bytes)。集成侧:运行 test/test_upload_integration.py,确认返回路径在返回时即可读;运行 test/test_publish_atomic.py 确认原子发布 helper 行为正确。Issue 报告候选补丁下七个上传/静态 worker 测试通过。

参考来源

gradio-app/gradio #13722

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 22544

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注