Rust in Everything
基于 pingora-core 0.9.0

error[E0252]: the name tls is defined multiple times

主线位置:请求主线里 TLS 握手那一步用哪套代码,是编译期由 feature 选的。pingora-core 怎么用 cfg 把某个后端改名成 tls、any_tls 和 openssl_derived 两个内部 feature 干什么,见怎么读这一卷和 TLS。本页讲两个后端同时打开时的报错,以及它常见的来路。

flowchart TB
  app["应用 openssl"] --> uni["feature 统一"]
  dep["依赖 rustls"] --> uni
  uni --> core["pingora-core"]
  core --> a["pub use openssl as tls"]
  core --> b["pub use rustls as tls"]
  a --> e1["E0252"]
  b --> e1
  core --> glob["tls 模块 glob 导出"]
  glob --> e2["E0659 等 74 个"]

实验 crate exp-pingora/pita-twotls(报错)和 exp-pingora/pita-onetls(修好的);M2 Max、macOS 27、rustc 1.99.0,debug。

现象

最直接的写法是在 pingora 的 feature 里同时写 openssl 和 rustls。更常见、也更难发现的是两处各开一个:应用通过门面 crate 要 openssl,另一行依赖(或者某个间接依赖)直接对 pingora-core 要 rustls:

[dependencies]
pingora = { version = "=0.9.0", features = ["openssl", "proxy"] }
pingora-core = { version = "=0.9.0", features = ["rustls"] }

src/main.rs 只有一个空的 fn main() {},cargo build:

error[E0252]: the name `tls` is defined multiple times
   --> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pingora-core-0.9.0/src/lib.rs:117:9
    |
114 | pub use pingora_openssl as tls;
    |         ---------------------- previous import of the module `tls` here
...
117 | pub use pingora_rustls as tls;
    |         ^^^^^^^^^^^^^^^^^^^^^ `tls` reimported here
    |
    = note: `tls` must be defined only once in the type namespace of this module
help: you can use `as` to change the binding name of the import
    |
117 | pub use pingora_rustls as other_tls;
...
error[E0659]: `Acceptor` is ambiguous
  --> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pingora-core-0.9.0/src/listeners/mod.rs:87:11
   |
87 | use tls::{Acceptor, TlsSettings};
...
error: could not compile `pingora-core` (lib) due to 75 previous errors
  • 75 个错误全在 pingora-core 里,没有一个指向用户的代码。按错误码数:E0659(is ambiguous)55 个、E0308 12 个、E0560 4 个,E0252、E0061、E0277、E0609 各 1 个。
  • rustc 的 help 建议把第二个改名成 other_tls。那一行在 cargo 缓存里的依赖源码中,改不了,也不该改。
  • 用户代码一行都没写,这个错误和「代码怎么写」无关,只和依赖图里打开了哪些 feature 有关。

出处

pingora-core/src/lib.rs 的这几行是并列的,没有互斥条件:

// If both openssl and boringssl are enabled, prefer boringssl.
// This is to make sure that boringssl can override the default openssl feature
// when this crate is used indirectly by other crates.
#[cfg(feature = "boringssl")]
pub use pingora_boringssl as tls;

#[cfg(feature = "openssl")]
pub use pingora_openssl as tls;

#[cfg(feature = "rustls")]
pub use pingora_rustls as tls;

#[cfg(feature = "s2n")]
pub use pingora_s2n as tls;

#[cfg(not(feature = "any_tls"))]
pub use protocols::tls::noop_tls as tls;

任意两个后端同时打开,就有两行 pub use ... as tls 生效,这是 E0252。protocols/tls/mod.rs、listeners/tls/mod.rs 再按 feature 把各后端的子模块 pub use ...::* 进来,同名的 Acceptor、TlsSettings 互相遮挡,这是那 55 个 E0659。

Cargo 的 feature 是按包统一的:整个依赖图里只编译一份 pingora-core,它打开的 feature 是所有依赖方要求的并集。所以「应用要 openssl、某个库要 rustls」和「应用自己写了两个」结果一样。注释里说的「prefer boringssl」在 0.9.0 的代码里不存在,同时开 openssl 和 boringssl 也会撞上第 111 行和第 114 行。

📝NOTE
冷知识:那段「prefer boringssl」的注释在 0.4.0 之前是真的

0.1.0 里 openssl 那一行写的是 #[cfg(all(not(feature = "boringssl"), feature = "openssl"))]:boringssl 打开时 openssl 那行不生效,注释说的「boringssl can override the default openssl feature」就是这样实现的。b939776(2024-10-11,「Refactor openssl code one more time before rustls integration」,没有 PR 编号,首次进入 0.4.0)为接入 rustls 重排这几行时,把它改成了 #[cfg(feature = "openssl")],加上 rustls 那一行,dummy_tls / some_tls 也改名成今天的 noop_tls / any_tls。互斥条件从那时起就没了,注释原样留到了 0.9.0。

修法

整个依赖图里只留一个 TLS 后端。pita-onetls 删掉直接对 pingora-core 要 rustls 的那一行,pingora-core 只通过 pingora 进来:

[dependencies]
pingora = { version = "=0.9.0", features = ["openssl", "proxy"] }
pingora-core = "=0.9.0"
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.58s

如果第二个后端来自一个间接依赖,就要找到它并关掉那个 feature(多数库会把 TLS 后端做成可选的 feature,default-features = false 再挑一个);找不到办法关的话,只能让应用跟着它用同一个后端。

怎么确认没中招

看 pingora-core 最终打开了哪些 feature:

$ cargo tree -i pingora-core -f '{p} [{f}]' --depth 0
pingora-core v0.9.0 [any_tls,default,openssl,openssl_derived,ouroboros,pingora-openssl,pingora-rustls,rustls]

这是 pita-twotls 的输出,openssl 和 rustls 同时在里面。pita-onetls 是 [any_tls,default,openssl,openssl_derived,pingora-openssl]。方括号里 openssl、boringssl、rustls、s2n 应当正好出现一个;一个都没有是另一个坑,见 No tls feature was specified。

要知道是谁打开的,用 cargo tree -e features -i pingora-core,找 pingora-core feature "rustls" 下面那一行:

│   └── pingora-core feature "rustls"
│       └── pita-twotls v0.1.0 (~/exp-pingora/pita-twotls) (*)

小结

  • pingora-core 用并列的 pub use <后端> as tls 选 TLS,没有互斥;开两个后端是 E0252 加一串 E0659,共 75 个错误,全在依赖里。
  • feature 在整个依赖图里统一,应用和某个依赖各开一个也算两个。
  • 「prefer boringssl」那段注释从 0.4.0(b939776)起就不成立。
  • 用 cargo tree -i pingora-core -f '{p} [{f}]' --depth 0 确认只剩一个后端,用 -e features 找是谁开的。

💬 关联讨论

发起讨论
加载中...