Go、向后兼容性和 GODEBUG

简介

Go 对向后兼容性的重视是其关键优势之一。但是,有时我们无法保持完全的兼容性。如果代码依赖于有错误的行为(包括不安全的行为),那么修复错误将破坏该代码。新功能也可能产生类似的影响:启用 HTTP 客户端的 HTTP/2 使用会破坏连接到具有错误 HTTP/2 实现的服务器的程序。这些类型的更改是不可避免的,并且Go 1 兼容性规则允许这样做。即便如此,Go 仍然提供了一种称为 GODEBUG 的机制来减少此类更改对使用较新工具链编译旧代码的 Go 开发人员的影响。

GODEBUG 设置是一个key=value对,用于控制 Go 程序某些部分的执行。环境变量GODEBUG可以包含这些设置的逗号分隔列表。例如,如果一个 Go 程序在包含以下内容的环境中运行:

GODEBUG=http2client=0,http2server=0

那么该 Go 程序将默认禁用 HTTP 客户端和 HTTP 服务器中的 HTTP/2 使用。也可以为给定程序设置默认的GODEBUG(如下所述)。

在准备任何 Go 1 兼容性允许但可能仍会破坏某些现有程序的更改时,我们首先设计更改以尽可能使更多现有程序继续工作。对于其余程序,我们定义一个新的 GODEBUG 设置,允许各个程序选择重新启用旧行为。如果这样做不可行,则可能不会添加 GODEBUG 设置,但这应该非常罕见。

为兼容性而添加的 GODEBUG 设置将至少维护两年(四个 Go 版本)。有些,例如http2clienthttp2server,将维护更长时间,甚至无限期维护。

如果可能,每个 GODEBUG 设置都有一个关联的runtime/metrics计数器,名为/godebug/non-default-behavior/<name>:events,它计算程序行为因该设置的非默认值而改变的次数。例如,当设置GODEBUG=http2client=0时,/godebug/non-default-behavior/http2client:events计算程序已配置为不具有 HTTP/2 支持的 HTTP 传输的数量。

默认 GODEBUG 值

当环境变量中未列出 GODEBUG 设置时,其值将从三个来源派生:用于构建程序的 Go 工具链的默认值,修改为与go.mod中列出的 Go 版本匹配,然后由程序中的显式//go:debug行覆盖。

GODEBUG 历史记录提供了每个 Go 工具链版本的精确默认值。例如,Go 1.21 引入了panicnil设置,用于控制是否允许panic(nil);它默认为panicnil=0,使panic(nil)成为运行时错误。使用panicnil=1恢复 Go 1.20 及更早版本的行为。

在编译声明较旧 Go 版本的工作模块或工作区时,Go 工具链会修改其默认值以尽可能匹配该较旧的 Go 版本。例如,当 Go 1.21 工具链编译程序时,如果工作模块的go.mod或工作区的go.work声明go 1.20,则程序默认为panicnil=1,匹配 Go 1.20 而不是 Go 1.21。

由于这种设置 GODEBUG 默认值的方法仅在 Go 1.21 中引入,因此列出 Go 1.20 之前版本的程序配置为匹配 Go 1.20,而不是较旧的版本。

要覆盖这些默认值,从 Go 1.23 开始,工作模块的go.mod或工作区的go.work可以列出以下一个或多个godebug行:

godebug (
    default=go1.21
    panicnil=1
    asynctimerchan=0
)

特殊键default指示要从中获取未指定设置的 Go 版本。这允许单独设置 GODEBUG 默认值,而不是模块中的 Go 语言版本。在此示例中,程序请求 Go 1.21 语义,然后请求旧的 Go 1.21 之前的panic(nil)行为和新的 Go 1.23 asynctimerchan=0行为。

仅工作模块的go.mod会被查询以获取godebug指令。忽略所需依赖项模块中的任何指令。列出具有无法识别的设置的godebug是错误的。(Go 1.23 之前的工具链会拒绝所有godebug行,因为它们根本不理解godebug。)

来自gogodebug行的默认值适用于所有构建的主包。为了进行更细粒度的控制,从 Go 1.21 开始,主包的源文件可以在文件的顶部(在package语句之前)包含一个或多个//go:debug指令。前面的示例中的godebug行将被写成:

//go:debug default=go1.21
//go:debug panicnil=1
//go:debug asynctimerchan=0

从 Go 1.21 开始,Go 工具链将把具有无法识别的 GODEBUG 设置的//go:debug指令视为无效程序。具有多个给定设置的//go:debug行的程序也被视为无效。(较旧的工具链完全忽略//go:debug指令。)

将编译到主包中的默认值由以下命令报告:

go list -f '{{.DefaultGODEBUG}}' my/main/package

仅报告与基本 Go 工具链默认值的差异。

在测试包时,*_test.go文件中的//go:debug行被视为测试主包的指令。在任何其他上下文中,工具链都会忽略//go:debug行;go vet会将此类行报告为放置错误。

GODEBUG 历史记录

本节记录了出于兼容性原因在每个主要 Go 版本中引入和删除的 GODEBUG 设置。包或程序可以为内部调试目的定义其他设置;例如,请参阅运行时文档go 命令文档

Go 1.23

Go 1.23 将包 time 创建的通道更改为无缓冲(同步),这使得正确使用Timer.StopTimer.Reset方法的结果变得容易得多。asynctimerchan设置禁用此更改。此更改没有运行时指标。此设置可能会在将来的版本中删除,最早在 Go 1.27 中。

Go 1.23 更改了os.Lstatos.Stat为重新解析点报告的模式位,可以使用winsymlink设置控制。从 Go 1.23(winsymlink=1)开始,挂载点不再设置os.ModeSymlink,并且不是符号链接、Unix 套接字或重复数据删除文件的重新解析点现在始终设置os.ModeIrregular。由于这些更改,filepath.EvalSymlinks不再评估挂载点,这是许多不一致和错误的来源。在以前的版本(winsymlink=0)中,挂载点被视为符号链接,并且其他具有非默认os.ModeType位(例如os.ModeDir)的重新解析点不设置ModeIrregular位。

Go 1.23 更改了os.Readlinkfilepath.EvalSymlinks以避免尝试将卷规范化为驱动器号,这并非总是可能的。此行为由winreadlinkvolume设置控制。对于 Go 1.23,它默认为winreadlinkvolume=1。以前的版本默认为winreadlinkvolume=0

Go 1.23 默认启用了实验性后量子密钥交换机制 X25519Kyber768Draft00。可以使用tlskyber设置恢复默认值。

Go 1.23 更改了crypto/x509.ParseCertificate的行为,以拒绝为负的序列号。可以使用x509negativeserial设置恢复此更改。

Go 1.23 默认重新启用了 html/template 中对 ECMAScript 6 模板字面量的支持。jstmpllitinterp设置不再有任何影响。

Go 1.23 更改了客户端和服务器在未明确配置时使用的默认 TLS 密码套件,删除了 3DES 密码套件。可以使用tls3des设置恢复默认值。

Go 1.23 更改了tls.X509KeyPairtls.LoadX509KeyPair的行为,以填充返回的tls.Certificate的 Leaf 字段。此行为由x509keypairleaf设置控制。对于 Go 1.23,它默认为x509keypairleaf=1。以前的版本默认为x509keypairleaf=0

Go 1.23 更改了net/http.ServeContentnet/http.ServeFilenet/http.ServeFS,以在提供错误时删除 Cache-Control、Content-Encoding、Etag 和 Last-Modified 标头。此行为由httpservecontentkeepheaders设置控制。使用httpservecontentkeepheaders=1恢复 Go 1.23 之前的行为。

Go 1.22

Go 1.22 添加了一个可配置的限制来控制在 TLS 握手期间可以使用的最大可接受 RSA 密钥大小,由tlsmaxrsasize设置控制。默认值为 tlsmaxrsasize=8192,将 RSA 限制为 8192 位密钥。为了避免拒绝服务攻击,此设置和默认值已回传到 Go 1.19.13、Go 1.20.8 和 Go 1.21.1。

Go 1.22 使 net/http 客户端或服务器读取的请求或响应具有空 Content-Length 标头成为错误。此行为由httplaxcontentlength设置控制。

Go 1.22 更改了 ServeMux 的行为,以接受扩展模式并按段对模式和请求路径进行转义。此行为可以通过httpmuxgo121设置控制。

Go 1.22 添加了 Alias 类型go/types 中,用于显式表示 类型别名。类型检查器是否生成 Alias 类型由 gotypesalias 设置 控制。对于 Go 1.22,它默认为 gotypesalias=0。对于 Go 1.23,gotypesalias=1 将成为默认值。此设置将在未来的版本中删除,最早在 Go 1.27 中删除。

Go 1.22 将服务器和客户端支持的默认最低 TLS 版本更改为 TLS 1.2。可以使用 tls10server 设置 将默认值恢复为 TLS 1.0。

Go 1.22 更改了客户端和服务器在未显式配置时使用的默认 TLS 密码套件,删除了使用基于 RSA 的密钥交换的密码套件。可以使用 tlsrsakex 设置 恢复默认值。

当连接既不支持 TLS 1.3 也不支持扩展主密钥(在 Go 1.21 中实现)时,Go 1.22 禁用了 ConnectionState.ExportKeyingMaterial。可以使用 tlsunsafeekm 设置 重新启用它。

Go 1.22 更改了运行时与 Linux 上的透明大页面的交互方式。特别是,常见的默认 Linux 内核配置会导致明显的内存开销,而 Go 1.22 不再绕过此默认设置。为了在不调整内核设置的情况下解决此问题,可以使用 disablethp 设置 禁用 Go 内存的透明大页面。此行为已回传到 Go 1.21.1,但该设置仅从 Go 1.21.6 开始可用。此设置可能会在将来的版本中删除,并且受此问题影响的用户应根据 GC 指南 中的建议调整其 Linux 配置,或切换到完全禁用透明大页面的 Linux 发行版。

Go 1.22 将运行时内部锁上的争用添加到 mutex 剖析 中。这些锁上的争用始终在 runtime._LostContendedRuntimeLock 处报告。可以使用 runtimecontentionstacks 设置 启用运行时锁的完整堆栈跟踪。这些堆栈跟踪具有非标准语义,有关详细信息,请参阅设置文档。

Go 1.22 添加了一个新的 crypto/x509.Certificate 字段,Policies,它支持具有大于 31 位的组件的证书策略 OID。默认情况下,此字段仅在解析期间使用,此时它会填充策略 OID,但在编组期间不使用。可以通过使用 x509usepolicies 设置 使用它来编组这些较大的 OID,而不是现有的 PolicyIdentifiers 字段。

Go 1.21

Go 1.21 使用空接口值调用 panic 成为运行时错误,由 panicnil 设置 控制。

Go 1.21 使 html/template 操作出现在 ECMAScript 6 模板字面量内成为错误,由 jstmpllitinterp 设置 控制。此行为已回传到 Go 1.19.8+ 和 Go 1.20.3+。

Go 1.21 引入了对 MIME 标头和多部分表单的最大数量的限制,分别由 multipartmaxheadersmultipartmaxparts 设置 控制。此行为已回传到 Go 1.19.8+ 和 Go 1.20.3+。

Go 1.21 添加了对多路径 TCP 的支持,但仅当应用程序显式请求时才使用。此行为可以通过 multipathtcp 设置 控制。

目前没有计划删除任何这些设置。

Go 1.20

Go 1.20 引入了对拒绝 tar 和 zip 档案中不安全路径的支持,由 tarinsecurepath 设置zipinsecurepath 设置 控制。它们默认为 tarinsecurepath=1zipinsecurepath=1,保留了早期 Go 版本的行为。Go 的未来版本可能会将默认值更改为 tarinsecurepath=0zipinsecurepath=0

Go 1.20 引入了 math/rand 全局随机数生成器的自动播种,由 randautoseed 设置 控制。

Go 1.20 引入了在证书验证期间使用的回退根的概念,由 x509usefallbackroots 设置 控制。

Go 1.20 从 Go 发行版中删除了标准库的预安装 .a 文件。安装现在像其他模块中的包一样构建和缓存标准库。 installgoroot 设置 恢复预安装 .a 文件的安装和使用。

目前没有计划删除任何这些设置。

Go 1.19

Go 1.19 使路径查找解析到当前目录中的二进制文件成为错误,由 execerrdot 设置 控制。目前没有计划删除此设置。

Go 1.19 开始在 DNS 请求上发送 EDNS0 附加标头。据报道,这可能会破坏某些路由器(例如 CenturyLink Zyxel C3000Z)上提供的 DNS 服务器。可以通过 netedns0 设置 更改此设置。此设置在 Go 1.21.12、Go 1.22.5、Go 1.23 及更高版本中可用。目前没有计划删除此设置。

Go 1.18

Go 1.18 删除了对大多数 X.509 证书中 SHA1 的支持,由 x509sha1 设置 控制。此设置将在未来的版本中删除,最早在 Go 1.22 中删除。

Go 1.10

Go 1.10 更改了构建缓存的工作方式并添加了测试缓存,以及 gocacheverifygocachehashgocachetest 设置。目前没有计划删除这些设置。

Go 1.6

Go 1.6 引入了对 HTTP/2 的透明支持,由 http2clienthttp2serverhttp2debug 设置 控制。目前没有计划删除这些设置。

Go 1.5

Go 1.5 引入了一个纯 Go DNS 解析器,由 netdns 设置 控制。目前没有计划删除此设置。