| /* |
| * 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: |
| // Oracle - initial API and implementation from Oracle TopLink |
| package org.eclipse.persistence.testing.tests.feature; |
| |
| import java.util.Comparator; |
| import java.util.Arrays; |
| |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.logging.SessionLog; |
| import org.eclipse.persistence.sessions.*; |
| import org.eclipse.persistence.sessions.server.Server; |
| |
| import org.eclipse.persistence.testing.framework.TestCase; |
| import org.eclipse.persistence.testing.models.employee.domain.Employee; |
| |
| /** |
| * This testcase test the thread-safeness of TopLink's sequencing. The type of sequencing specified in the login |
| * info is tested (either native or table sequencing is used). To test the other kind of sequencing you must logout, |
| * change you login info, login, and rerun the test. |
| * The test start a number of threads. Each thread generates a number of sequence numbers. For this test the |
| * preallocation size is set to 1. This maximizes the concurrency of the threads and increases the effectiveness |
| * of this test. |
| * The choice of number of threads and iterations must be large enough for the test to be effective. I got this test |
| * to fail (with the 2.5.1.1 sequences bug) with 5 threads and 50 iterations. |
| * |
| * Caveat: This test cannot prove that sequencing is thread safe, it can only prove that it is not. |
| * |
| * author: Robert Campbell |
| */ |
| public class SequencingConcurrencyTest extends TestCase implements Comparator { |
| public java.util.Vector sequences; |
| public SequencingConcurrencyTest[] tests; |
| public int nThreads; |
| public boolean useServerSession; |
| public boolean useSeparateConnection; |
| public Server serverSession; |
| public DatabaseSession dbSession; |
| public int previousSequencePreallocationSize; |
| public int sequencePreallocationSize; |
| public boolean useSeparateConnectionOriginal; |
| public boolean shouldLogMessages; |
| public int originalLogLevel; |
| public int nIterations; |
| public Session session; |
| public int threadNumber; |
| public boolean handleException; |
| public Exception exception; |
| |
| protected SequencingConcurrencyTest(int threadNumber, int nIterations, java.util.Vector sequences, Session session, boolean handleException) { |
| this.threadNumber = threadNumber; |
| this.nIterations = nIterations; |
| this.sequences = sequences; |
| this.session = session; |
| this.handleException = handleException; |
| } |
| |
| /** |
| * To be called by testing framework. |
| */ |
| public SequencingConcurrencyTest(int nThreads, int nIterations, boolean useServerSession, boolean useSeparateConnection) { |
| this(nThreads, nIterations, useServerSession, useSeparateConnection, 1); |
| } |
| |
| /** |
| * To be called by testing framework. |
| */ |
| public SequencingConcurrencyTest(int nThreads, int nIterations, boolean useServerSession, boolean useSeparateConnection, int sequencePreallocationSize) { |
| this.nThreads = nThreads; |
| this.nIterations = nIterations; |
| this.useServerSession = useServerSession; |
| this.useSeparateConnection = useSeparateConnection; |
| String sessionUsed; |
| if (useServerSession) { |
| sessionUsed = "ServerSession"; |
| } else { |
| sessionUsed = "DatabaseSession"; |
| } |
| shouldLogMessages = false; |
| this.sequencePreallocationSize = sequencePreallocationSize; |
| setName(getName() + " " + sessionUsed + " separateConnection=" + useSeparateConnection + " seqPreallocSize=" + sequencePreallocationSize + " threads=" + nThreads + " iterations=" + nIterations); |
| } |
| |
| /** |
| * Compare the two BigDecimal, for using TOPSort. |
| */ |
| @Override |
| public int compare(Object b1, Object b2) { |
| java.math.BigDecimal big1 = new java.math.BigDecimal(((Number)b1).longValue()); |
| java.math.BigDecimal big2 = new java.math.BigDecimal(((Number)b2).longValue()); |
| return big1.compareTo(big2); |
| } |
| |
| /** |
| * Sets the logging and the sequence preallocation size the way it was. |
| */ |
| @Override |
| public void reset() { |
| if (useServerSession) { |
| if (serverSession != null) { |
| serverSession.logout(); |
| serverSession = null; |
| } |
| } else { |
| if (dbSession != null) { |
| // dbSession.getSequencingControl().setPreallocationSize(this.previousSequencePreallocationSize); |
| dbSession.getSequencingControl().initializePreallocated(); |
| if (useSeparateConnection != useSeparateConnectionOriginal) { |
| dbSession.getSequencingControl().setShouldUseSeparateConnection(useSeparateConnectionOriginal); |
| dbSession.getSequencingControl().resetSequencing(); |
| } |
| dbSession.setLogLevel(originalLogLevel); |
| dbSession = null; |
| } |
| } |
| } |
| |
| public Runnable runnable() { |
| return new Runnable() { |
| |
| /** |
| * Each thread does this: gets nIterations number of sequence numbers and puts them in a thread specific array. |
| */ |
| @Override |
| public void run() { |
| // Test |
| Number[] sequence = (Number[])sequences.elementAt(threadNumber); |
| try { |
| if (handleException) { |
| for (int i = 0; i < nIterations; i++) { |
| try { |
| sequence[i] = (Number)((AbstractSession)session).getSequencing().getNextValue(org.eclipse.persistence.testing.models.employee.domain.Employee.class); |
| } catch (org.eclipse.persistence.exceptions.ConcurrencyException ex) { |
| if (ex.getErrorCode() == org.eclipse.persistence.exceptions.ConcurrencyException.SEQUENCING_MULTITHREAD_THRU_CONNECTION) { |
| // that's an acceptable exception, try again. |
| i--; |
| } else { |
| throw ex; |
| } |
| } |
| } |
| } else { |
| for (int i = 0; i < nIterations; i++) { |
| sequence[i] = (Number)((AbstractSession)session).getSequencing().getNextValue(org.eclipse.persistence.testing.models.employee.domain.Employee.class); |
| } |
| } |
| } catch (Exception ex2) { |
| exception = ex2; |
| } finally { |
| if (session.isClientSession()) { |
| session.release(); |
| } |
| } |
| } |
| }; |
| } |
| |
| @Override |
| public void setup() { |
| if (getAbstractSession().getDescriptor(Employee.class).getSequence().shouldAcquireValueAfterInsert()) { |
| throw new org.eclipse.persistence.testing.framework.TestWarningException("Not a valid test against databases where the native sequencing is done entirely in the database."); |
| } |
| |
| // Setup |
| dbSession = (DatabaseSession)getSession(); |
| if (useServerSession) { |
| int numConnections = java.lang.Math.min(nThreads, 5); |
| serverSession = new Project(getSession().getDatasourceLogin().clone()).createServerSession(numConnections, numConnections); |
| serverSession.addDescriptors(new org.eclipse.persistence.testing.models.employee.relational.EmployeeProject()); |
| // serverSession.getSequencingControl().setPreallocationSize(sequencePreallocationSize); |
| serverSession.getSequencingControl().setShouldUseSeparateConnection(useSeparateConnection); |
| serverSession.setSessionLog(getSession().getSessionLog()); |
| if (shouldLogMessages) { |
| serverSession.setLogLevel(SessionLog.FINE); |
| } |
| serverSession.login(); |
| } else { |
| originalLogLevel = dbSession.getLogLevel(); |
| if (shouldLogMessages) { |
| dbSession.setLogLevel(SessionLog.FINE); |
| } |
| // this.previousSequencePreallocationSize = dbSession.getSequencingControl().getPreallocationSize(); |
| // dbSession.getSequencingControl().setPreallocationSize(sequencePreallocationSize); |
| dbSession.getSequencingControl().resetSequencing(); |
| dbSession.getSequencingControl().initializePreallocated(); |
| useSeparateConnectionOriginal = dbSession.getSequencingControl().shouldUseSeparateConnection(); |
| if (useSeparateConnection != useSeparateConnectionOriginal) { |
| dbSession.getSequencingControl().setShouldUseSeparateConnection(useSeparateConnection); |
| dbSession.getSequencingControl().resetSequencing(); |
| } |
| } |
| |
| // Setup the arrays of BigDecimals to be filled. |
| sequences = new java.util.Vector(nThreads); |
| for (int i = 0; i < nThreads; i++) { |
| sequences.addElement(new Number[nIterations]); |
| } |
| } |
| |
| /** |
| * Start each thread, then wait until all of them are finished. |
| */ |
| @Override |
| public void test() { |
| // |
| Thread[] threads = new Thread[nThreads]; |
| tests = new SequencingConcurrencyTest[nThreads]; |
| Session writeSession = getSession(); |
| boolean handleException = !useServerSession && !useSeparateConnection; |
| for (int i = 0; i < nThreads; i++) { |
| if (useServerSession) { |
| writeSession = serverSession.acquireClientSession(); |
| } |
| tests[i] = new SequencingConcurrencyTest(i, nIterations, sequences, writeSession, handleException); |
| threads[i] = new Thread(tests[i].runnable()); |
| threads[i].start(); |
| } |
| |
| // Join with all the threads so we don't proceed until all the threads have finished. |
| for (int i = 0; i < nThreads; i++) { |
| try { |
| threads[i].join(); |
| } catch (Exception ex) { |
| throw new org.eclipse.persistence.testing.framework.TestErrorException(ex.getMessage()); |
| } |
| } |
| } |
| |
| /** |
| * Make sure that the sequence numbers generated contained no gaps or duplicates. |
| */ |
| @Override |
| public void verify() { |
| // Verify |
| for (int i = 0; i < nThreads; i++) { |
| if (tests[i].exception != null) { |
| throw new org.eclipse.persistence.testing.framework.TestErrorException("exception in thread " + i + ", session(" + System.identityHashCode(session) + ") ", tests[i].exception); |
| } |
| } |
| |
| // Put all the sequences into one big array. |
| Number[] big = new Number[nThreads * nIterations]; |
| for (int i = 0; i < nThreads; i++) { |
| System.arraycopy(sequences.elementAt(i), 0, big, i * nIterations, nIterations); |
| } |
| try { |
| // sort the array. |
| Arrays.sort(big, this); |
| |
| // Verify that there are no duplicates or gaps in the array. |
| Number previous = big[0]; |
| for (int i = 1; i < (nIterations * nThreads); i++) { |
| Number current = big[i]; |
| if ((previous.intValue() + 1) != current.intValue()) { |
| throw new org.eclipse.persistence.testing.framework.TestErrorException("Gap in sequencing, or incorrect sequences generated."); |
| } |
| previous = current; |
| } |
| } catch (Exception ex) { |
| throw new org.eclipse.persistence.testing.framework.TestErrorException(ex.getMessage()); |
| } |
| } |
| } |