You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@openjpa.apache.org by Tim McConnell <ti...@gmail.com> on 2009/02/28 04:00:03 UTC

Re: svn commit: r748390 - in /openjpa/branches/1.3.x: openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/ openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/

Hi Mike, thanks for committing this patch for the 1.3.x branch. I should mention 
though that when Albert committed the patch on trunk, he modified it very 
slightly to synchronize only on a single statement in the getSequence() method 
instead of at the method level. It will work either way, and we in fact tested it 
both ways, but his alternation is a slight improvement, and we probably should 
keep the fix consistent for  trunk and this branch. Thanks

mikedd@apache.org wrote:
> Author: mikedd
> Date: Fri Feb 27 04:44:01 2009
> New Revision: 748390
> 
> URL: http://svn.apache.org/viewvc?rev=748390&view=rev
> Log:
> OPENJPA-466 committing patch provided by Tim McConnell with formatting changes.
> 
> Added:
>     openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/
>     openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/EntityEmployee.java
>     openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/EntityPerson.java
>     openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/TestSequence.java
> Modified:
>     openjpa/branches/1.3.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/NativeJDBCSeq.java
> 
> Modified: openjpa/branches/1.3.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/NativeJDBCSeq.java
> URL: http://svn.apache.org/viewvc/openjpa/branches/1.3.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/NativeJDBCSeq.java?rev=748390&r1=748389&r2=748390&view=diff
> ==============================================================================
> --- openjpa/branches/1.3.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/NativeJDBCSeq.java (original)
> +++ openjpa/branches/1.3.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/NativeJDBCSeq.java Fri Feb 27 04:44:01 2009
> @@ -275,7 +275,7 @@
>      /**
>       * Return the next sequence value.
>       */
> -    private long getSequence(Connection conn)
> +    private synchronized long getSequence(Connection conn)
>          throws SQLException {
>          PreparedStatement stmnt = null;
>          ResultSet rs = null;
> 
> Added: openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/EntityEmployee.java
> URL: http://svn.apache.org/viewvc/openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/EntityEmployee.java?rev=748390&view=auto
> ==============================================================================
> --- openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/EntityEmployee.java (added)
> +++ openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/EntityEmployee.java Fri Feb 27 04:44:01 2009
> @@ -0,0 +1,135 @@
> +/*
> + * 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;
> +
> +@Entity
> +@Table(name="ENTITY_EMPLOYEE")
> +public class EntityEmployee implements Serializable {
> +
> +    private static final long serialVersionUID = 2L;
> +
> +    @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;
> +    }
> +
> +}
> 
> Added: openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/EntityPerson.java
> URL: http://svn.apache.org/viewvc/openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/EntityPerson.java?rev=748390&view=auto
> ==============================================================================
> --- openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/EntityPerson.java (added)
> +++ openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/EntityPerson.java Fri Feb 27 04:44:01 2009
> @@ -0,0 +1,130 @@
> +/*
> + * 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;
> +
> +@Entity
> +@Table(name="ENTITY_PERSON")
> +public class EntityPerson implements Serializable {
> +
> +    private static final long serialVersionUID = 1L;
> +
> +    @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;
> +    } 
> +}
> 
> Added: openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/TestSequence.java
> URL: http://svn.apache.org/viewvc/openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/TestSequence.java?rev=748390&view=auto
> ==============================================================================
> --- openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/TestSequence.java (added)
> +++ openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/sequence/TestSequence.java Fri Feb 27 04:44:01 2009
> @@ -0,0 +1,540 @@
> +/*
> + * 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;
> +
> +public class TestSequence extends SingleEMFTestCase {
> +
> +    private String multiThreadExecuting = null;
> +    private static final int NUMBER_ENTITIES = 10000;
> +
> +    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.
> +     */
> +    public void mttest(String title, final int serialCount, final int threads,
> +        final int iterations, final VolatileRunnable runner)
> +        throws ThreadingException {
> +        final List<ThreadingException> exceptions =
> +            Collections.synchronizedList(new LinkedList<ThreadingException>());
> +
> +        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
> +        ThreadingException[] errors =
> +            exceptions.toArray(new ThreadingException[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 = 1L;
> +        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");
> +    }
> +
> +}
> 
> 
> 

-- 
Thanks,
Tim McConnell