blob: 9cfdf62bcb8ce191896bd58c84b2a8756efe2f7a [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. 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
*/
// Contributors:
// dminsky - initial API and implementation
package org.eclipse.persistence.testing.tests.simultaneous;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.eclipse.persistence.sessions.UnitOfWork;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.queries.QueryResultsCachePolicy;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.testing.models.employee.domain.Employee;
import org.eclipse.persistence.testing.framework.TestCase;
/**
* Bug 304423 - CacheIdentityMap is incorrectly synchronized
* Test invoking many queries from multiple threads against a QueryCache
* Underlying issue was an infinite loop in CacheIdentityMap ensureFixedSize()
* @author dminsky
*/
public class ConcurrentQueryCacheQueryResultsTest extends TestCase {
// constants
protected static final int CACHE_SIZE = 5;
protected static final int CONCURRENT_THREADS = 7;
protected static final int TESTDATA_INSTANCES = 100;
protected static final int NUMBER_OF_EXECUTIONS = 20000;
protected static final int TIMEOUT_IN_SECONDS = 60;
protected static final String QUERY_NAME = "findEmployeeByFirstName";
// shared results/testdata
protected List<Employee> testEmployees;
protected List<Boolean> testResults;
protected boolean terminatedNormally;
public ConcurrentQueryCacheQueryResultsTest() {
super();
setDescription("Test invoking many queries from multiple threads against a QueryCache");
}
@Override
public void setup() {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
// create a simple named query on the session and configure it with a small query results cache size,
// set to CACHE_SIZE
ReadObjectQuery query = new ReadObjectQuery(Employee.class);
ExpressionBuilder builder = query.getExpressionBuilder();
query.setSelectionCriteria(builder.get("firstName").equal(builder.getParameter("firstName")));
query.addArgument("firstName");
query.setQueryResultsCachePolicy(new QueryResultsCachePolicy(CACHE_SIZE));
getSession().addQuery(QUERY_NAME, query);
testEmployees = new ArrayList<Employee>(TESTDATA_INSTANCES);
// set all test results to false
testResults = new ArrayList<Boolean>(NUMBER_OF_EXECUTIONS);
for (int i = 0; i < NUMBER_OF_EXECUTIONS; i++) {
testResults.add(i, Boolean.FALSE);
}
// create our own test data
UnitOfWork uow = getSession().acquireUnitOfWork();
for (int i = 0; i < TESTDATA_INSTANCES; i++) {
Employee emp = new Employee();
emp.setFirstName(String.valueOf(UUID.randomUUID()).substring(0, 8));
emp.setLastName(String.valueOf(UUID.randomUUID()).substring(0, 8));
uow.registerObject(emp);
testEmployees.add(emp);
}
uow.commit();
}
@Override
public void test() throws Throwable {
// configure a thread pool and ExecutorService, add Runnable instances to execute
ExecutorService executor = Executors.newFixedThreadPool(CONCURRENT_THREADS);
for (int resultId = 0; resultId < NUMBER_OF_EXECUTIONS; resultId++) {
executor.execute(new QueryWorkerThread(resultId));
}
executor.shutdown(); // no more tasks
// configure a timeout and await termination
try {
terminatedNormally = executor.awaitTermination(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw e;
}
}
@Override
public void verify() {
// fail if timeout occurred, instead of 'normal' termination
if (!terminatedNormally) {
fail(getClass().getSimpleName() + " timed out whilst executing, after " + TIMEOUT_IN_SECONDS + " seconds");
}
// fail if any test result was incorrect (false)
for (int resultId = 0; resultId < NUMBER_OF_EXECUTIONS; resultId++) {
if (this.testResults.get(resultId) == Boolean.FALSE) {
fail("Test result failed (false) for test result id: " + resultId);
}
}
}
@Override
public void reset() {
getSession().removeQuery(QUERY_NAME);
UnitOfWork uow = getSession().acquireUnitOfWork();
uow.deleteAllObjects(testEmployees);
uow.commit();
getSession().getIdentityMapAccessor().initializeIdentityMaps();
}
protected class QueryWorkerThread implements Runnable {
protected int resultId;
public QueryWorkerThread(int resultId) {
super();
this.resultId = resultId;
}
@Override
public void run() {
int randomNumber = new Random().nextInt(TESTDATA_INSTANCES);
Employee randomEmployee = testEmployees.get(randomNumber);
Vector<String> args = new Vector<String>();
args.add(randomEmployee.getFirstName());
Employee employeeFound = (Employee) getSession().executeQuery(QUERY_NAME, args);
assertEquals(randomEmployee.getId(), employeeFound.getId());
assertEquals(randomEmployee.getFirstName(), employeeFound.getFirstName());
assertEquals(randomEmployee.getLastName(), employeeFound.getLastName());
testResults.set(this.resultId, Boolean.TRUE);
}
}
}