https://github.com/apache/shardingsphere/issues/19346
本地运行 shardingsphere-jdbc-core 的单元测试没问题,但 GitHub Actions 中的 CI 有一定概率失败。
使用 Maven 构建 shardingsphere-jdbc-core,多次尝试未能复现:
mvn clean install -pl shardingsphere-jdbc/shardingsphere-jdbc-core
尝试了以下几种 JDK:
均未能复现问题。
重新观察报错信息:
可以发现几乎所有的报错都指向同一个地方:
都是因为 TransactionType 是一个 Mock 的对象,导致获取不到 ShardingSphereTransactionManager。
来源一:
TransactionRule 的 defaultType
来源二:
来源于 TransactionTypeHolder。但 TransactionTypeHolder 中的值的根本来源是 TransactionRule 的 defaultType。所以根源还是来源一。
TransactionTypeHolder 使用了 ThreadLocal 存储 TransactionType。由于单元测试在同一个模块中默认单线程串行运行,所以此处考虑有没有可能是单元测试中存在 ThreadLocal 泄漏?
由于报错的直接原因是 TransactionType 是个 mock 对象,因此我们先看看哪里有可能会产生 mock 的 TransactionType。
通过全局搜索发现,代码中并没有直接 mock TransactionType。但是存在不少 TransactionRule 的 RETURNS_DEEP_STUBS。
很可能是这些 TransactionRule 的 DEEP_STUBS 返回的 mock 的 TransactionType。
通过在 TransactionTypeHolder 打断点也能确认,部分单元测试会将 mock TransactionType 放入 ThreadLocal
但是本地如何才能重现问题证明泄漏?
根据经验,单元测试偶发报错很大可能和执行顺序有关系,观察 GitHub Actions 报错的单元测试执行顺序:
发现其中的 org.apache.shardingsphere.driver.jdbc.adapter.ConnectionAdapterTest 使用了 mock TransactionRule 并且在报错的测试之前执行:
相关链接:https://github.com/apache/shardingsphere/runs/7407025246?check_suite_focus=true
但是本地执行单元测试时,ConnectionAdapterTest 永远在会报错的测试后面,通过 Pattern 指定也无法控制执行顺序。
换个思路,如果我们把 ConnectionAdapterTest 测试类挪个位置?
在本地把 ConnectionAdapterTest 挪到 org.apache.shardingsphere.driver.jdbc.core.statement 包下面:
再次执行测试,本地复现成功:
单元测试中凡是涉及 mock TransactionRule DEEP_STUBS 的地方都需要清理 ThreadLocal。
https://github.com/apache/shardingsphere/pull/19362
与 mockStatic 泄漏不同,使用 ThreadLocal 的是生产代码,对于不熟悉代码的人根本不知道里面使用了 ThreadLocal,更不知道会有泄漏的风险。最好的解决方案是从生产代码上避免使用 ThreadLocal,但需要考虑实际情况。