no field mean_poll_duration on type RuntimeMetrics
主线位置:运行时主线的读取端。
RuntimeIntervals::next()返回的RuntimeMetrics有几个字段,取决于编译时有没有--cfg tokio_unstable。
flowchart TB
code["读 m.mean_poll_duration"] --> cfg{"编译时带 --cfg tokio_unstable?"}
cfg -->|带| ok["41 个字段,编译通过"]
cfg -->|不带| gone["只有 10 个稳定字段"]
gone --> err["E0609:no field"]
docs["docs.rs:带 cfg 构建"] --> see["文档里看得到这个字段"]
see --> code
env["RUSTFLAGS 环境变量"] -->|设了就覆盖| conf[".cargo/config.toml 的 rustflags"]
conf --> cfg
复现
use tokio_metrics::RuntimeMonitor;
#[tokio::main]
async fn main() {
let monitor = RuntimeMonitor::new(&tokio::runtime::Handle::current());
let m = monitor.intervals().next().unwrap();
println!("{:?}", m.mean_poll_duration);
}
不带任何 rustflags 编译:
error[E0609]: no field `mean_poll_duration` on type `tokio_metrics::RuntimeMetrics`
--> src/bin/e0609.rs:7:22
|
7 | println!("{:?}", m.mean_poll_duration);
| ^^^^^^^^^^^^^^^^^^ unknown field
|
help: a field with a similar name exists
|
7 - println!("{:?}", m.mean_poll_duration);
7 + println!("{:?}", m.max_busy_duration);
|
同一份代码加上 RUSTFLAGS="--cfg tokio_unstable" 就能编译,打印 0ns(刚开始采样,区间里没有 poll)。
编译器的建议要当心:max_busy_duration 只是名字像,量的是「区间里最忙的那个 worker 忙了多久」,和「每次 poll 平均多久」毫无关系。照着建议改,代码能编译,看板上的数字是错的。
为什么:字段在宏里被 cfg 删掉
RuntimeMetrics 不是手写的结构体,是 define_runtime_metrics! 宏生成的。宏把字段分成 stable 和 unstable 两组,后一组的每个字段都加上 #[cfg(tokio_unstable)]:
pub struct RuntimeMetrics {
$(
$(#[$($attributes)*])*
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
$vis $name: $ty,
)*
$(
$(#[$($unstable_attributes)*])*
#[cfg(tokio_unstable)]
#[cfg_attr(docsrs, doc(cfg(all(feature = "rt", tokio_unstable))))]
$unstable_vis $unstable_name: $unstable_ty,
)*
}
mean_poll_duration 在 unstable 那一组。不带 cfg 时它不是「值为 0」,而是不存在。稳定的 10 个、不稳定的 31 个分别是哪些,见稳定与不稳定两层字段。
文档里为什么看得到?docs.rs 构建时传了 --cfg tokio_unstable(Cargo.toml 第 60 行,怎么读这一卷里贴过原文)。上面宏里的 doc(cfg(all(feature = "rt", tokio_unstable))) 会在文档上给这些字段打一个「需要 rt 和 tokio_unstable」的标注,但它很小,从示例代码复制过来时容易忽略。字段自己的文档示例也用 # #[cfg(tokio_unstable)] 包着,隐藏行在页面上看不见。
修法一:不需要它,就用 cfg 包住读它的代码
大多数程序只想要稳定字段。读不稳定字段的代码跟着同一个 cfg 走,两种构建都能编译:
use tokio_metrics::RuntimeMonitor;
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
let monitor = RuntimeMonitor::new(&tokio::runtime::Handle::current());
let mut iv = monitor.intervals();
tokio::spawn(async {}).await.unwrap();
let m = iv.next().unwrap();
println!("cfg!(tokio_unstable) = {}", cfg!(tokio_unstable));
println!("stable: workers={} live_tasks={}", m.workers_count, m.live_tasks_count);
// 不稳定字段只在带 --cfg tokio_unstable 编译时存在,读它的代码也得跟着 cfg
#[cfg(tokio_unstable)]
println!("unstable: polls={} mean_poll={:?}", m.total_polls_count, m.mean_poll_duration);
}
# 不带 cfg
cfg!(tokio_unstable) = false
stable: workers=2 live_tasks=0
# RUSTFLAGS="--cfg tokio_unstable"
cfg!(tokio_unstable) = true
stable: workers=2 live_tasks=0
unstable: polls=1 mean_poll=3.617µs
自己的 crate 里写 cfg(tokio_unstable) 时,rustc 会对未登记的 cfg 名报 unexpected_cfgs 警告。在 Cargo.toml 里登记一下,和 tokio-metrics 自己的做法一样:
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] }
修法二:需要它,就打开 cfg
tokio_unstable 是传给 rustc 的 --cfg,不是 feature,只能由构建最终程序的人设:写在项目的 .cargo/config.toml 里,或者用 RUSTFLAGS 环境变量。两者不是相加,而是环境变量一旦设了就整个替换掉配置文件。一个只打印 cfg!(tokio_unstable) 的程序,.cargo/config.toml 里写着:
[build]
rustflags = ["--cfg", "tokio_unstable"]
cargo 1.99.0 下各种组合:
| 配置文件 | 环境变量 | cfg!(tokio_unstable) |
|---|---|---|
[build] rustflags | 不设 | true |
[build] rustflags | RUSTFLAGS="-C debuginfo=0" | false |
[build] rustflags | RUSTFLAGS="" | false |
[build] rustflags | CARGO_ENCODED_RUSTFLAGS="" | false |
[target.aarch64-apple-darwin] rustflags | 不设 | true |
[target.aarch64-apple-darwin] rustflags | RUSTFLAGS="-C debuginfo=0" | false |
空串也算「设了」。CI 或 Dockerfile 里为了别的目的加一句 RUSTFLAGS=...,配置文件里的 cfg 就悄悄没了,接着是本页的 E0609;如果读字段的代码像修法一那样用 cfg 包着,则连错都不报,只是那几行日志不见了。所以要打开 cfg 时,在程序启动时把 cfg!(tokio_unstable) 打一行日志,比相信配置文件可靠。
打开之后还有三件事:rustflags 一变,整个依赖树都要重新编译;tokio 的 unstable API 不在 semver 承诺之内;用 dx 构建全栈应用时,flags 会不会传进 wasm 客户端取决于写在哪一节。这几件事放在在本站中实测的「打开 tokio_unstable 要付什么」一节,用本站的构建方式逐个核实。
小结
RuntimeMetrics的 31 个不稳定字段由宏加上#[cfg(tokio_unstable)],不带 cfg 时不存在,读它就是E0609。- 编译器会建议一个名字相近的稳定字段(
max_busy_duration),意思完全不同,别照改。 - docs.rs 带 cfg 构建,文档上看得到这些字段,标注在字段旁边。
- 不需要不稳定字段:读它的代码用
#[cfg(tokio_unstable)]包住,并在[lints.rust]里登记这个 cfg。 - 需要它:
.cargo/config.toml或RUSTFLAGS二选一;RUSTFLAGS哪怕是空串也会覆盖配置文件,启动时打印cfg!(tokio_unstable)确认。