Rust in Everything
基于 loom 0.7.2

源码地图

主线位置:四条主线经过的文件。主线 1(一次 model 的一生)在 model.rs 和 rt/ 的 scheduler.rs、execution.rs、path.rs;主线 2(一次 load 的候选)在 rt/atomic.rs、rt/synchronize.rs;主线 3(数据竞争的检测)在 rt/cell.rs 和 rt/atomic.rs 的后半;主线 4(看到什么、看不到什么)是全卷的横线。

flowchart TB
  user["被测代码(cfg(loom) 切换后)"] --> api
  subgraph api["对外 mock 层:sync / cell / thread / alloc / lazy_static / future / hint(3 539 行)"]
    sync["sync::atomic::AtomicUsize、Mutex、Arc、mpsc…"]
    cell["cell::UnsafeCell、Cell"]
    thr["thread::spawn、park、thread_local!"]
  end
  api -->|"rt::Atomic::new(v, location!())"| rt
  subgraph rt["运行时 rt/:全部 pub(crate)(4 965 行)"]
    sched["scheduler.rs:协程"]
    exec["execution.rs:Execution、schedule(DPOR)"]
    path["path.rs:Path"]
    obj["object.rs:Store、Ref"]
    prims["atomic / cell / mutex / rwlock / condvar / notify / arc / mpsc / alloc / lazy_static / thread"]
  end
  model["model.rs:model、Builder(278 行)"] --> rt
  rt --> gen["generator(有栈协程)"]
  rt --> stls["scoped-tls(STATE)"]
  model --> trc["tracing + tracing-subscriber"]

两层

src/ 下共 8 504 行(含文档注释;loom 自己的源码里没有 #[cfg(test)] 模块,测试都在 tests/):

部分行数可见性管什么
顶层:lib.rs、model.rs、thread.rs、alloc.rs、lazy_static.rs、hint.rs1 295pubcrate 文档(lib.rs 474 行里 344 行是文档)、model / Builder、thread 与 alloc 的 mock、两个宏
sync/1 436pubstd sync 的 mock:原子、Arc、Mutex、RwLock、Condvar、mpsc,以及 loom 独有的 Notify
cell/663pubUnsafeCell(带 with / with_mut / get / get_mut)与 Cell
future/145pub,futures featureblock_on、AtomicWaker
rt/4 965全部 pub(crate)(除了 yield_now、explore 等几个转出去的函数和 MAX_THREADS)运行时:调度、路径、对象仓库、每种原语的状态与检查

对外类型几乎都只是「一个 rt 对象 + location!()」的薄壳。以 AtomicUsize::load 为例,它调 sync::atomic::Atomic::load,后者调 self.state.load(location!(), order),再往下就进了 rt::Atomic。location!() 宏在 LOOM_LOCATION 关闭时返回一个空位置,打开时借 #[track_caller] 记下调用者的文件、行、列(死锁、分支上限与位置信息)。

顶层与 sync、cell、future:每个文件一句话

文件行数管什么本卷
lib.rs474crate 文档(背景、写测试、调试、局限、实现);导出;thread_local! 与 lazy_static! 两个宏loom 解决什么问题
model.rs278Builder 10 个字段与环境变量、check 主循环、model、checkpoint 读写model 与 Builder
thread.rs314spawn(无 Send 约束)、JoinHandle、Builder(名字、栈大小)、park / unpark、current、LocalKey线程、线程局部与 lazy_static
alloc.rs109alloc / alloc_zeroed / dealloc 的泄漏跟踪版、Track<T>泄漏检测
lazy_static.rs92Lazy<T>:每轮重新初始化的 lazy_static;文件开头还重新导出了 AccessError、yield_now、panicking,和 thread 模块的导出相同线程、线程局部与 lazy_static
hint.rs28spin_loop(等于 yield_now)、unreachable_unchecked(直接 panic)死锁、分支上限与位置信息
sync/atomic/(5 个文件)607AtomicBool、AtomicPtr、AtomicI8…AtomicUsize 共 10 个整数类型;fence;spin_loop_hint03 内存模型
sync/arc.rs282Arc<T>:std Arc 存数据 + rt::Arc 计数;from_stdArc、mpsc 与 future
sync/rwlock.rs、mutex.rs、condvar.rs169、120、79数据放在 std 的同名锁里,loom 的 rt 对象管调度与同步Mutex 与 RwLock、Condvar、Notify 与 park
sync/mpsc.rs89无界 channel;recv_timeout 是 unimplemented!Arc、mpsc 与 future
sync/notify.rs51loom 独有的 Notify:一次性的 park / unpark 配对,可伪唤醒一次Condvar、Notify 与 park
sync/barrier.rs19Barrier 桩:new 和 wait 都是 unimplemented!("std::sync::Barrier is not supported yet in Loom.")本页
sync/mod.rs20导出;LockResult、TryLockResult 直接用 std 的—
cell/unsafe_cell.rs538UnsafeCell<T>、ConstPtr、MutPtr;大部分是文档UnsafeCell 与原子的非同步访问
cell/cell.rs117Cell<T>:在 UnsafeCell 上实现的 std Cell 接口同上
future/mod.rs、atomic_waker.rs78、67block_on、AtomicWaker(tokio 同名类型的 mock)Arc、mpsc 与 future

rt:每个文件一句话

文件行数管什么本卷
mod.rs222MAX_THREADS = 5、MAX_ATOMIC_HISTORY = 7;spawn、park、branch、synchronize、yield_now、thread_done;explore / stop_exploring / skip_branchExecution 与对象仓库
scheduler.rs161generator 协程、STATE、switch、run、tick协程调度器
execution.rs286Execution、new_thread、step、schedule(DPOR、选线程、死锁断言)Execution 与对象仓库、DPOR
path.rs547Path、Schedule / Load / Spurious、step、backtrack、抢占计数Path、抢占上限与剪枝
object.rs465Store、Ref、Entry、Operation、Action;branch_*;泄漏检查的入口Execution 与对象仓库
access.rs42Access { path_id, dpor_vv }DPOR
vv.rs86VersionVec:[u16; 5] 的向量时钟向量时钟与同步点
synchronize.rs63Synchronize:按内存序做 acquire / release同上
thread.rs492线程状态、三套时钟、Set、seq_cst / seq_cst_fence、线程局部同上
atomic.rs931原子的状态:7 次写的历史、coherence、match_load_to_stores、RMW、fence、非同步访问检查03 内存模型、UnsafeCell 与原子的非同步访问
num.rs52Numeric:所有原子值都换成 u64 存写历史与 coherence
cell.rs188单元格的读写时钟与竞争检查UnsafeCell 与原子的非同步访问
location.rs150Location、LocationSet、PanicBuilder(报错的格式)死锁、分支上限与位置信息
alloc.rs95分配的泄漏跟踪泄漏检测
arc.rs210Arc 的计数、三种动作与依赖、泄漏检查Arc、mpsc 与 future
mutex.rs、rwlock.rs、condvar.rs、notify.rs155、271、104、148各原语的状态、阻塞与唤醒、同步05 同步原语的模型
mpsc.rs178channel:每条消息一个 SynchronizeArc、mpsc 与 future
lazy_static.rs87每轮的 lazy_static 值表线程、线程局部与 lazy_static
futures.rs32不参与编译:rt/mod.rs 没有声明这个模块。内容是第一个提交(2b37a35)就有的一个 Notify,引用的 _futures::executor、rt::ThreadHandle 早已不存在;最后一次被改是 88e4225(#111,2020-03)—

feature 与依赖

Cargo.toml:

依赖版本要求可选?用在哪
cfg-if1.0.0否if_futures! 宏
scoped-tls1.0.0否scheduler.rs 的 STATE
generator0.8.1否有栈协程;本卷实验锁到 0.8.11
tracing0.1.27,default-features = false, features = ["std"]否运行时到处的 trace! / info!、iter 与 thread span
tracing-subscriber0.3.8,features = ["env-filter"]否model() 每次调用都新建一个 fmt 订阅者,EnvFilter 读 LOOM_LOG
serde(derive)、serde_json1.0.92、1.0.33是,checkpoint feature把 Path 存成 JSON
pin-utils0.1.0是,futures featureblock_on 里的 pin_mut!

两个 feature 都默认关闭:

feature打开什么不开时
checkpointPath 等类型 derive(Serialize, Deserialize);LOOM_CHECKPOINT_FILE 可用设了 LOOM_CHECKPOINT_FILE 就 panic「not compiled with checkpoint feature」(model.rs:267-278)
futuresloom::future 模块(block_on、AtomicWaker)没有这个模块

tracing-subscriber 不是可选依赖这一点值得记住:任何用 loom 的 crate,开发依赖树里都会多出它和它的 env-filter(regex 等),即使从不看 loom 的日志。

公开 API 一览

项在哪本卷
loom::model(f)、loom::model::Buildermodel.rsmodel 与 Builder
loom::explore()、stop_exploring()、skip_branch()rt/mod.rs抢占上限与剪枝
loom::MAX_THREADSrt/mod.rs,pub const 只为文档导出model 与 Builder
loom::thread_local!、loom::lazy_static!lib.rs 的两个 #[macro_export] 宏线程、线程局部与 lazy_static
loom::sync::*、loom::sync::atomic::*、loom::sync::mpscsync/05、03
loom::cell::{UnsafeCell, Cell, ConstPtr, MutPtr}cell/04
loom::thread::*thread.rs05
loom::alloc::{alloc, alloc_zeroed, dealloc, Track, Layout}alloc.rs04
loom::hint::{spin_loop, unreachable_unchecked}hint.rs04
loom::future::{block_on, AtomicWaker}future/,futures feature05

没有的东西

把 std 的代码换成 loom 时会撞上的缺口(在 src/ 里 grep 不到):

std 里有loom 0.7.2怎么办
std::sync::Weak没有模型里避免 Weak,或只在 cfg(not(loom)) 下用
OnceLock、Once、LazyLock没有用 loom::lazy_static!(每轮重新初始化,线程、线程局部与 lazy_static);crossbeam-epoch 就是这么替换默认收集器的
thread::scope没有用 loom::sync::Arc + thread::spawn + join
thread::sleep没有模型里没有时间,用 yield_now 或同步原语
atomic::compiler_fence没有,sync::atomic 只导出 fencecrossbeam 用 fence 代替(crossbeam 卷)
AtomicU128 / AtomicI128没有—
mpsc::sync_channel没有,只有无界 channel—
Barrier有类型,但调用即 panic不能用;用 Condvar 或 Notify 自己写
mpsc::Receiver::recv_timeout有方法,但调用即 panic同上

「有类型但调用即 panic」的两项尤其要注意:代码能编译,loom 测试一跑到那里就失败,报错是 unimplemented! 的文案。

tests:20 个文件

上游 tests/ 共 20 个文件、2 163 行。本卷只读不跑(不在上游目录里运行任何测试),在相关章引用它们作为「上游自己写下的预期」:

文件测试数测什么本卷
smoke.rs1一个故意错的计数器,loom 必须报错—
atomic.rs7(1 个 #[ignore])lazy_static 里的 Arc、unsync_load 的非法用法、CAS 读旧值、fetch_add03、05
atomic_int.rs6(1 个 #[ignore])各整数原子的方法03
atomic_relaxed.rs4compare_and_swap 与内存序合法性RMW 与释放序列
fence.rs7fence 同步;SB+fences(Promising Semantics 论文)、RWC+syncs 与 W+RWC(SCFix 论文)fence 与 SeqCst
litmus.rs2(1 个 #[ignore])load buffering(注释:「Loom currently does not support load buffering」,被 ignore)、store bufferingSeqCst 与 load buffering
spec.rs3(2 个 #[ignore])从 cppreference 内存序页面转写的例子03
unsafe_cell.rs13单元格的各种竞争组合UnsafeCell 与原子的非同步访问
arc.rs6基本用法、Drop 里的同步、泄漏检测、try_unwrapArc、mpsc 与 future
deadlock.rs1两把锁反序加锁的死锁死锁、分支上限与位置信息
double_panic_in_drop.rs1分支超限发生在 Drop 里时不应二次 panic(#245)同上
mutex.rs3互斥、「建立 SeqCst」、into_innerMutex 与 RwLock
rwlock.rs10读写锁的各种组合与 try_*同上
rwlock_regression1.rs1两个写者的回归测试(0.6.0 的 #300)同上
condvar.rs2notify_one、notify_allCondvar、Notify 与 park
mpsc.rs6顺序与并行收发、多发送者、drop 接收端Arc、mpsc 与 future
futures.rs2AtomicWaker、伪 poll同上
thread_api.rs8线程 id、名字、栈大小、join、park / unpark线程、线程局部与 lazy_static
thread_local.rs3thread_local! 每线程一份、嵌套 with、析构同上
yield.rs1带 yield_now 的自旋能跑完死锁、分支上限与位置信息

小结

  • 两层:pub 的 mock 层(3 539 行)只是 rt 对象 + location!() 的薄壳;rt/(4 965 行)全部 pub(crate),是本卷的主体。
  • rt/futures.rs 32 行没有被 rt/mod.rs 声明,不参与编译,是第一个提交留下的。
  • 两个 feature:checkpoint(serde 存 Path)、futures(block_on 与 AtomicWaker);tracing-subscriber 是必选依赖。
  • 公开 API 里没有 Weak、OnceLock、scope、sleep、compiler_fence、128 位原子、sync_channel;Barrier 与 recv_timeout 能编译、一调用就 panic。
  • tests/ 20 个文件,本卷只引用不运行。

💬 关联讨论

发起讨论
加载中...