第 4 章:Torrent 生命周期 —— 从一段元数据到可恢复任务
.torrent文件不是任务,magnet 链接也不是任务。只有当元数据、用户覆盖、resume 状态、文件事实和 Session 策略合并后,tr_torrent才成为一个真正可运行的领域对象。
一、创建前先有 tr_ctor
Transmission 使用 tr_ctor 收集创建参数。它同时容纳:
- 来源:本地
.torrent、原始 bencode、magnet link; - 强制值:本次添加明确指定的目录、暂停状态、Peer 限制;
- fallback:来自 Session 的默认下载目录、默认暂停状态、默认 Peer 限制;
- 文件选择与优先级;
- 是否删除原始 torrent 文件;
- 验证完成回调。
这相当于 Builder,但它还承担“值从哪里来”的优先级语义。TR_FORCE 与 TR_FALLBACK 使 RPC/UI 的本次参数可以覆盖全局默认,又不会直接修改 Session。
二、添加流程的七个阶段
2.1 身份以 info hash 为准
tr_torrentNew() 先从 ctor “偷走”解析后的 metainfo。没有 info hash 就拒绝;Session 的 tr_torrents 容器已有相同 hash 就报告重复。临时 torrent id 在 daemon 重启后可能改变,而 info hash 是稳定身份。
2.2 注册发生得很早
tr_torrent::init() 在读取 resume 之前就调用 session->addTorrent(this):分配 id、加入 queue、为 Peer Manager 建立 swarm。之后 resume 加载与信号连接都能通过稳定 id 找到任务。
2.3 配置合并有明确顺序
tr_resume::load() 的核心顺序是:
ctor 强制字段 → resume 文件 → ctor fallback 字段已加载的字段用 64 位 bit mask 记录,后续来源只补缺失项。这样“这次明确要求暂停”不会被旧 resume 覆盖,“没有明确要求下载目录”则能恢复上次目录。
三、.torrent 与 magnet 走的是两段式生命周期
3.1 完整 torrent
tr_torrent_metainfo::parse_benc() 解析 announce list、文件、piece hash、piece length、私有标志、webseed 等信息,并计算 info dictionary 的 hash。任务一开始就能建立:
tr_block_info:总大小、piece/block 数量与坐标转换;tr_file_piece_map:文件、piece、字节范围关系;tr_completion:已有 block/piece;- 文件 wanted 与 priority 状态。
3.2 magnet
magnet 初始只有 info hash、显示名、tracker/webseed 等少量信息,不能知道文件布局和 piece hashes。Torrent 可以绕过普通下载队列先启动,与支持 LTEP 的 Peer 交换 ut_metadata。
收到 metadata piece 后,tr_metadata_download 检查编号与长度、拼接 info dictionary,并做两级验证:
- 计算完整 metadata 的 SHA-1,必须等于 magnet 的 info hash;
- 把它包装成合成 torrent 顶层字典,再走标准 bencode metainfo 解析。
成功后保存 .torrent、删除 .magnet,调用 set_metainfo() 重建 completion/file map 等结构,再进入与普通 torrent 相同的校验和启动流程。
设计重点
magnet 没有另写一套长期下载逻辑。它只是一种“元数据尚未补齐”的临时状态,一旦补齐就汇入标准 tr_torrent_metainfo 路径。
四、Torrent 状态不是一个 enum
公开活动状态 TR_STATUS_* 是多个内部字段的派生结果:
Verify Active → CHECK
Verify Queued → CHECK_WAIT
is_running → DOWNLOAD 或 SEED(取决于 completion)
is_queued → DOWNLOAD_WAIT 或 SEED_WAIT
其他 → STOPPED这比维护一个可随意赋值的大 enum 更安全:验证、运行、队列和完成度各自有独立事实,activity() 只负责组合展示。
五、启动为何分成两段
tr_torrent::start() 可从任意调用线程进入,先在锁内做快速判定:
- 已在下载/做种就直接返回;
- 正在验证则等待验证结束;
- 队列没有空槽则只设置
is_queued_; - 本地文件消失则设置 local error;
- 手动重启已达 ratio 的任务时,关闭 torrent 级 ratio 限制。
随后设置运行意图,并把 start_in_session_thread() 投递到 Session 线程。第二段才做有副作用的系统动作:
- 为 wanted 文件创建空文件;
- 重算完成度;
- 清除 queued;
- 重置本次运行统计与错误;
- 让 Announcer 发送 STARTED;
- 安排 LPD;
- 发出
started_信号。
这种两段式把“调用者立即看到的意图”与“必须在事件线程串行完成的动作”分开。
六、停止、删除与删除数据是三件事
6.1 停止
tr_torrentStop() 清除 start_when_stable_,然后在 Session 线程运行 stop_now()。后者停止验证、发信号、向 Tracker 安排 STOPPED、关闭任务文件句柄并保存 resume。
6.2 从 Session 删除任务
tr_torrentRemove() 标记 is_deleting_ 后排队执行。删除任务会:停止 torrent、从 Announcer/Peer Manager/容器/队列移除、删除 .torrent/.magnet/.resume 状态文件,最后释放对象。
6.3 删除本地数据
只有 delete_flag 为真时才调用 tor->files().remove(...)。执行前先关闭文件句柄并从验证队列移除,防止 Windows 文件占用或后台校验与删除竞争。文件删除失败会记录警告,但仍继续移除 torrent 元对象。
这三个语义必须在 UI/RPC 层清楚区分,否则“移除任务”很容易变成不可恢复的数据删除。
七、一个 block 完成后发生什么
tr_torrent::on_block_received() 是下载数据闭环的关键:
- 重复 block 只修正下载计数,不重复写完成状态;
- 在
tr_completion中标记 block; - 计算该 block 覆盖的首尾 piece;
- 对刚刚凑齐的 piece 执行
check_piece(); - 成功调用
on_piece_completed(),失败调用on_piece_failed()。
Piece 成功后会发信号、标记需要重算 completeness,并检查它是否让某些文件完整。文件完成时关闭句柄、记录 mtime、把 .part 路径改回正式文件名。
Piece 失败则增加 corrupt 计数、回退 downloaded 计数、清除已有 piece,并通知 Peer Manager。Peer Manager 可借助每个 Peer 的 blame/strike 统计识别持续提供坏数据的节点。
八、完成度为什么还要重算
“所有 wanted 文件完成”和“整个 torrent 所有 piece 完成”不是同一件事。用户可以取消选择文件,但边界 piece 可能同时覆盖 wanted 与 unwanted 文件。tr_completion 与 tr_file_piece_map 共同判断:
TR_LEECH:仍缺 wanted 数据;TR_PARTIAL_SEED:wanted 文件已完成,但 torrent 并不完整;TR_SEED:拥有全部数据。
这一状态会影响 queue direction、Tracker 上报、Peer 策略、完成脚本和文件移动。
九、恢复文件是任务状态的快照,不是事实来源
.resume 保存上传/下载计数、Peer、进度、wanted、优先级、限速、目录、运行意图、日期、ratio/idle、文件名、标签、分组和顺序下载设置。
但磁盘可能在 Transmission 关闭期间被修改。恢复进度时会结合文件 mtime 与 checked-pieces bitfield,决定哪些 piece 仍可信。完整验证永远以文件内容和 metainfo hash 为准。
这是重要原则:resume 是加速恢复的缓存,磁盘字节与 cryptographic hash 才是真相。
十、异步回调为什么常捕获 id 而不只捕获指针
验证、移动和 RPC 异步动作完成时,用户可能已经删除 torrent。安全模式是捕获 session + tor_id,回到 Session 线程后再次查询 session->torrents().get(id),并确认仍是同一对象、未处于 deleting 状态。
只捕获裸 tr_torrent* 再延迟执行,会把生命周期问题变成 use-after-free。源码中值得重点审查所有“lambda 捕获 tor 指针并跨事件循环”的位置。
源码锚点
libtransmission/torrent-ctor.cc:创建参数与 metainfo 入口libtransmission/torrent.cc:init/start/stop/remove/on_block_receivedlibtransmission/torrent.h:状态字段与派生activity()libtransmission/torrent-metainfo.cc:bencode 元数据解析libtransmission/torrent-magnet.cc:metadata 下载与校验libtransmission/resume.cc:三段式配置合并与恢复快照libtransmission/completion.cc:完成度计算