gRPC Java 收到 GOAWAY 后报 Http2Error cannot be null,调用一直等到超时
gRPC Java 1.83.1 遇到未知 GOAWAY 错误码可能在错误转换时空引用。1.85.0 正式修复将未知码按 INTERNAL_ERROR 处理,同时保留 RPC deadline。
RPC 经过 Apache httpd 后偶发不结束,客户端日志出现:
java.lang.NullPointerException: Http2Error cannot be null for GOAWAY
同一时段代理可能记录发送 GOAWAY, err=70007。如果使用 gRPC Java 1.83.1,要检查它处理未知 HTTP/2 错误码的路径:把数字映射成 Http2Error 枚举时可能得到 null,随后构造断连错误又要求非空,导致客户端没有正常完成这次关闭处理。
这与服务端返回一个业务错误不同。日志来自连接监听器,RPC 还可能一直等到自己的 deadline 才失败。
对照连接事件与同一次调用
保留完整的 onGoAwayReceived、NettyClientHandler.goingAway、GoAwayDisconnectError 堆栈,关联代理的连接时间和错误码。不要只看到一条 NPE 就归因于业务返回 null,也不要把任何 GOAWAY 都当成这项缺陷。
HTTP/2 的错误码是数字,客户端不能假定每个收到的值都能映射到自己认识的枚举。问题出在未知码处理没有保持有效的传输关闭流程;即使代理的数字不常见,也应以正常错误状态结束,而不是在错误转换中再次抛出空引用。
已确认的环境包含 gRPC Java 1.83.1 与 Apache httpd 2.4.62。它不表示任意版本的 Apache 都会持续发送该码,也不证明所有 RPC 超时都经过这条路径。若客户端没有相同堆栈,继续检查实际 deadline、连接和服务端处理。
采用包含未知码处理的正式发行
gRPC Java 1.85.0 的正式说明列出这项修复:未知 GOAWAY 码按 INTERNAL_ERROR 处理,使 transport 能正常关闭。这并不把 RPC 改成成功,也不消除代理断连的原始原因;它恢复的是失败传播与连接清理。
通过工程已有 BOM 或依赖约束一起更新相关 gRPC 模块,确认最终打包的 netty 或 netty-shaded 实现确实来自选定版本。只有 Gradle 或 Maven 声明变了,应用仍加载旧 transport,错误会继续存在。
不要随意选择一个看起来更新的历史补丁号:1.84.2 曾因不合适的修正被后继发行替代。这里使用 1.85.0 作为已有正式证据的选择,实际项目仍需审查 Android/JDK、Netty 与其他模块兼容性。
同时保留合理的每次 RPC deadline。没有 deadline 时,一个异常关闭路径可能让等待更久;把 deadline 无限放大只会延迟暴露。反过来,把它缩到极短以便“快速报错”,也不能修复连接状态机,还可能让正常请求大量失败。
验证错误能够结束,重试另按业务决定
在受控代理或协议测试中发送未知错误码的 GOAWAY,观察客户端不再产生这条空引用,调用能按修复后的传输错误规则结束,后续连接和请求仍可使用。再覆盖标准错误码与正常调用,避免只测试一条异常日志消失。
如果失败请求允许重试,要结合方法幂等性和服务端实际处理记录决定,不能见到 GOAWAY 就把写请求无条件再发一次。部分请求可能已经到达后端,连接关闭不等于业务完全没执行。
部署前保存依赖与 transport 配置,回退时恢复一致组合。错误能够明确返回之后,再继续调查代理为什么关闭连接;不要通过屏蔽日志或忽略调用结果把原来的超时变成静默丢失。


