invalid internal loom state:同线程嵌套 read() 之后 abort
主线位置:主线 4 在
RwLock上的两条:同线程嵌套读报内部错误(假阳性),没有写者优先(假阴性)。机制见 Mutex 与 RwLock。
flowchart TB code["同一线程:read() 还没放,又 read()"] --> loom["loom:读者集合是线程 id 的 HashSet,按线程不按次数"] loom --> e["内层守卫 drop 后集合已空;外层 drop 时 invalid internal loom state → 二次 panic → abort"] code --> real["真实实现:有写者排队、写者优先时死锁(parking_lot、本机 std)"] real --> miss["loom 没有写者优先,这种死锁本来也报不出"]
现象
invalid internal loom state
cannot access Loom execution state from outside a Loom model. …
thread caused non-unwinding panic. aborting.
第一行来自 rt/rwlock.rs:105,后面两行是展开中的二次 panic(panic 之后进程 abort)。
最小复现
E14(Mutex 与 RwLock):主线程 read() 里再 read(),另一个线程 write()。
| 写法 | 结果 |
|---|---|
嵌套:第一个读守卫还活着时再 read() | 第 1 个交错「invalid internal loom state」,随后 abort |
不嵌套:两个先后独立的 read() 块(反向验证) | 9 个交错,通过 |
源码出处
读者集合是 HashSet<thread::Id>(rt/rwlock.rs:12-16)。同一线程第二次 read 只是重复插入,集合里还是一个元素。内层守卫 drop 时 release_read_lock 把这个线程删掉,集合空了,锁被置成 None;外层守卫 drop 时再进 release_read_lock,匹配不到 Locked::Read,落到 _ => panic!("invalid internal loom state")(105 行)。也就是说,loom 的读者集合按线程记,不按次数记,「同一线程持有两个读守卫」在这个表示里根本写不下。std 的 RwLock 文档只说这样做可能死锁或 panic,并不禁止。
另一面:loom 的读者只要没有写者持锁就能进,写者在等时新读者照样进(Mutex 与 RwLock 实测 9 个交错里 3 个)。真实世界里嵌套读的危险正好来自写者优先:parking_lot 与本机 std 在「同线程读两次、中间有写者排队」时都 3/3 卡死(parking_lot 卷 recursive-read)。loom 没有写者优先,就算不报内部错误,也模拟不出这个死锁。
怎么改
- 被测代码:不要在持有读守卫时调用可能再读同一把锁的函数,先把要的数据复制出来、放掉守卫。这本来就是真实环境里的正确做法。
- 模型:确实需要嵌套读的路径,loom 测不了;在模型里改成先后两次独立的读,嵌套读的死锁风险靠代码审查和 parking_lot 那种压力复现。
- 报错之后会 abort,这类测试分进程跑。
小结
- 同线程嵌套读:loom 第 1 个交错报「invalid internal loom state」(
rwlock.rs:105)后 abort,这是假阳性。 - loom 的
RwLock没有写者优先,嵌套读在真实实现上的死锁它也模拟不出,这是假阴性。 - E14 反向:改成不嵌套的两次读,9 个交错通过。
- 改法:读守卫不跨可能重入的调用;模型里不写嵌套读。
💬 关联讨论
发起讨论加载中...