querydsl: QueryDSL not thread-safe, Alias.$(alias.getId()) returns null

I have been using QueryDSL in various projects over the years. Now I have two concrete projects that are struggling with a severe bug issue that QueryDSL: As it seems Alias stuff has a concurrency problem leading to Alias.$ function to return null where it should not. That bug only occurrs in complex multi-threaded batch processing situations and is not so easy to reproduce.

General code pattern of the affected query was something like this (nothing really exciting or exotic):

FooEntity foo = Alias.alias(FooEntity.class);
BarEntity bar = Alias.alias(BarEntity.class);
JPAQuery<FooEntity> query = new JPAQuery<FooEntity>(getEntityManager()).from(Alias.$(foo));
query.join(Alias.$(bar)).on(Alias.$(bar.getFoo().getId()).eq(Alias.$(foo.getId())));
...

We could even reproduce the bug locally and see in the debugger what is going on:

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 16 (9 by maintainers)

Commits related to this issue

Most upvoted comments

FYI the issue becomes reproducible after adding the following Rule to AliasTest (making all tests run themselves a couple of times parallel):

public static final int COUNT = 4;

@Rule
public TestRule concurrencyRule = new TestRule() {
    @Override
    public Statement apply(final Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                final CountDownLatch countDownLatch = new CountDownLatch(COUNT);
                ExecutorService executorService = Executors.newFixedThreadPool(COUNT);
                try {
                    List<Future<?>> results = new ArrayList<Future<?>>(COUNT);
                    for (int i = 0; i < COUNT; i++) {
                        results.add(executorService.submit(new Callable<Object>() {
                            @Override
                            public Object call() throws Exception {
                                try {
                                    countDownLatch.countDown();
                                    countDownLatch.await();
                                    base.evaluate();
                                    return null;
                                } catch (Exception e) {
                                    throw e;
                                } catch (Throwable t) {
                                    throw new Exception(t);
                                }
                            }
                        }));
                    }

                    for (Future<?> result : results) {
                        result.get();
                    }
                } finally{
                    executorService.shutdownNow();
                }
            }
        };
    }
};