Rust in Everything
基于 pingora-core 0.9.0

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 传进去。

💬 关联讨论

发起讨论
加载中...