Worker threads cannot be set to 0 / assertion failed: threads != 0
主线位置:服务主线的「
Server::new读配置」和「run_forever给每个服务建运行时」。ServerConf各字段的默认值和读它的代码见配置与命令行,两种运行时见运行时。本页讲threads: 0一路被放行到哪里才出错。
flowchart TB
yaml["threads 0"] --> parse["from_yaml"]
parse --> val["validate 不查"]
val --> test{"带 -t"}
test -->|是| pass["Server Test passed"]
test -->|否| boot["bootstrap"]
boot --> rt["create_runtime"]
rt --> ws{"work_stealing"}
ws -->|true| p1["tokio Builder panic"]
ws -->|false| p2["NoSteal assert"]
实验 crate exp-pingora/pita-lab,bin svc:一个 ServeHttp 服务,Server::new(Some(Opt::parse_args()));M2 Max、macOS 27、rustc 1.99.0,debug,tokio 1.53.2。
现象
两份配置,conf/t0.yaml 是 threads: 0,conf/t0ns.yaml 再加 work_stealing: false:
== t0
[ 0.011s INFO svc] conf threads=0 work_stealing=true grace=None shutdown_timeout=None pid=41536
[ 0.011s INFO pingora_core::server::bootstrap_services] Bootstrap starting
[ 0.011s INFO pingora_core::server::bootstrap_services] Bootstrap done
[ 0.012s INFO pingora_core::server] Server starting
[ 0.012s INFO pingora_core::server] Starting services in dependency order: ["lab"]
[ 0.012s INFO pingora_core::server] Starting service: lab
thread 'main' (5859152) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pingora-runtime-0.9.0/src/lib.rs:471:14:
Worker threads cannot be set to 0
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
exit=101
== t0ns
...
thread 'main' (5859156) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pingora-runtime-0.9.0/src/lib.rs:611:9:
assertion failed: threads != 0
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
exit=101
- 配置读进来了(第一行打出
threads=0),Bootstrap done,服务也开始启动,panic 在建运行时的那一刻。 - 同一个配置错误,两种运行时报的是两句不相干的话:Steal 的那句来自 tokio 的
Builder::worker_threads,NoSteal 的是 pingora 自己的assert!。搜日志时要两句都搜。 - 用
-t检查这份配置:
[ 0.000s INFO pingora_core::server::bootstrap_services] Bootstrap starting
[ 0.000s INFO pingora_core::server::bootstrap_services] Server Test passed, exiting
exit=0
-t 在 bootstrap 里就退出了,走不到建运行时那一步,所以部署前用 -t 做的配置检查放过了它。
出处
ServerConf::from_yaml 解析完调 validate,它只查 max_blocking_threads 和两个运行时直方图参数,不看 threads:
pub fn validate(self) -> Result<Self> {
if self.max_blocking_threads == Some(0) {
return Error::e_explain(ReadError, "max_blocking_threads must be greater than zero");
}
// ... two histogram checks ...
Ok(self)
}
threads 一直走到 run 里:每个服务取 service.threads().unwrap_or(conf.threads),交给 run_service → create_runtime。Steal 走 build_work_stealing_tokio_builder,第 471 行 .worker_threads(self.threads) 由 tokio 断言;NoSteal 走 NoStealRuntime::new,文档注释写着「Panic if threads is 0」,第 611 行 assert!(threads != 0)。一个服务自己用 threads() 返回 Some(0) 也是同一条路。0.9.0 之后的 main(84f21ee)上 validate 和这两处都没变。
validate 在 0.1.0 到 0.8.0 一直是个空函数0.1.0 的 validate 只有两行:// TODO: do the validation 和 Ok(self),0.8.0 还是这样。b994854(2026-03-16,「Add config for tokio blocking pool options」,没有 PR 编号,首次进入 0.9.0)给 ServerConf 加了 max_blocking_threads 和 blocking_threads_ttl_seconds,顺手把那行 TODO 换成了第一条检查:max_blocking_threads == Some(0) 返回 ReadError。也就是说,0.9.0 里 validate 拦下的只是这个提交新加的字段,从 0.1.0 就有的 threads 仍然没人检查;它的 0 会一直传到 tokio 或 NoStealRuntime 里。
修法
pingora 不查,就在 bootstrap 之前自己查,配置错误时干净地退出(svc 的 SVC_CHECK=1):
if server.configuration.threads == 0 {
eprintln!("config error: threads must be at least 1");
std::process::exit(2);
}
server.bootstrap();
[ 0.011s INFO svc] conf threads=0 work_stealing=true grace=None shutdown_timeout=None pid=42090
config error: threads must be at least 1
exit=2
放在 bootstrap 之前,-t 也会经过它,配置检查就能拦下这个值。改成 threads: 2 照常启动。
怎么确认没中招
- 配置里的
threads如果来自模板或环境变量替换,空值、0都要防:grep -n 'threads' conf/*.yaml,再看每个服务有没有覆盖threads()。 - 不要只靠
-t:它只证明Server::new能读通配置、bootstrap之前的代码能跑。启动后真的发一个请求,才知道运行时建起来了。 - 日志里
Worker threads cannot be set to 0(Steal)和assertion failed: threads != 0(NoSteal)是同一个原因。
小结
ServerConf::validate不查threads;threads: 0能解析、能过-t,到run给服务建运行时时主线程 panic,进程以 101 退出。- Steal 报 tokio 的
Worker threads cannot be set to 0(pingora-runtimelib.rs:471),NoSteal 报assertion failed: threads != 0(lib.rs:611)。 validate到 0.9.0 才有第一条检查(b994854),查的是新字段max_blocking_threads。在bootstrap之前自己检查threads。