Skip to content

Commit

Permalink
Bug 463042: Concurrency issue with Case expression operator (#1358)
Browse files Browse the repository at this point in the history
Signed-off-by: Will Dazey <dazeydev.3@gmail.com>
Signed-off-by: Patrick Haller <patrick.haller@sap.com>
  • Loading branch information
dazey3 committed Nov 12, 2021
1 parent 09f029a commit 1564cea
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 1998, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021 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
Expand Down Expand Up @@ -106,7 +107,17 @@ public void printSQL(ExpressionSQLPrinter printer) {

@Override
protected void postCopyIn(Map alreadyDone) {
((ListExpressionOperator)operator).setNumberOfItems(0);
/*
* Bug 463042: All ArgumentListFunctionExpression instances store the same operator reference.
* Unfortunately, ListExpressionOperator.numberOfItems stores state. If multiple ArgumentListFunctionExpression
* are run concurrently, then the ListExpressionOperator.numberOfItems state shared by all instances
* becomes inconsistent. A solution is to make sure each ArgumentListFunctionExpression has a unique operator
* reference.
*/
final ListExpressionOperator originalOperator = ((ListExpressionOperator) this.operator);
this.operator = new ListExpressionOperator();
originalOperator.copyTo(this.operator);

Boolean hasLastChildCopy = hasLastChild;
hasLastChild = null;
super.postCopyIn(alreadyDone);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018 IBM Corporation. All rights reserved.
* Copyright (c) 2018, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2021 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
Expand All @@ -19,7 +19,10 @@
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
Expand Down Expand Up @@ -76,7 +79,51 @@ public void testInsertConcurrency() throws Exception {
}

Assert.assertTrue(errors.toString(), errors.isEmpty());
}

/**
* Bug 463042: Executing the same query simultaneously on separate threads has the possibility of
* causing an ArrayOutOfBoundsException to be thrown. This test spins up multiple threads, executes
* the same query on each and validates that none of the threads failed.
*
* @throws Exception
*/
@Test
public void testCaseExpressionOperatorConcurrency() throws Exception {
final AtomicInteger count = new AtomicInteger();
final AtomicInteger error = new AtomicInteger();

final int threads = 100;
final ExecutorService taskExecutor = Executors.newFixedThreadPool(threads);

// Spawn 100 threads
for (int i = 0; i < threads; i++) {
taskExecutor.execute(new Runnable() {
public void run() {
count.incrementAndGet();

final EntityManager em = emf.createEntityManager();
try {
// Executing the Query
em.createNamedQuery("CONCURR_CASE_QUERY", Integer.class).setParameter("id", 1).getSingleResult();
} catch (Exception e) {
error.incrementAndGet();
System.out.println(e.getMessage());
} finally {
if (em != null) {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
em.close();
}
}
}
});
}
taskExecutor.shutdown();
taskExecutor.awaitTermination(5, TimeUnit.SECONDS);

Assert.assertEquals("Expected no failures, but " + error.intValue() + "/" + count.intValue() + " threads failed", 0, error.intValue());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018 IBM Corporation. All rights reserved.
* Copyright (c) 2018, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2021 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
Expand All @@ -25,10 +25,12 @@
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table;

@Entity
@Table(name="CONCURR_USER")
@NamedQuery(name = "CONCURR_CASE_QUERY", query = "SELECT CASE WHEN (COUNT(e) > 0) THEN true ELSE false END FROM User e WHERE e.id = :id")
public class User {

@Id private int id;
Expand Down

0 comments on commit 1564cea

Please sign in to comment.