mirror of https://github.com/apache/openjpa.git
OPENJPA-466 - Commit patch contributed by Tim McConnell.
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@745737 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
1a407b6d68
commit
679989fd16
|
@ -294,7 +294,9 @@ public class NativeJDBCSeq
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
stmnt = conn.prepareStatement(_select);
|
stmnt = conn.prepareStatement(_select);
|
||||||
|
synchronized(this) {
|
||||||
rs = stmnt.executeQuery();
|
rs = stmnt.executeQuery();
|
||||||
|
}
|
||||||
if (rs.next())
|
if (rs.next())
|
||||||
return rs.getLong(1);
|
return rs.getLong(1);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* 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 agreed 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.Serializable;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.SequenceGenerator;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Tim McConnell
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name="ENTITY_EMPLOYEE")
|
||||||
|
public class EntityEmployee implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 2961572787273807912L;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@SequenceGenerator(name="SeqEmployee", sequenceName="test_native_sequence")
|
||||||
|
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SeqEmployee")
|
||||||
|
private int id;
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
private float salary;
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getSalary() {
|
||||||
|
return salary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSalary(float salary) {
|
||||||
|
this.salary = salary;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "EntityEmployee: Employee id: " + getId() +
|
||||||
|
" firstName: " + getFirstName() +
|
||||||
|
" lastName: " + getLastName() +
|
||||||
|
" salary: " + getSalary();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result
|
||||||
|
+ ((getFirstName() == null) ? 0 : getFirstName().hashCode());
|
||||||
|
result = prime * result + getId();
|
||||||
|
result = prime * result
|
||||||
|
+ ((getLastName() == null) ? 0 : getLastName().hashCode());
|
||||||
|
result = prime * result + Float.floatToIntBits(getSalary());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final EntityEmployee other = (EntityEmployee) obj;
|
||||||
|
if (getId() != other.getId()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getFirstName() == null) {
|
||||||
|
if (other.getFirstName() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!getFirstName().equals(other.getFirstName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getLastName() == null) {
|
||||||
|
if (other.getLastName() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!getLastName().equals(other.getLastName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Float.floatToIntBits(getSalary()) != Float.floatToIntBits(other
|
||||||
|
.getSalary())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
* 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 agreed 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.Serializable;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.SequenceGenerator;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Tim McConnell
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name="ENTITY_PERSON")
|
||||||
|
public class EntityPerson implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 3772049669261731520L;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@SequenceGenerator(name="SeqPerson", sequenceName="test_native_sequence")
|
||||||
|
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SeqPerson")
|
||||||
|
private int id;
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
|
||||||
|
public EntityPerson() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityPerson(String firstName, String lastName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "EntityPerson: Person id: " + getId() +
|
||||||
|
" firstName: " + getFirstName() +
|
||||||
|
" lastName: " + getLastName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result
|
||||||
|
+ ((getFirstName() == null) ? 0 : getFirstName().hashCode());
|
||||||
|
result = prime * result + getId();
|
||||||
|
result = prime * result
|
||||||
|
+ ((getLastName() == null) ? 0 : getLastName().hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final EntityPerson other = (EntityPerson) obj;
|
||||||
|
if (getId() != other.getId()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getFirstName() == null) {
|
||||||
|
if (other.getFirstName() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!getFirstName().equals(other.getFirstName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getLastName() == null) {
|
||||||
|
if (other.getLastName() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!getLastName().equals(other.getLastName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,543 @@
|
||||||
|
/*
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue