1.记一次ECONNRESET的源码问题排查
记一次ECONNRESET的问题排查
最近在云构建任务中发现,实时日志上传OSS时会触发ECONNRESET错误,修改此现象仅在特定任务中出现。不启构建任务由多个步骤组成,动排每个步骤执行完毕后,源码下一个步骤才会启动,修改怎样保护源码直至任务结束。不启当某个步骤执行时间较长时,动排ECONNRESET错误就会出现。源码
为了复现此问题,修改编写了一段测试代码。不启每个步骤都会输出日志,动排为了提高效率,源码利用stream特性,修改日志一产出即执行上传。不启为了模拟耗时步骤,设置秒间隔,表示步骤执行时间。测试后得到ECONNRESET报错。初步判断可能是OSS的问题,因为这种错误通常是卡销系统源码服务端主动断开连接,且之前遇到过类似问题,过段时间再尝试即可解决。但这次的错误有规律可循,无法简单归咎于OSS端,因此决定进行问题排查。
通过错误堆栈追踪到createHangUpError函数,查看源码发现ECONNRESET被设置到error对象上。进一步发现socketCloseListener函数,它处理socket关闭事件。此函数的大圣轮回经典源码作用在于监听socket关闭事件,当请求未收到响应即触发关闭事件时,可以判断是客户端主动断开连接。打印信息显示从发起请求到报错耗时ms,即秒左右。基于此线索,继续分析客户端触发断开连接的逻辑。
研究ali-oss初始化实例过程,发现默认使用全局的KeepAliveAgent实例,它继承自http.Agent,针对长连接进行配置。模板短信平台源码options.timeout默认设置为秒,此参数用于设置socket的最大允许空闲时间。查阅父类实现,得知timeout参数用于调用socket.setTimeout方法,即超过最大允许空闲时间后,客户端会回收socket,导致连接断开。与打印的请求耗时相吻合,即秒。
明确问题所在后,Android权限源码分析对测试代码进行修改。在构造OSS实例时手动传入自定义agent实例,并将socket的最大允许空闲时间设置为足够大(分钟)。再次执行代码,报错名变成了ResponseTimeoutError,表示等待响应超时。打印的请求过程时间是秒,而urllib默认响应超时时间为5秒,为什么等待这么久才报错?进一步检查初始化OSS实例时是否设置了其他配置。
发现OSS实例初始化时重新设置过timeout参数,将其设置为分钟。修改代码,增加timeout配置项并设置足够大值(分钟)。再次运行,报错ECONNRESET,与之前遇到的情况一致。经过对比分析,发现错误堆栈不同,由socketCloseListener变为socketOnEnd,说明是不同的行为导致的。深入研究源码,发现逻辑走到了socket的end事件处理函数。
查阅API文档,得知当客户端接收到远端服务器发送的FIN包后,会触发end事件。因此,连接是被远程服务器主动断开的,客户端被动关闭连接。结合打印的请求时间为秒,可以推断OSS服务器的socket最大允许空闲时间应为秒。
解决此问题的方法是增加心跳包。既然OSS服务端允许空闲时间秒,客户端需要在秒内至少发送一个包以维持连接。对readable stream进行适当改造(仅供参考),并设置心跳包内容。修改测试代码后,问题得到解决。
此问题的排查和解决过程展示了全栈技术在处理网络通信问题时的应用,包括错误分析、代码调试、逻辑追踪、系统配置调整等多方面知识。如果你对全栈技术感兴趣,欢迎交流和探讨,共同进步。