检测器
loom 的「检查」有两层。一层是用户自己写在模型里的 assert!:每一轮执行都跑一遍,哪一轮断言失败,模型就失败。另一层是 loom 自己内置的检测器,不需要用户写任何断言:只要模型里用了 loom 的类型,下面四类问题在某一轮里出现,loom 就 panic。这一部分讲第二层。
flowchart LR
subgraph acc["每次访问时(不是调度点)"]
cell["UnsafeCell:with / with_mut / get / get_mut"]
atom["原子:load / store / RMW / with_mut / unsync_load"]
end
subgraph sch["每次调度时(Execution::schedule)"]
dl["没有可运行线程、又不是全部结束 → deadlock"]
end
subgraph br["每次新建分支时(Path)"]
mb["分支数达到 max_branches → Model exceeded maximum number of branches"]
end
subgraph end_["每轮结束时(check_for_leaks)"]
lk["Arc 计数不为 0、分配未释放、channel 里还有消息"]
end
acc --> vv["比较向量时钟(03 内存模型)"]
三章:
- UnsafeCell 与原子的非同步访问:主线 3。
rt::Cell的读写时钟;访问开始时检查、守卫 drop 时再检查一次;单元格访问不是调度点,竞争靠向量时钟判定(E7:两个线程直接with_mut,第 1 个交错就报错);ConstPtr/MutPtr与「指针逃出守卫」的盲区;原子的with_mut/unsync_load的四组时钟与九种报错文案。 - 泄漏检测:每轮结束时检查
Arc、分配、channel 三类对象;loom::sync::Arc怎么计数;Track<T>;loom::alloc的指针登记表(E8 的十一个场景);原始分配泄漏时报告之后进程 abort。 - 死锁、分支上限与位置信息:死锁判定与报错格式;死锁 panic 之后的展开为什么会 abort(E10 与反向验证:锁放进
Box::leak就正常 panic);分支上限(E11:不 yield 的自旋锁第 28 个交错撞上限,调大上限也一样);LOOM_LOCATION与PanicBuilder的输出格式;hint模块。
报错原文一览
都是 panic 消息,loom 没有别的报告渠道。
| 报错 | 检测器 | 出处 |
|---|---|---|
| Causality violation: Concurrent read and write accesses. | 单元格:读时发现并发的写 | rt/cell.rs:109 |
| Causality violation: Concurrent write accesses to `UnsafeCell`. | 单元格:写时发现并发的写 | rt/cell.rs:125 |
| Causality violation: Concurrent read and write accesses to `UnsafeCell`. | 单元格:写时发现并发的读 | rt/cell.rs:140 |
| currently writing to cell / currently reading from cell | 单元格:另一个访问还在闭包里(普通 assert!) | rt/cell.rs:60、75-76 |
| Causality violation: Concurrent … (九种) | 原子的 with_mut / unsync_load 与其他访问并发 | rt/atomic.rs:568-712 |
| Arc leaked. / Allocation leaked. / Messages leaked. | 每轮结束的泄漏检查 | rt/arc.rs:171-182、rt/alloc.rs:83-94、rt/mpsc.rs:146-163 |
| pointer not tracked / pointer already tracked | loom::alloc 的登记表 | rt/alloc.rs:31、45 |
| deadlock; threads = [...] | 调度时没有可运行线程 | rt/execution.rs:216-223 |
| Model exceeded maximum number of branches. … | 分支数达到上限 | rt/path.rs:109-123 |
共同的形状
- 搭在同一套时钟上。 竞争检测用 03 向量时钟 的
causality;泄漏、死锁只看对象和线程的状态。没有一个检测器需要额外的执行:它们都在探索交错的同一轮里顺带完成。 - 只看得见 loom 类型。 std 的
Arc、std 的原子、裸指针、FFI 对 loom 都不存在:mem::forget一个 stdArc不报泄漏(E8),从ConstPtr派生出去的指针不受检查(UnsafeCell 与原子的非同步访问)。汇总在 07 「loom 通过」意味着什么。 - 报告即 panic,有时 panic 两次。 panic 发生在模型线程里时,展开会运行栈上对象的析构;析构里再碰 loom 状态,就可能第二次 panic,进程直接 abort(SIGABRT,退出码 134)。本部分遇到两处:死锁之后 drop loom
Arc(死锁、分支上限与位置信息),原始分配泄漏之后 drop 登记表(泄漏检测)。abort 的测试不能和别的测试放在同一个进程里,本部分的实验都是一个#[test]一个场景、逐个--exact运行。
演进
| 时间 | 提交 | 版本 | 变化 |
|---|---|---|---|
| 2019-07-29 | 2b320d3「Atomic unsync load」(#26) | 0.2.0 | 原子的 unsync_load 与对应的竞争检测 |
| 2019-08-17 | 862df05「Add CausalCell concurrent immut+mut access race detection」(#42) | 0.2.3 | 「Causality violation」检测;CausalCell 在 0.3.0 改名 UnsafeCell |
| 2019-10-04 | a857427「Check for leaks」(#73) | 0.2.9 | 每轮结束的泄漏检查与 Track |
| 2021-10-30 | e26add9「Don't attempt to cleanup Mutex when threads are deadlocked」(#236) | 0.5.3 | Mutex::release_lock 在死锁后直接返回 |
| 2021-12-03 | b70695a「Add access guards to UnsafeCell」(#219) | 0.5.4 | UnsafeCell::get / get_mut 返回 ConstPtr / MutPtr 守卫 |
| 2021-12-04 | 9a546f6「fix double panic when exceeding the branch limit in Drop」(#245) | 0.5.5 | 分支上限断言在 panic 期间跳过;顺带把报错里的拼写「exeeded」改成「exceeded」 |
| 2022-05-09 | 945c11a「add additional location tracking to Arc, alloc, and mpsc」(#265) | 0.5.5 | 泄漏报错里的「Allocated:」「Channel created:」 |
0.5.3、0.5.4 在仓库里没有 tag,版本号按 CHANGELOG。
本部分的实验
机器 M2 Max,macOS,rustc 1.99.1,loom =0.7.2,RUSTFLAGS="--cfg loom" cargo test --release --offline,单独的 CARGO_TARGET_DIR。交错数是确定的。会 abort 的场景单独成一个 #[test],用 exp-loom/checkers/run_each.sh 逐个在独立进程里运行并记录退出码。
| # | 目录 | 看什么 | 章 |
|---|---|---|---|
| E7 | exp-loom/checkers/tests/cell.rs | 两个线程直接 with_mut / 加锁;spawn 前后写;闭包期间被别的线程访问;指针逃出守卫;原子的 unsync_load / with_mut | UnsafeCell 与原子的非同步访问 |
| E8 | exp-loom/checkers/tests/leaks.rs | forget loom Arc / Track / std Arc;into_raw;loom::alloc 的四种用法;channel | 泄漏检测 |
| E10 | exp-loom/checkers/tests/deadlock.rs | 自锁、AB-BA;锁放在 loom Arc / std Arc / Box::leak 里时死锁之后是否 abort | 死锁、分支上限与位置信息 |
| E11 | exp-loom/checkers/tests/deadlock.rs | 自旋锁不 yield / yield_now / hint::spin_loop;不同的 LOOM_MAX_BRANCHES | 死锁、分支上限与位置信息 |
💬 关联讨论
发起讨论加载中...