Skip to content

Commit

Permalink
Fixes coalesce and case operators in multithreaded environments with …
Browse files Browse the repository at this point in the history
…different number of arguments

The bug is described in the issue eclipse-ee4j#2136

TestArgumentListFunctionExpressionConcurrency.java - adds tests that reproduce the bug.
ArgumentListFunctionExpression.java - fixes printSQL to use the operator itself and not the common instance of it
  • Loading branch information
Igor-Mukhin committed Sep 11, 2024
1 parent 1b44275 commit ffbbaa1
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,10 @@ public void setOperator(ExpressionOperator theOperator) {
*/
@Override
public void printSQL(ExpressionSQLPrinter printer) {
ListExpressionOperator realOperator;
realOperator = (ListExpressionOperator)getPlatformOperator(printer.getPlatform());
operator.copyTo(realOperator);
realOperator.setIsComplete(true);
realOperator.printCollection(this.children, printer);
ListExpressionOperator operator = (ListExpressionOperator) this.operator;

operator.setIsComplete(true);
operator.printCollection(this.children, printer);
}


Expand Down
23 changes: 23 additions & 0 deletions igor-readme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
2.7.x:
Idea:
- set java to 17
M2_HOME=/usr/share/maven ant -f antbuild.xml build
M2_HOME=/usr/share/maven ant -f antbuild.xml -Dtest.fail.fast=true -Dfail.on.error=true test-lrg

5.0.x:
Idea:
- set java to 21
- activate "mysql" maven project
Shell:
sdk default java 21.0.3-tem
mvn -U install -Pstaging,oss-release -DskipTests


Idea test run props:
-Djakarta.persistence.jdbc.driver=com.mysql.cj.jdbc.Driver
-Djakarta.persistence.jdbc.url=jdbc:mysql://localhost:3306/ecltests?allowPublicKeyRetrieval=true
-Djakarta.persistence.jdbc.user=igor
-Djakarta.persistence.jdbc.password=start
-Ddb.platform=org.eclipse.persistence.platform.database.MySQLPlatform

mvn -Pmysql -Ddb.driver=com.mysql.cj.jdbc.Driver -Ddb.url=jdbc:mysql://localhost:3306/ecltests -Ddb.user=igor -Ddb.pwd=start -Dit.test=org.eclipse.persistence.jpa.test.jpql.TestArgumentListFunctionExpressionConcurrency verify
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
package org.eclipse.persistence.jpa.test.jpql;


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.ObjIntConsumer;

import org.eclipse.persistence.jpa.test.framework.DDLGen;
import org.eclipse.persistence.jpa.test.framework.Emf;
import org.eclipse.persistence.jpa.test.framework.EmfRunner;
import org.eclipse.persistence.jpa.test.jpql.model.JPQLEntity;
import org.junit.Test;
import org.junit.runner.RunWith;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;

/**
* This test reproduces the issues #2136, #1867 and #1717.
*
* @author Igor Mukhin
*/
@RunWith(EmfRunner.class)
public class TestArgumentListFunctionExpressionConcurrency {

private static final int MAX_THREADS = Math.min(Runtime.getRuntime().availableProcessors(), 4);
private static final int ITERATIONS_PER_THREAD = 1000;

@Emf(name = "argumentListFunctionExpressionConcurrencyEMF", createTables = DDLGen.DROP_CREATE, classes = { JPQLEntity.class })
private EntityManagerFactory emf;

@Test
public void testConcurrentUseOfCoalesce() throws Exception {
runInParallel((em, i) -> {
String jpql;
if (i % 2 == 0) {
jpql = "SELECT p FROM JPQLEntity p WHERE p.string1 = coalesce(p.string2, p.string1, '" + cacheBuster(i) + "')";
} else {
jpql = "SELECT p FROM JPQLEntity p WHERE p.string1 = coalesce(p.string2, '" + cacheBuster(i) + "')";
}
em.createQuery(jpql, JPQLEntity.class).getResultList();
});
}

@Test
public void testConcurrentUseOfCaseCondition() throws Exception {
runInParallel((em, i) -> {
String jpql;
if (i % 2 == 0) {
jpql = "SELECT p FROM JPQLEntity p"
+ " WHERE p.string1 = case "
+ " when p.string2 = '" + cacheBuster(i) + "' then p.string1 "
+ " else null "
+ " end";
} else {
jpql = "SELECT p FROM JPQLEntity p"
+ " WHERE p.string1 = case "
+ " when p.string2 = '" + cacheBuster(i) + "' then p.string1"
+ " when p.string2 = 'x' then p.string2"
+ " else null "
+ " end";

}
em.createQuery(jpql, JPQLEntity.class).getResultList();
});
}

private static String cacheBuster(Integer i) {
return "cacheBuster." + Thread.currentThread().getName() + "." + i;
}

private void runInParallel(ObjIntConsumer<EntityManager> runnable) throws Exception {
AtomicReference<Exception> exception = new AtomicReference<>();

// start all threads
List<Thread> threads = new ArrayList<>();
for (int t = 0; t < MAX_THREADS; t++) {
Thread thread = new Thread(() -> {
try {
for (int i = 0; i < ITERATIONS_PER_THREAD; i++) {
if (exception.get() != null) {
return;
}

try (EntityManager em = emf.createEntityManager()) {
runnable.accept(em, i);
}

}
} catch (Exception e) {
exception.set(e);
}
});
threads.add(thread);
thread.start();
}

// wait for all threads to finish
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
exception.set(e);
}
});

// throw the first exception that occurred
if (exception.get() != null) {
throw exception.get();
}
}
}

0 comments on commit ffbbaa1

Please sign in to comment.