ShardingSphere inline表达式线程安全问题定位
ShardingSphere inline表达式线程安全问题定位
问题背景
春节期间发现 ShardingSphere 事务 E2E 偶发执行失败问题,并且每次执行失败需要执行很久,直到超时。最终定位发现 inline 表达式存在线程安全问题。本文记录定位并解决 inline 表达式线程安全问题的过程。
问题原因
1.GroovyInlineExpressionParser 里有成员变量,存在并发修改,不能使用单例 SPI 实现;
2.执行 Groovy 表达式时,需要执行 rehydrate 方法 copy Closure,使得每个 Closure 都有独立的执行环境,避免属性赋值时产生线程安全问题。
问题定位
构造测试用例尝试复现问题
构造测试用例,且在测试用例中添加线程相关信息,观察执行结果。
@Test
@SneakyThrows({
ExecutionException.class, InterruptedException.class})
void assertThreadSafety() {
int threadCount = 2;
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
List<Future<?>> futures = new ArrayList<>(threadCount);
for (int i = 0; i < threadCount; i++) {
Future<?> future = pool.submit(this::createInlineExpressionParseTask);
futures.add(future);
}
for (Future<?> future : futures) {
future.get();
}
pool.shutdown();
}
private void createInlineExpressionParseTask() {
for (int j = 0; j < 5; j++) {
// 传入线程信息
String resultSuffix = Thread.currentThread().getName() + "--" + j;
String actual = TypedSPILoader.getService(InlineExpressionParser.class, "GROOVY", PropertiesBuilder.build(
new PropertiesBuilder.Property(InlineExpressionParser.INLINE_EXPRESSION_KEY, "ds_${id%2}"))).evaluateWithArgs(Collections.singletonMap("id", 1));
// 断言执行出来的结果是不是当前线程的
assertThat(actual, is(String.format("ds_%s", resultSuffix)));
String actual2 = TypedSPILoader.getService(InlineExpressionParser.class, "GROOVY", PropertiesBuilder.build(
new PropertiesBuilder.Property(InlineExpressionParser.INLINE_EXPRESSION_KEY, "account_${id}"))).evaluateWithArgs(Collections.singletonMap("id", resultSuffix));
浙公网安备 33010602011771号