Rust in Everything
基于 loom 0.7.2

检测器

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 内存模型)"]

三章:

  1. UnsafeCell 与原子的非同步访问:主线 3。rt::Cell 的读写时钟;访问开始时检查、守卫 drop 时再检查一次;单元格访问不是调度点,竞争靠向量时钟判定(E7:两个线程直接 with_mut,第 1 个交错就报错);ConstPtr / MutPtr 与「指针逃出守卫」的盲区;原子的 with_mut / unsync_load 的四组时钟与九种报错文案。
  2. 泄漏检测:每轮结束时检查 Arc、分配、channel 三类对象;loom::sync::Arc 怎么计数;Track<T>;loom::alloc 的指针登记表(E8 的十一个场景);原始分配泄漏时报告之后进程 abort。
  3. 死锁、分支上限与位置信息:死锁判定与报错格式;死锁 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 trackedloom::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 一个 std Arc 不报泄漏(E8),从 ConstPtr 派生出去的指针不受检查(UnsafeCell 与原子的非同步访问)。汇总在 07 「loom 通过」意味着什么。
  • 报告即 panic,有时 panic 两次。 panic 发生在模型线程里时,展开会运行栈上对象的析构;析构里再碰 loom 状态,就可能第二次 panic,进程直接 abort(SIGABRT,退出码 134)。本部分遇到两处:死锁之后 drop loom Arc(死锁、分支上限与位置信息),原始分配泄漏之后 drop 登记表(泄漏检测)。abort 的测试不能和别的测试放在同一个进程里,本部分的实验都是一个 #[test] 一个场景、逐个 --exact 运行。

演进

时间提交版本变化
2019-07-292b320d3「Atomic unsync load」(#26)0.2.0原子的 unsync_load 与对应的竞争检测
2019-08-17862df05「Add CausalCell concurrent immut+mut access race detection」(#42)0.2.3「Causality violation」检测;CausalCell 在 0.3.0 改名 UnsafeCell
2019-10-04a857427「Check for leaks」(#73)0.2.9每轮结束的泄漏检查与 Track
2021-10-30e26add9「Don't attempt to cleanup Mutex when threads are deadlocked」(#236)0.5.3Mutex::release_lock 在死锁后直接返回
2021-12-03b70695a「Add access guards to UnsafeCell」(#219)0.5.4UnsafeCell::get / get_mut 返回 ConstPtr / MutPtr 守卫
2021-12-049a546f6「fix double panic when exceeding the branch limit in Drop」(#245)0.5.5分支上限断言在 panic 期间跳过;顺带把报错里的拼写「exeeded」改成「exceeded」
2022-05-09945c11a「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 逐个在独立进程里运行并记录退出码。

#目录看什么章
E7exp-loom/checkers/tests/cell.rs两个线程直接 with_mut / 加锁;spawn 前后写;闭包期间被别的线程访问;指针逃出守卫;原子的 unsync_load / with_mutUnsafeCell 与原子的非同步访问
E8exp-loom/checkers/tests/leaks.rsforget loom Arc / Track / std Arc;into_raw;loom::alloc 的四种用法;channel泄漏检测
E10exp-loom/checkers/tests/deadlock.rs自锁、AB-BA;锁放在 loom Arc / std Arc / Box::leak 里时死锁之后是否 abort死锁、分支上限与位置信息
E11exp-loom/checkers/tests/deadlock.rs自旋锁不 yield / yield_now / hint::spin_loop;不同的 LOOM_MAX_BRANCHES死锁、分支上限与位置信息

💬 关联讨论

发起讨论
加载中...