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));
  
posted on 2024-02-29 20:27  flyingzc  阅读(16)  评论(0)    收藏  举报  来源