记一次协程环境下类成员变量污染的问题

2022-06-21 00:20:26 浏览数 (2)

我维护的项目采用的技术框架是php中基于swoole的一个web框架叫做swoft。该框架是协程环境,并且是模仿springboot的一个注解式的php框架。我做的一个需求,发生了协程环境下的类成员变量污染的问题。事故是这样的:发起一次rpc请求,返回的结果存在一个类的成员变量,但是发现请求的参数跟结果对不上,我先是排查了错误日志,没发现异常,然后排查请求执行日志,发现日志数量丢失了。于是我仔细看了代码,我通过职责链模式将结果传递到了每一个类的成员变量中,而BeanFactory::getBean这个方法默认是获取单例的类,于是类的成员变量在没有保护的情况下被其他协程污染了。知道这个问题后,我通过修改注解,将职责链的每一个类都修改成多例模式。在本地使用Jmeter压测时结果是正确的,但是又发现了一个新的问题就是执行日志丢失的问题。后面仔细查看了swoft中log包的实现,发现框架开发组也犯了一个跟我一样的错误。他这里的思路是:将日志存放在类的成员变量messages中,这是一个数组,当这个数组中元素大于刷盘数量时,将该数组中的日志记录刷入文件中。但是却没有线程同步的代码块保护,于是在并发的情况下就可以复现日志被覆盖导致缺失的问题了。我在想框架的开发组不应该会犯这样的错误。于是翻了一下github的swoft框架的issue,发现在20年的时候就已经有人提出过这个问题,但是一直没有得到修复。后面我翻看了一下源码,发现他的log这个bean是单例模式,所以才有messages成员变量被污染的问题,我通过创建一个新的newLogger类并将其设置在框架启动时的日志处理类,该并发污染的问题就被解决了。

通过这次事故的思考:1. 框架开发组也未必是正确的,哪怕是这个github 5k多的star也存在大量的问题未解决(吃瓜:基于swoole的web框架貌似大多数都跟swoole底层开发组的人有利益冲突导致没人愿意基于swoole去开发框架。)。2. 该框架的mysql连接池也做的并不是特别好,连接保活的机制都没有,经常在终端日志上输出mysql has gone away的错误,该错误就是因为mysql server将该连接关闭了,但是代码层面仍然将该连接当作正常连接去发送数据,于是报了一个这样的错。

文中相关的两个issue的链接:

https://github.com/swoft-cloud/swoft/issues/1430。

https://github.com/swoft-cloud/swoft/issues/1367。这个日志重复的问题可以通过写一个简单的接口,就直接打印4条日志,然后10个线程并发访问,结果不一定是40条日志被打印,该结果足以证明协程污染类成员变量的问题。

0 人点赞