From ffbbaa1c0bec53814d236065ed1753005e62bfa6 Mon Sep 17 00:00:00 2001 From: Igor Mukhin Date: Wed, 11 Sep 2024 14:19:44 +0200 Subject: [PATCH] Fixes coalesce and case operators in multithreaded environments with different number of arguments The bug is described in the issue #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 --- .../ArgumentListFunctionExpression.java | 9 +- igor-readme.txt | 23 ++++ ...mentListFunctionExpressionConcurrency.java | 125 ++++++++++++++++++ 3 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 igor-readme.txt create mode 100644 jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/jpql/TestArgumentListFunctionExpressionConcurrency.java diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/expressions/ArgumentListFunctionExpression.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/expressions/ArgumentListFunctionExpression.java index 96d5fd9564f..aef4bdd28f7 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/expressions/ArgumentListFunctionExpression.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/expressions/ArgumentListFunctionExpression.java @@ -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); } diff --git a/igor-readme.txt b/igor-readme.txt new file mode 100644 index 00000000000..0c82cd0b7a8 --- /dev/null +++ b/igor-readme.txt @@ -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 diff --git a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/jpql/TestArgumentListFunctionExpressionConcurrency.java b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/jpql/TestArgumentListFunctionExpressionConcurrency.java new file mode 100644 index 00000000000..05c063efff4 --- /dev/null +++ b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/jpql/TestArgumentListFunctionExpressionConcurrency.java @@ -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 runnable) throws Exception { + AtomicReference exception = new AtomicReference<>(); + + // start all threads + List 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(); + } + } +}