panic in a destructor during cleanup,测试进程 SIGABRT
主线位置:主线 1 里「一轮失败之后」。loom 的每个发现都是 panic(08 设计取舍 第 7 条);panic 展开时还要 drop 模型里的对象,loom
Arc的 drop 是一次 loom 操作,要用执行状态。这时执行状态已经不可用,就是第二次 panic,Rust 直接 abort。
flowchart TB
p1["第一次 panic:死锁 / 断言 / 分支上限 / 竞争 ..."] --> w{"展开时要 drop 的 loom Arc 在哪"}
w -->|"栈上,且是死锁:当前线程已被设成 None"| a1["active_mut() 对 None unwrap"]
w -->|"排队中的 spawn 闭包里:tick 已返回,STATE 已撤"| a2["cannot access Loom execution state ..."]
a1 --> ab["panic in a destructor during cleanup → abort(134)"]
a2 --> ab
w -->|"Box::leak / std Arc,或子线程已经跑过"| ok["正常 panic,测试照常失败(101)"]
现象
测试输出先是真正的报错,接着一条看起来无关的 panic,然后:
cannot access Loom execution state from outside a Loom model. are you accessing a Loom synchronization primitive from outside a Loom test (a call to `model` or `check`)?
…
panic in a destructor during cleanup
thread caused non-unwinding panic. aborting.
error: test failed, to rerun pass `--test pit`
Caused by:
process didn't exit successfully: `… --exact assert_after_spawn_loom_arc` (signal: 6, SIGABRT: process abort signal)
同一个测试二进制里排在后面的测试都不会再跑;#[should_panic] 的测试也算失败,因为测试框架根本没收到 panic。
两种触发方式
一:死锁,栈上有 loom Arc。 E10(死锁、分支上限与位置信息):AB-BA 两把锁放在 loom Arc 里,报死锁后 rt/thread.rs:276 对 None 调 unwrap,abort;放在 std Arc 或 Box::leak 里就正常 panic。死锁断言之前,set_active 已经把当前线程设成 None;MutexGuard 的 drop 有「已经死锁就直接返回」的防护(#236),Arc 的 drop 没有。
二:spawn 之后、第一次切换之前出错,排队的闭包里有 loom Arc。 这是本部分新测到的(exp-loom/pitfalls/tests/pit.rs,每个 #[test] 单独进程):
let a = Arc::new(AtomicUsize::new(0));
let a2 = a.clone();
let t = thread::spawn(move || { a2.fetch_add(1, Relaxed); });
assert_eq!(a.load(Relaxed), 1, "user assertion"); // 第 1 轮里子线程还没跑,读到 0
t.join().unwrap();
| 场景 | 第一次 panic | 结果 |
|---|---|---|
上面这段(assert_after_spawn_loom_arc) | 用户断言 | 第二次 panic「cannot access Loom execution state from outside a Loom model」,abort(134) |
同上,子线程闭包捕获 Box::leak 出来的 &'static(assert_after_spawn_leaked) | 用户断言 | 正常失败(101) |
闭包捕获 loom Arc,断言放在 join 之后(assert_after_join_loom_arc) | 用户断言 | 正常失败(101) |
| 分支上限(上一页 的 n = 1000) | 分支上限 | abort(134) |
不确定的模型(坑 的 nondet_std_once_flag) | Is the model fully deterministic? | abort(134) |
RUST_BACKTRACE=1 的第二段回溯是:Scheduler::tick → drop VecDeque<QueuedSpawn> → drop 闭包里的 loom Arc → Arc::drop → rt::execution → with_state panic。thread::spawn 只把闭包放进 queued_spawn(scheduler.rs:73-75),要等 tick 返回后 run 才把它变成协程(96-106 行)。主线程在第一次让出之前 panic,panic 穿出 STATE.set 的作用域,tick 里的局部变量 queued_spawn 在展开中 drop(110-121 行),闭包里的 loom Arc 在 Arc::drop 里调 ref_dec,要进执行状态,这时 STATE 已经撤了。
第二种比第一种常见:「spawn 一个子线程,主线程马上做点什么再断言」是最普通的模型形状,loom 第 1 轮总是让主线程一直跑下去(DPOR 的「尽量不抢占」),主线程在这期间的任何失败都满足条件。
怎么改
- 看第一条 panic。abort 前打印的第一条才是 loom 的发现,后面的
Option::unwrap()、「cannot access Loom execution state」都是展开中的噪音。 - 共享对象用
Box::leak或 stdArc。死锁、竞争检测只要求锁和原子本身是 loom 类型,不要求Arc是;泄漏检测才需要 loomArc。两种触发方式在上面的反向验证里都消失了。 - 会失败的模型一个测试一个进程。
cargo test -- --exact 名字逐个跑,或一个文件只放一个这样的测试;本卷的实验都这么做(exp-loom/pitfalls/run_each.sh)。 - 死锁类的
#[should_panic]测试:锁放进 loomArc时它会因 abort 而失败(should_panic_abba_loom_arc,134);同样的模型改用Box::leak就按预期通过(should_panic_abba_leaked)。
小结
- loom 的报错是 panic;展开时 drop loom
Arc会再碰执行状态,碰不到就第二次 panic,进程 abort(134)。 - 两种触发:死锁且栈上有 loom
Arc(E10);spawn 之后第一次切换之前出错,排队的闭包里有 loomArc(本部分新测:用户断言、分支上限、不确定模型三种第一次 panic 都会这样)。 - 反向验证:换成
Box::leak、或断言挪到join之后,都正常失败(101)。 - 只看第一条 panic;共享对象用
Box::leak/ stdArc;会失败的模型分进程跑。