Rust in Everything
基于 loom 0.7.2

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 另写一份。

💬 关联讨论

发起讨论
加载中...