cannot access Loom execution state from outside a Loom model
主线位置:主线 1 的起点。loom 的执行状态只在
Scheduler::tick期间存在(协程调度器),每个 loom 对象又只是当轮对象仓库里的一个下标(Execution 与对象仓库)。在这之外碰 loom 类型,有两种报错。
flowchart LR
subgraph out["model 外"]
s1["测试 setup 里 Arc::new / AtomicUsize::new"]
s2["static 里的原子(编译期 E0015)"]
s3["依赖写裸 cfg(loom),在 model 外建队列"]
end
subgraph cross["跨轮"]
c1["loom 对象藏进 std static / OnceLock"]
end
out --> e1["cannot access Loom execution state ..."]
cross --> e2["failed to get object for ref Ref<...>(0)"]
现象
测试一启动就失败,一个交错都没跑:
thread 'outside_setup' panicked at …/loom-0.7.2/src/rt/scheduler.rs:128:13:
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`)?
最小复现
exp-loom/pitfalls/tests/pit.rs,每个场景一个 #[test]、单独进程运行:
let shared = Arc::new(AtomicUsize::new(0)); // loom::sync::Arc,在 model 外
loom::model(move || {
shared.fetch_add(1, Relaxed);
});
| 场景 | 结果 |
|---|---|
outside_setup:上面这段 | 0 个交错,报上面那句 |
outside_setup_fixed:同样的对象挪到闭包里建,主线程和一个子线程各加一 | 4 个交错,通过 |
object_across_rounds:第 0 轮在 std OnceLock 里存一个 Box::leak 出来的 loom 原子,之后每轮取出来用 | 第 2 个交错报「failed to get object for ref Ref<loom::rt::atomic::State>(0)」 |
源码出处
第一种来自 Scheduler::with_state:STATE 是 scoped_thread_local!,只在 tick 里 STATE.set 的作用域内有值,其余时间一律 panic。构造也要经过它:AtomicUsize::new 要往当轮的对象仓库里插一项,Arc::new 要登记计数对象。所以 loom 类型没有 const fn new,static FOO: AtomicUsize = AtomicUsize::new(0); 在 --cfg loom 下直接是编译错误 E0015(cfg(loom) 切换层)。
第二种来自 Ref::set_action 的断言。每轮开始对象仓库清空、下标从 0 重新分配;上一轮留下的对象只剩一个下标,这一轮那个位置要么没有对象、要么是别的类型的对象。本例第 2 轮里下标 0 已经是别的东西,报的就是它。这一种比第一种难查:报错不提「跨轮」,而且只在第二轮才出现。
常见来源
- 测试 setup:在
model外建好共享对象再move进闭包。改成在闭包里建,每轮一份。 - 被测库的
static/const fn:给 loom 另写一份,或用loom::lazy_static!(每轮重新初始化,线程、线程局部与 lazy_static)。tokio 的任务 id 计数器、crossbeam-epoch 的默认收集器都是这么做的;bytes 为此另写了非const的Bytes::new。 - 依赖被一起换掉:concurrent-queue、event-listener、parking 这类写裸
cfg(loom)的库,下游一开--cfg loom就变成 loom 类型(cfg(loom) 切换层 的对照表)。测试 setup 里建一个它们的队列,就是这句报错。 - 缓存:用 std 的
OnceLock、static把 loom 对象(或指向它的指针)留到下一轮,报第二种错误。
怎么改
- 所有 loom 对象都在
loom::model的闭包里新建;需要「全局」的东西用loom::lazy_static!。 - 被测库里的
static原子、const fn new,在切换层里给 loom 一份非const的版本。 - 依赖被一起换掉时,要么把用到它的那段也放进模型,要么让依赖用自己的 cfg 名字(crossbeam 用
crossbeam_loom,下游开--cfg loom不影响它)。
小结
- loom 的执行状态只在
tick期间存在;model 外构造或使用 loom 类型,panic 在scheduler.rs:128,0 个交错。 - 构造也要进执行状态,所以 loom 原子不能
const构造,static里写会编译失败(E0015)。 - 把 loom 对象留到下一轮,第 2 个交错报「failed to get object for ref」,来自
object.rs的断言。 - 改法:对象都在闭包里建;全局量用
loom::lazy_static!;const fn给 loom 另写一份。
💬 关联讨论
发起讨论加载中...