Server::new(None):-c 配置文件不生效
主线位置:服务主线的第一步「
Server::new读选项和配置」。三个入口各用什么默认值、Opt有哪几个开关,见配置与命令行。本页讲最常见的那一种误会。
flowchart TB
new["Server::new"] --> o{"opt"}
o -->|None| c1["ServerConf new"]
o -->|"Opt default"| c2["opt override"]
o -->|"parse_args"| q{"有 -c"}
q -->|有| c3["读 YAML 文件"]
q -->|没有| c2
c1 --> def["全是默认值"]
c2 --> def
c3 --> file["文件里的值"]
实验 crate exp-pingora/pita-lab,bin svc:SVC_NEW=none 时 Server::new(None),SVC_NEW=default 时 Server::new(Some(Opt::default())),否则 Server::new(Some(Opt::parse_args()));启动后打印 server.configuration 的几个字段。M2 Max、macOS 27、rustc 1.99.0,debug,tokio 1.53.2。
现象
conf/app.yaml 写的是 threads: 4 和 grace_period_seconds: 5。三种写法,命令行完全相同:
$ SVC_NEW=none svc -c conf/app.yaml
[ 0.000s INFO svc] conf threads=1 work_stealing=true grace=None shutdown_timeout=None pid=43214
$ SVC_NEW=none svc -c /nonexistent.yaml --bogus
[ 0.001s INFO svc] conf threads=1 work_stealing=true grace=None shutdown_timeout=None pid=43257
$ SVC_NEW=default svc -c conf/app.yaml
[ 0.010s INFO svc] conf threads=1 work_stealing=true grace=None shutdown_timeout=None pid=74226
$ svc -c conf/app.yaml
[ 0.000s INFO svc] conf threads=4 work_stealing=true grace=Some(5) shutdown_timeout=None pid=43265
None时配置是默认值:1 个线程、宽限期None(也就是 300 秒)。连不存在的文件和不认识的--bogus都不报错,因为根本没人去读命令行。Some(Opt::default())也一样:Opt有了,但它的conf是None,不是从命令行来的。docs/user_guide/rate_limiter.md和pingora-proxy/examples/rate_limiter.rs用的就是这种写法。- 只有
Opt::parse_args()读到了threads: 4和grace=Some(5)。
这个坑的危险在于没有任何报错:服务照常起来,只是线程数、宽限期、max_retries、连接池大小都不是你写的那些。宽限期没读到时后果最明显,见 grace period 300s starts。
出处
Server::new 的签名是 new(opt: impl Into<Option<Opt>>),按「有没有 Opt」「Opt 里有没有 conf」分三路:None 走 ServerConf::new(),解析一段只有 version: 1 的 YAML,其余字段取默认;有 Opt 但没有 conf 走 new_with_opt_override;有 conf 才 load_yaml_with_opt_override 读文件。三段代码的原文在配置与命令行。
命令行只在 Opt::parse_args 里读,它就是 clap 的 Opt::parse()。函数的文档注释说得很清楚:「Command line options can either be passed by parsing the command line arguments via Opt::parse_args(), or be generated by other means」。docs/quick_start.md 前三个例子都是 Server::new(None),讲命令行的那一节才改成 Some(Opt::parse_args()),照着前面的例子写下来的服务,-c 就不生效。
本站网关也是 Server::new(None),所以它没法用 -c 改配置,也拿不到平滑升级要的 -u,见在本站中。
修法
要用 pingora 的命令行,就把它传进去:
let mut server = Server::new(Some(Opt::parse_args())).unwrap();
$ svc -c conf/app.yaml
[ 0.000s INFO svc] conf threads=4 work_stealing=true grace=Some(5) shutdown_timeout=None pid=43265
$ svc -c /nonexistent.yaml
thread 'main' (5867593) panicked at src/bin/svc.rs:46:42:
called `Result::unwrap()` on an `Err` value: Error { etype: ReadError, esource: Unset, retry: Decided(false), cause: Some(Os { code: 2, kind: NotFound, message: "No such file or directory" }), context: Some(Owned("Unable to read conf file from /nonexistent.yaml")) }
现在文件不存在会在启动时报 ReadError(panic 来自示例自己的 unwrap),写错参数 clap 会拒绝。
程序已经有自己的命令行(比如自己的 clap 结构体)时,Opt::parse_args 会和它抢 std::env::args。这时自己读配置,用 Server::new_with_opt_and_conf 直接传一份 ServerConf(比如 ServerConf::load_from_yaml(path) 读出来的);要转发 -u、-d 这些开关,就自己构造 Opt 一起传,或者把属于 pingora 的那几个参数挑出来交给 Opt::parse_from_args。
怎么确认没中招
grep -rn 'Server::new(' src/:参数是None或Opt::default(),就说明命令行没进来。- 启动后打印
server.configuration里关心的几个字段,像上面svc那样,和配置文件对一遍。 - 加
--help启动一次:传了Opt::parse_args()的svc打出-u、-d、-t、-c四个开关后以 0 退出;SVC_NEW=none的svc不理会它,打出Bootstrap starting照常启动。
小结
Server::new(None)和Server::new(Some(Opt::default()))都不读命令行,-c和任何参数都被静默忽略,配置全是默认值。- 只有
Opt::parse_args()会读-c;文件不存在、参数写错此时才会报错。 - 自己有命令行的程序,用
new_with_opt_and_conf把读好的ServerConf传进去。