Once instance has previously been poisoned
主线位置:主线 1 的骨架用在
Once上。执行初始化闭包的线程持有LOCKED_BIT;闭包 panic 时PanicGuard::drop把状态换成POISON_BIT并unpark_all,所有等待者醒来看到毒化(Once)。
sequenceDiagram participant I as 初始化线程 participant O as Once 状态 participant W as 4 个等待者 I->>O: 抢到 LOCKED_BIT,执行闭包 W->>O: call_once:LOCKED,停车 I->>I: 闭包 panic I->>O: PanicGuard:swap(POISON_BIT),unpark_all W->>O: 醒来读到 POISON_BIT W->>W: 全部 panic「Once instance has previously been poisoned」 Note over O: 之后每次 call_once 都 panic,call_once_force 才能重试
报错原文
E10 ③(exp-parking_lot/once,不开 deadlock_detection),闭包 panic 后再调用:
pl first call_once: Some("init failed"); state now Poisoned
pl second call_once: Some("Once instance has previously been poisoned"); state now Poisoned
pl call_once_force closure sees Poisoned (poisoned() = true)
pl after call_once_force: state Done
std Once first: Some("init failed"); is_completed false
std Once second: Some("Once instance has previously been poisoned")
std call_once_force closure sees is_poisoned() = true
OnceLock first get_or_init: Some("init failed"); get() = None
OnceLock second get_or_init: Ok(7)
LazyLock first deref: Some("init failed")
LazyLock second deref: Some("LazyLock instance has previously been poisoned")
E10 ④,等待中毒化:一个线程在闭包里睡 100 ms 后 panic,期间另外 4 个线程在同一个 Once 上等。parking_lot 的 4 个等待者全部 panic,报错同上;OnceLock 的 4 个等待者里有一个重新执行了初始化,4 个都拿到了同一个值。
一次初始化失败,可能连带四个线程 panic。
源码出处
// If the state has been poisoned and we aren't forcing, then panic
if state & POISON_BIT != 0 && !ignore_poison {
// Need the fence here as well for the same reason
fence(Ordering::Acquire);
panic!("Once instance has previously been poisoned");
}
毒化发生在 PanicGuard::drop:swap(POISON_BIT, Release),旧值有 PARKED_BIT 就 unpark_all。等待者醒来不看令牌,回到循环开头读状态,读到 POISON_BIT 就走上面那个分支。
std 的 Once 用的是同一句话(once/queue.rs:167);OnceLock 内部用 call_once_force,所以不会卡在毒化上(sync/once_lock.rs:562-580)。
这是 parking_lot 里唯一有毒化的原语,Mutex / RwLock 都没有(持锁时 panic)。
怎么改
- 初始化可能失败、希望下次重试:改用 std
OnceLock::get_or_init:闭包 panic 后不毒化,下一次调用重新执行初始化。OnceLock稳定以后,新代码里需要 parking_lotOnce的理由只剩 1 字节的大小。 - 要继续用
Once:用call_once_force(|state| ..),闭包里state.poisoned()为真时做清理再重试。等待者也要用call_once_force,否则照样 panic。 - 初始化闭包里别 panic:把可能失败的部分(读文件、解析配置)放到闭包外,失败时返回错误,闭包里只做不会失败的赋值。
小结
- 初始化闭包 panic 后,parking_lot
Once毒化,之后每次call_once都 panic「Once instance has previously been poisoned」,与 stdOnce一字不差。 - 正在等的线程也一起 panic:E10 中 4 个等待者全部 panic。
- std
OnceLock不毒化、下次重试;LazyLock毒化。 - 要重试就用
OnceLock或call_once_force。
💬 关联讨论
发起讨论加载中...