结论:Athena 的按远端请求上传用「4 个 worker + 优先队列 + Params 快照」实现,但它不是一张事务表:入队写盘、出队、取消、过期这些时点的快照并不一致,取消与过期分支都不会刷新快照,进程重启后已取消或已过期的任务可能重新出现。重试上限是 30 次、每次最长等待 10 秒,而 401/403/412 被归入终止分支、日志里也记为 success,不能当作远端已存储证明。以下为固定源码快照 c8fb906 的静态分析与隔离探针结论。
队列与四个 worker
这里讨论的是 Athena 按远端请求上传指定文件的队列,不是 loggerd/uploader.py 的常规路段上传。upload_handler() 由四个线程共享一个优先队列,取任务后依次处理取消、31 天过期、计费网络判断、PUT 响应与异常;线程数由 handle_long_poll() 固定创建四个,不受 RPC 的 HANDLER_THREADS 配置影响。main() 进程启动时只恢复一次队列,每次 WebSocket 会话创建上传线程,重连成功后清空当前任务字典。
UploadFile、UploadItem 负责 RPC 参数映射与队列任务;UploadItem.__lt__/__eq__ 只比较 priority,不以 ID 比较。因此优先队列不会因此去重,同优先级的 FIFO 顺序也没有由任务 ID 或入队序号明确保证。
| 组件 | 入口 | 职责和边界 |
|---|---|---|
UploadItem |
athenad.py |
RPC 参数映射与队列任务;比较只按 priority,不按 ID,队列不去重。 |
UploadQueueCache |
athenad.py |
启动从 Params AthenadUploadQueue 恢复;只快照等待队列并排除已取消 ID。 |
uploadFilesToUrls() |
athenad.py |
校验文件名与存在性、按 URL 去查询串去重、生成 ID、入队并写快照。 |
upload_handler() |
athenad.py |
四个线程共享优先队列,处理取消、31 天过期、计费网络、PUT 响应与异常。 |
retry_upload()、cb() |
athenad.py |
计数小于 30 时重排并持久化,随后最长等待 10 秒;回调只在状态年龄大于 1 秒时检查计费。 |
listUploadQueue()、cancelUpload() |
athenad.py |
列表合并等待与当前任务;取消只标记等待项,不中止当前传输,也不立即刷新 Params。 |
入队去重与任务 ID 的真实边界
uploadFilesToUrls() 会检查文件名与存在性:拒绝空名、绝对路径与包含 .. 的名字,但没有证明符号链接与真实路径受限。去重比较的是 file.url.split('?')[0],覆盖等待与当前列表,未比较文件内容、完整 URL 或 headers。它只防止入队当时可见的相同基础 URL。
任务 ID 用包含创建时间的 str(item) 做 SHA-1,这不是内容摘要,也不是服务端事务 ID。因此这套去重与 ID 都不能充当跨重启或并发请求的幂等键——重启后旧快照可能把任务恢复回来,而同一文件换个查询串就会被当作新任务。
结果码、重试与等待
上传结果被分成两类:响应码为 200/201/401/403/412 时终止任务、不再重试;其他 HTTP 状态以及 Timeout/ConnectionError/SSLError 计入重试。retry_upload() 在当前 retry_count < 30 时重新排队与持久化,随后最长等待 10 秒;计数初始 0 可重排为 1,29 可重排为 30,已经是 30 时不再重排。
计费网络与重连中止走的是不增计数分支:调用 retry_upload(..., False),符合上限判断才重排并写盘,但每次都仍按最长 10 秒等待。_do_upload() 在请求的 .zst 不存在而扩展名去掉后的原文件存在时,会边压缩边上传,使用请求提供的 URL 与 headers,覆盖 Content-Length,PUT 超时 30 秒。总体而言,名为 success 的日志事件包含 401/403/412,不能当作远端已存储的证明。
| 结果 | 是否重试 | 说明 |
|---|---|---|
200/201/401/403/412 |
否,终止任务 | HTTP 返回后写等待队列快照;success 日志含 401/403/412,非远端存储证明。 |
| 其他 HTTP 状态 | 是 | 计数加一后重排并写盘;计数 30 时不再重排。 |
Timeout/ConnectionError/SSLError |
是 | 同可重试失败路径,计数达到上限后无额外快照更新。 |
| 计费网络/会话退出 | 中止但不增计数 | 调用 retry_upload(..., False),仍按最长 10 秒等待;进入时已达 30 则不重排。 |
持久化窗口
UploadQueueCache.cache() 的 block=True 只要求 Params 写入调用阻塞完成,不是远端上传回执,也没有把「等待、传输中、完成、失败」作为单个原子状态持久化。listUploadQueue() 是瞬时内存视图;缓存与列表分别直接访问优先队列内部数组,不能视作同一事务快照。
隔离探针复现了几个必须知道的窗口:等待项已有快照后调用 cancelUpload(),内存列表隐藏该 ID,但 Params 中仍是旧快照,模拟重启后会重新进入等待队列;retry_upload() 先重排与缓存、再清 cur_upload_items[tid],在缓存调用期间查询列表可暂时看到同一 ID 的「等待」和「当前」两项。探针没有并发调度、真实持久化故障或 HTTP 请求,证明的是允许这些状态的局部执行顺序。
| 路径 | Params 快照时机 | 可观察结果 |
|---|---|---|
| 入队 | 循环结束后写一次 | RPC enqueued 表示入队,不是上传确认;多任务入队与写盘非原子。 |
| 出队及上传中 | 出队时不写快照 | 原快照可能仍含该项;中断后重放既不能保证也不能排除。 |
| 成功分支 | HTTP 返回后写等待队列快照 | 当前字典直到下轮才置空,列表可能短暂显示已结束项。 |
| 可重试失败 | 先入队再写快照,HTTP 分支随后再写一次 | 计数可 0→1、29→30;已是 30 则不再重排。 |
| 取消/过期 | 两个提前 continue 分支都不刷新快照 | 旧快照可能仍有该项,进程重启后可能重新出现。 |
| 其他异常 | 没有统一任务终结/缓存动作 | 是否重新出现取决于此前快照与其他线程写盘,不应推断为可靠重试。 |
自制硬件与插件重构要求
源文档给出一组建议:把上传置于非实时服务域,与 CAN/执行器控制隔离;文件能力使用目录 FD 或真实路径约束,目标 URL、重定向与 headers 用显式策略,不能把远端 RPC 参数直接授予任意插件。任务应定义 queued / leased / transferring / accepted / rejected / retry_wait / exhausted / cancelled / expired 等可持久状态,出队先记录租约、超时回收,并用单一事务或追加日志保存状态迁移。
幂等应以服务端任务 ID 或文件内容摘要加目标对象键实现;重试需退避、抖动、总期限与错误分类,401/403/412 应有各自的鉴权/权限/前置条件结果,不能统一归为成功。取消 API 必须覆盖等待与传输中任务并持久化取消终态。验收要用离线测试服务与故障注入:四线程并发、每个持久化时点断进程、重复 RPC、计费网络切换、重连、缺失文件、符号链接、HTTP 拒绝与重试耗尽。
常见问题
RPC 返回 enqueued 是不是就说明文件已上传?
不是。入队只在循环结束后写一次 Params 快照,enqueued 表示入队成功,不代表上传确认,多任务入队与写盘也不是原子事务。
上传重试最多几次,间隔多久?
重试上限是 30 次:retry_upload() 在 retry_count < 30 时重排,已是 30 则不再重排;每次重排后最长等待 10 秒。计费网络或会话退出中止时不增加计数,但仍按最长 10 秒等待。
日志里出现 success 就等于远端存好了吗?
不等于。名为 success 的日志事件包含 401/403/412,这些是终止分支的响应码;它们不构成远端已存储的证明,需要服务端回执另行确认。
为什么重启后已取消的任务又出现了?
因为取消与过期这两个提前 continue 分支都不刷新 Params 快照,旧快照可能仍包含该项;模拟重启时清空内存取消集并执行 initialize(),任务会重新进入等待队列。
延伸阅读
- 路段下载与鉴权边界:直读 URL、内网路径与本地文件的分权 —— 远端文件传输的目标域与权限边界。
- WebRTC 会话生命周期与 HTTP 边界 —— 重连与当前任务字典清空发生在哪一层。
- 设备配对与数据回传 —— 上传链上游的配对与数据通道。
- 日志源可用性与缓存 —— 上传任务读取文件前的能力约束。
参考资料
- 项目 02 ——
openpilot-research/ATHENA_UPLOAD_QUEUE_PERSISTENCE_RETRY_DEEP_DIVE.md(Athena 上传队列、四个 worker、结果码与持久化窗口)。 - 项目 02 ——
openpilot-research/同目录探针记录:四项队列快照与五项上传回调计费门隔离探针结论。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复