blob: ee9075ad36dffaad785e8469053c80f0e78d2a14 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agEmployee_Last_Name to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.persistence.sequence;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import javax.persistence.EntityManager;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
/**
* @author Tim McConnell
* @since 2.0.0
*/
public class TestSequence extends SingleEMFTestCase {
private String multiThreadExecuting = null;
private static final int NUMBER_ENTITIES = 5000;
public void setUp() {
setUp(EntityPerson.class, EntityEmployee.class, CLEAR_TABLES,
"openjpa.Multithreaded", "true");
}
// Override teardown to preserve database contents
@Override
public void tearDown() throws Exception {
}
public void testMultiThreadedNativeSequences() throws Exception {
boolean supportsNativeSequence = false;
try {
supportsNativeSequence = ((JDBCConfiguration) emf
.getConfiguration()).getDBDictionaryInstance()
.nextSequenceQuery != null;
} catch (Throwable t) {
supportsNativeSequence = false;
}
if (supportsNativeSequence) {
mttest(6, 8);
switch ((int) (Math.random() * 7)) {
case 0:
createAndRemove();
break;
case 1:
createManyPersonsInSeparateTransactions();
break;
case 2:
createManyEmployeesInSeparateTransactions();
break;
case 3:
createManyPersonsAndEmployeesInSeparateTransactions();
break;
case 4:
createManyPersonsInSingleTransaction();
break;
case 5:
createManyEmployeesInSingleTransaction();
break;
case 6:
createManyPersonsAndEmployeesInSingleTransaction();
break;
}
}
}
private void createAndRemove() {
int person_id;
int employee_id;
EntityManager em = emf.createEntityManager();
EntityPerson person = new EntityPerson();
person.setFirstName("Person_First_Name");
person.setLastName("Person_Last_Name");
EntityEmployee employee = new EntityEmployee();
employee.setFirstName("Employee_First_Name");
employee.setLastName("Employee_Last_Name");
employee.setSalary(NUMBER_ENTITIES);
em.getTransaction().begin();
em.persist(person);
em.persist(employee);
em.getTransaction().commit();
em.refresh(person);
em.refresh(employee);
person_id = person.getId();
employee_id = employee.getId();
person = em.find(EntityPerson.class, person_id);
assertTrue(person != null);
assertTrue(person.getId() == person_id);
assertTrue(person.getFirstName().equals("Person_First_Name"));
assertTrue(person.getLastName().equals("Person_Last_Name"));
employee = em.find(EntityEmployee.class, employee_id);
assertTrue(employee != null);
assertTrue(employee.getId() == employee_id);
assertTrue(employee.getFirstName().equals("Employee_First_Name"));
assertTrue(employee.getLastName().equals("Employee_Last_Name"));
assertTrue(employee.getSalary() == NUMBER_ENTITIES);
em.getTransaction().begin();
em.remove(person);
em.remove(employee);
em.getTransaction().commit();
em.clear();
em.close();
}
private void createManyPersonsInSeparateTransactions() {
EntityManager em = emf.createEntityManager();
for (int ii = 0; ii < NUMBER_ENTITIES; ii++) {
EntityPerson person = new EntityPerson();
person.setFirstName("1_First_name_" + ii);
person.setLastName("1_Last_name_" + ii);
em.getTransaction().begin();
em.persist(person);
em.getTransaction().commit();
}
em.clear();
em.close();
}
private void createManyEmployeesInSeparateTransactions() {
EntityManager em = emf.createEntityManager();
for (int ii = 0; ii < NUMBER_ENTITIES; ii++) {
EntityEmployee employee = new EntityEmployee();
employee.setFirstName("2_First_name_" + ii);
employee.setLastName("2_Last_name_" + ii);
employee.setSalary(ii);
em.getTransaction().begin();
em.persist(employee);
em.getTransaction().commit();
}
em.clear();
em.close();
}
private void createManyPersonsAndEmployeesInSeparateTransactions() {
EntityManager em = emf.createEntityManager();
for (int ii = 0; ii < NUMBER_ENTITIES; ii++) {
EntityPerson person = new EntityPerson();
person.setFirstName("3_First_name_" + ii);
person.setLastName("3_Last_name_" + ii);
EntityEmployee employee = new EntityEmployee();
employee.setFirstName("4_First_name_" + ii);
employee.setLastName("4_Last_name_" + ii);
employee.setSalary(ii);
em.getTransaction().begin();
em.persist(person);
em.persist(employee);
em.getTransaction().commit();
}
em.clear();
em.close();
}
private void createManyPersonsInSingleTransaction() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
for (int ii = 0; ii < NUMBER_ENTITIES; ii++) {
EntityPerson person = new EntityPerson();
person.setFirstName("5_First_name_" + ii);
person.setLastName("5_Last_name_" + ii);
em.persist(person);
}
em.getTransaction().commit();
em.clear();
em.close();
}
private void createManyEmployeesInSingleTransaction() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
for (int ii = 0; ii < NUMBER_ENTITIES; ii++) {
EntityEmployee employee = new EntityEmployee();
employee.setFirstName("6_First_name_" + ii);
employee.setLastName("6_Last_name_" + ii);
employee.setSalary(ii);
em.persist(employee);
}
em.getTransaction().commit();
em.clear();
em.close();
}
private void createManyPersonsAndEmployeesInSingleTransaction() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
for (int ii = 0; ii < NUMBER_ENTITIES; ii++) {
EntityPerson person = new EntityPerson();
person.setFirstName("7_First_name_" + ii);
person.setLastName("7_Last_name_" + ii);
EntityEmployee employee = new EntityEmployee();
employee.setFirstName("8_First_name_" + ii);
employee.setLastName("8_Last_name_" + ii);
employee.setSalary(ii);
em.persist(person);
em.persist(employee);
}
em.getTransaction().commit();
em.clear();
em.close();
}
/**
* Re-execute the invoking method a random number of times in a random
* number of Threads.
*/
public void mttest() throws ThreadingException {
// 6 iterations in 8 threads is a good trade-off between
// tests taking way too long and having a decent chance of
// identifying MT problems.
int iterations = 6;
int threads = 8;
mttest(threads, iterations);
}
/**
* Execute the calling method <code>iterations</code> times in
* <code>threads</code> Threads.
*/
public void mttest(int threads, int iterations) {
mttest(0, threads, iterations);
}
public void mttest(int serialCount, int threads, int iterations)
throws ThreadingException {
String methodName = callingMethod("mttest");
mttest(serialCount, threads, iterations, methodName, new Object[0]);
}
/**
* Execute a test method in multiple threads.
*
* @param threads
* the number of Threads to run in
* @param iterations
* the number of times the method should be execute in a single
* Thread
* @param method
* the name of the method to execute
* @param args
* the arguments to pass to the method
* @throws ThreadingException
* if an errors occur in any of the Threads. The actual
* exceptions will be embedded in the exception. Note that this
* means that assert() failures will be treated as errors rather
* than warnings.
* @author Marc Prud'hommeaux
*/
public void mttest(int threads, int iterations, final String method,
final Object[] args) throws ThreadingException {
mttest(0, threads, iterations, method, args);
}
public void mttest(int serialCount, int threads, int iterations,
final String method, final Object[] args) throws ThreadingException {
if (multiThreadExecuting != null
&& multiThreadExecuting.equals(method)) {
// we are currently executing in multi-threaded mode:
// don't deadlock!
return;
}
multiThreadExecuting = method;
try {
Class<?>[] paramClasses = new Class[args.length];
for (int i = 0; i < paramClasses.length; i++)
paramClasses[i] = args[i].getClass();
final Method meth;
try {
meth = getClass().getMethod(method, paramClasses);
} catch (NoSuchMethodException nsme) {
throw new ThreadingException(nsme.toString(), nsme);
}
final Object thiz = this;
mttest("reflection invocation: (" + method + ")", serialCount,
threads, iterations, new VolatileRunnable() {
public void run() throws Exception {
meth.invoke(thiz, args);
}
});
} finally {
multiThreadExecuting = null;
}
}
public void mttest(String title, final int threads, final int iterations,
final VolatileRunnable runner) throws ThreadingException {
mttest(title, 0, threads, iterations, runner);
}
/**
* Execute a test method in multiple threads.
*
* @param title
* a description of the test, for inclusion in the error message
* @param serialCount
* the number of times to run the method serially before spawning
* threads.
* @param threads
* the number of Threads to run in
* @param iterations
* the number of times the method should
* @param runner
* the VolatileRunnable that will execute the actual test from
* within the Thread.
* @throws ThreadingException
* if an errors occur in any of the Threads. The actual
* exceptions will be embedded in the exception. Note that this
* means that assert() failures will be treated as errors rather
* than warnings.
* @author Marc Prud'hommeaux
*/
public void mttest(String title, final int serialCount, final int threads,
final int iterations, final VolatileRunnable runner)
throws ThreadingException {
final List exceptions = Collections.synchronizedList(new LinkedList());
Thread[] runners = new Thread[threads];
final long startMillis = System.currentTimeMillis() + 1000;
for (int i = 1; i <= threads; i++) {
final int thisThread = i;
runners[i - 1] = new Thread(title + " [" + i + " of " + threads
+ "]") {
public void run() {
// do our best to have all threads start at the exact
// same time. This is imperfect, but the closer we
// get to everyone starting at the same time, the
// better chance we have for identifying MT problems.
while (System.currentTimeMillis() < startMillis)
yield();
int thisIteration = 1;
try {
for (; thisIteration <= iterations; thisIteration++) {
// go go go!
runner.run();
}
} catch (Throwable error) {
synchronized (exceptions) {
// embed the exception into something that gives
// us some more information about the threading
// environment
exceptions.add(new ThreadingException("thread="
+ this.toString() + ";threadNum=" + thisThread
+ ";maxThreads=" + threads + ";iteration="
+ thisIteration + ";maxIterations="
+ iterations, error));
}
}
}
};
}
// start the serial tests(does not spawn the threads)
for (int i = 0; i < serialCount; i++) {
runners[0].run();
}
// start the multithreaded
for (int i = 0; i < threads; i++) {
runners[i].start();
}
// wait for them all to complete
for (int i = 0; i < threads; i++) {
try {
runners[i].join();
} catch (InterruptedException e) {
}
}
if (exceptions.size() == 0)
return; // sweeeeeeeet: no errors
// embed all the exceptions that were throws into a
// ThreadingException
Throwable[] errors = (Throwable[]) exceptions.toArray(new Throwable[0]);
throw new ThreadingException("The " + errors.length
+ " embedded errors " + "occured in the execution of " + iterations
+ " iterations " + "of " + threads + " threads: [" + title + "]",
errors);
}
/**
* Check to see if we are in the top-level execution stack.
*/
public boolean isRootThread() {
return multiThreadExecuting == null;
}
/**
* A Runnable that can throw an Exception: used to test cases.
*/
public static interface VolatileRunnable {
public void run() throws Exception;
}
/**
* Exception for errors caught during threading tests.
*/
public class ThreadingException extends RuntimeException {
private static final long serialVersionUID = -1911769845552507956L;
private final Throwable[] _nested;
public ThreadingException(String msg, Throwable nested) {
super(msg);
if (nested == null)
_nested = new Throwable[0];
else
_nested = new Throwable[] { nested };
}
public ThreadingException(String msg, Throwable[] nested) {
super(msg);
if (nested == null)
_nested = new Throwable[0];
else
_nested = nested;
}
public void printStackTrace() {
printStackTrace(System.out);
}
public void printStackTrace(PrintStream out) {
printStackTrace(new PrintWriter(out));
}
public void printStackTrace(PrintWriter out) {
super.printStackTrace(out);
for (int i = 0; i < _nested.length; i++) {
out.print("Nested Throwable #" + (i + 1) + ": ");
_nested[i].printStackTrace(out);
}
}
}
/**
* Return the last method name that called this one by parsing the current
* stack trace.
*
* @param exclude
* a method name to skip
* @throws IllegalStateException
* If the calling method could not be identified.
* @author Marc Prud'hommeaux
*/
public String callingMethod(String exclude) {
// determine the currently executing method by
// looking at the stack track. Hackish, but convenient.
StringWriter sw = new StringWriter();
new Exception().printStackTrace(new PrintWriter(sw));
for (StringTokenizer stackTrace = new StringTokenizer(sw.toString(),
System.getProperty("line.separator"))
; stackTrace.hasMoreTokens() ; ) {
String line = stackTrace.nextToken().trim();
// not a stack trace element
if (!(line.startsWith("at ")))
continue;
String fullMethodName = line.substring(0, line.indexOf("("));
String shortMethodName = fullMethodName.substring(fullMethodName
.lastIndexOf(".") + 1);
// skip our own methods!
if (shortMethodName.equals("callingMethod"))
continue;
if (exclude != null && shortMethodName.equals(exclude))
continue;
return shortMethodName;
}
throw new IllegalStateException("Could not identify calling "
+ "method in stack trace");
}
}