Rust in Everything
基于 loom 0.7.2

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 或 std Arc。死锁、竞争检测只要求锁和原子本身是 loom 类型,不要求 Arc 是;泄漏检测才需要 loom Arc。两种触发方式在上面的反向验证里都消失了。
  • 会失败的模型一个测试一个进程。cargo test -- --exact 名字 逐个跑,或一个文件只放一个这样的测试;本卷的实验都这么做(exp-loom/pitfalls/run_each.sh)。
  • 死锁类的 #[should_panic] 测试:锁放进 loom Arc 时它会因 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 之后第一次切换之前出错,排队的闭包里有 loom Arc(本部分新测:用户断言、分支上限、不确定模型三种第一次 panic 都会这样)。
  • 反向验证:换成 Box::leak、或断言挪到 join 之后,都正常失败(101)。
  • 只看第一条 panic;共享对象用 Box::leak / std Arc;会失败的模型分进程跑。

💬 关联讨论

发起讨论
加载中...