likes
comments
collection
share

Go 负责人 rsc 翻车,决定追加 godebug 行

作者站长头像
站长
· 阅读数 3

大家好,我是煎鱼。

上年我给大家分享过《加大力度!Go 将会增强 Go1 向后兼容性》,当时是 Go 核心团队负责人 @Russ Cox(下称:rsc)主导和推进的。

没想到,那么快就发现新的坑。为此 rsc 光速推进了一个新的提案《cmd/go: separate default GODEBUGs from go language version》,现在已经是接受状态了。

Go 负责人 rsc 翻车,决定追加 godebug 行

今天就由本煎鱼为大家分享和说明具体的内容和缘由。

快速背景

当时提案的核心目的是:为了加强 Go 向后兼容性,具体的行为是:根据 go.mod 中的 Go 版本号来设置对应 GODEBUG,以提供超越当前兼容性准则所保证的兼容性。

Go 负责人 rsc 翻车,决定追加 godebug 行

使用案例来看,跟现有的 GODEBUG 其实是类似。例如 Go1.20 时引入了一个新的 GODEBUG zipinsecurepath。

该值会遵循一定的流程规范,当时官方给出的例子如下:

  • Go1.20 中默认值为 1,以保留旧的行为并允许不安全的路径。
  • Go1.21 会将默认值更改为 0,以开始拒绝 archive/zip 中的不安全路径。如果是这样,且 Go1.21 也实现了这个 GODEBUG 提案,那么当使用 Go1.21 编译的带有 Go1.20 的模块(go.mod)时,将继续允许不安全的路径。只有当这些模块版本更新到 Go1.21 时,它们才会开始拒绝不安全的路径。

以上是当时定的提案方向和实施的结论。看着很美好,不过咱们程序员写代码,总会有一些意料之外的场景导致新 “坑” 出现。

兼容规则小结

我们对现在 Go 处理兼容性的方式做一下规则总结,如下:

  1. 定义了名为 GODEBUG 设置的配置方式,让用户可以控制这些更改是否或何时在其特定程序中发生。
    1. 默认设置取自主软件包 go.mod 文件中的 "go" 版本行。
    2. 主软件包 Go 源文件可以用 //go:debug key=value 行做覆盖。
  2. 对于 Go Toolchain(工具链),决定当前工具链版本是否足够新以此满足构建模块的要求时,需要检查顶级的 “go” 行,以其为准。
  3. 任何模块中的 "go" 行必须是其依赖关系中所有 "go" 行的最大值。

翻车在哪

仅仅经过了 1.5 个大版本,Go 社区就发现了新的问题。这几个方面的机制产生了冲突,以一种令人遗憾的方式相互影响。

具体场景如下:

1、某个特定程序的维护版本 M 希望锁定从他们开始使用的发布版中的 GODEBUG 语义,他们可以通过设置“go”行来实现,即使在更新到更新的工具链时也可以。

2、但是当他们更新到依赖项的更新版本时,如果这些依赖项已经更新到更新的 “go” 行,那么这将迫使在顶层模块中使用更新的 “go” 行,从而更改了默认的 GODEBUG 设置。

3、如果这种情况发生在一个依赖项上,它可以被 fork+replace。但是对于具有庞大依赖树的模块项目,这可能会发生在许多依赖项上,此时 fork+replace 就不再具备可扩展性。

解决方案

变更方向

Go 官方将计划做两处修改,以便将原先默认的 GODEBUGgo.mod 中的 "go" 行分离,解决上述的兼容规则冲突的问题。

  • 添加一个新的基础设置:default=go1.X,作用和含义是:"按照 Go 1.X 的方式设置一切",与 "go 1.X" 行的意思相同。
    • 对于这个新的基础设置而言,go.mod 文件中的 "go 1.X" 行实质上意味着每个主程序包中的 //go:debug default=go1.X
    • 该设置也可以在 $GODEBUG 中使用。
    • 你可以有一个写着 "go 1.23" 的模块,但主程序却写着 //go:debug default=go1.21
  • go.modgo.work 中添加一行新的 godebug,仅在当前模块中使用(就像 toolchain、replace 和 exclude 仅在当前模块中使用一样)。它需要一个 k=v 参数,与 //go:debug 源代码行相同,但适用于模块中的所有软件包 main。

对我们影响最大的核心点之一是:go.mod 文件中的 go 行不再和 GODEBUG 强绑定,拆了一个新的 godebug 语义和用法出来,两者独立开来,不再在兼容性机制上重叠。

具体例子

基于上述的变更说明,godebug default 的案例,代码如下:

go 1.23
godebug default=go1.21

也可以设置多种 godebug,go.mod 或 go.work 均是一样的表达方式。

代码如下:

go 1.23

godebug (
	default=go1.21
	panicnil=0
)

优先顺序如下:运行时的 $GODEBUG > 软件包 main 中的 //go:debug 行 > go.workgo.mod(使用工作区时为 go.work,否则为 go.mod)

一些瓜

现阶段这个提案在 rsc 的高度关注下,快速穿越过各个提案阶段,已经 Accepted。根据我的观察,本次问题,大概率是 Kubernetes 团队在 Google 发现和在内部提出的。

因为 @Jordan Liggitt 光速出现在了评论区,也符合具有庞大依赖树的模块项目的特征,同时也强关注了本提案。

Go 负责人 rsc 翻车,决定追加 godebug 行

果然,无论是哪里。对于大客户和自家大佬的问题和进度都是特别关注和高资源推进的。(有内部渠道光速响应)

总结

本提案确实解决了前一轮 Go1 兼容性保障增强带来的副作用,共用预定义的泛化内容总是会带来一些奇奇怪怪的问题。本次拆开也便于直观上的理解。

也可以满足使用更新的 Go 版本,且同时 GODEBUG 语义使用更低版本的诉求,这我觉得是不错的。这样未来 Go 新版本好的优化也可以享受到。

根据提案内容和计划,预计本次变更最快可以在 Go1.23 享受到。大家可以期待一下!

文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blo… 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。

推荐阅读