/*
* Copyright (c) 2010-2015 Evolveum
*
* Licensed 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 com.evolveum.midpoint.repo.sql;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SequenceType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import org.hibernate.Session;
import org.hibernate.jdbc.Work;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.testng.annotations.Test;
import java.io.File;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.fail;
/**
* @author Pavol Mederly
*/
@ContextConfiguration(locations = {"../../../../../ctx-test.xml"})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class SequenceTest extends BaseSQLRepoTest {
private static final Trace LOGGER = TraceManager.getTrace(SequenceTest.class);
private static final String TEST_DIR = "src/test/resources/sequence/";
private static final int STOP_TIMEOUT = 10000;
@Test
public void test001_OneThread() throws Exception {
WorkerThread[] mts = new WorkerThread[] {
new WorkerThread(1)
};
concurrencyUniversal("Test1", "sequence-unbound.xml", 10000L, mts, false);
}
@Test
public void test002_TwoThreads() throws Exception {
WorkerThread[] mts = new WorkerThread[] {
new WorkerThread(1),
new WorkerThread(2)
};
concurrencyUniversal("Test2", "sequence-unbound.xml", 10000L, mts, false);
}
@Test
public void test003_TenThreads() throws Exception {
WorkerThread[] mts = new WorkerThread[] {
new WorkerThread(1),
new WorkerThread(2),
new WorkerThread(3),
new WorkerThread(4),
new WorkerThread(5),
new WorkerThread(6),
new WorkerThread(7),
new WorkerThread(8),
new WorkerThread(9),
new WorkerThread(10)
};
concurrencyUniversal("Test3", "sequence-unbound.xml", 10000L, mts, false);
}
@Test
public void test010_ReturningValues() throws Exception {
OperationResult result = new OperationResult("test010_ReturningValues");
final File file = new File(TEST_DIR + "sequence-bound-returned-wrapped.xml");
PrismObject<SequenceType> sequence = prismContext.parseObject(file);
String oid = repositoryService.addObject(sequence, null, result);
assertEquals(0L, repositoryService.advanceSequence(oid, result));
assertEquals(1L, repositoryService.advanceSequence(oid, result));
assertEquals(2L, repositoryService.advanceSequence(oid, result));
assertEquals(3L, repositoryService.advanceSequence(oid, result));
assertEquals(4L, repositoryService.advanceSequence(oid, result));
repositoryService.returnUnusedValuesToSequence(oid, Arrays.asList(2L, 4L), result);
assertEquals(2L, repositoryService.advanceSequence(oid, result));
assertEquals(4L, repositoryService.advanceSequence(oid, result));
assertEquals(5L, repositoryService.advanceSequence(oid, result));
assertEquals(6L, repositoryService.advanceSequence(oid, result));
repositoryService.returnUnusedValuesToSequence(oid, null, result);
repositoryService.returnUnusedValuesToSequence(oid, new ArrayList<Long>(), result);
repositoryService.returnUnusedValuesToSequence(oid, Arrays.asList(6L), result);
assertEquals(6L, repositoryService.advanceSequence(oid, result));
repositoryService.returnUnusedValuesToSequence(oid, Arrays.asList(0L, 1L, 2L, 3L, 4L, 5L, 6L), result); // only 0-4 will be returned
assertEquals(0L, repositoryService.advanceSequence(oid, result));
assertEquals(1L, repositoryService.advanceSequence(oid, result));
assertEquals(2L, repositoryService.advanceSequence(oid, result));
assertEquals(3L, repositoryService.advanceSequence(oid, result));
assertEquals(4L, repositoryService.advanceSequence(oid, result));
assertEquals(7L, repositoryService.advanceSequence(oid, result));
assertEquals(8L, repositoryService.advanceSequence(oid, result));
assertEquals(9L, repositoryService.advanceSequence(oid, result));
assertEquals(0L, repositoryService.advanceSequence(oid, result));
assertEquals(1L, repositoryService.advanceSequence(oid, result));
assertEquals(2L, repositoryService.advanceSequence(oid, result));
}
@Test
public void test020_ReachingLimit() throws Exception {
OperationResult result = new OperationResult("test020_ReachingLimit");
final File file = new File(TEST_DIR + "sequence-bound.xml");
PrismObject<SequenceType> sequence = prismContext.parseObject(file);
String oid = repositoryService.addObject(sequence, null, result);
assertEquals(0L, repositoryService.advanceSequence(oid, result));
assertEquals(1L, repositoryService.advanceSequence(oid, result));
assertEquals(2L, repositoryService.advanceSequence(oid, result));
assertEquals(3L, repositoryService.advanceSequence(oid, result));
assertEquals(4L, repositoryService.advanceSequence(oid, result));
assertEquals(5L, repositoryService.advanceSequence(oid, result));
assertEquals(6L, repositoryService.advanceSequence(oid, result));
assertEquals(7L, repositoryService.advanceSequence(oid, result));
assertEquals(8L, repositoryService.advanceSequence(oid, result));
assertEquals(9L, repositoryService.advanceSequence(oid, result));
try {
long value = repositoryService.advanceSequence(oid, result);
fail("Expected an exception, got value of " + value);
} catch (SystemException e) {
// ok
}
}
@Test
public void test031_OneThreadReturning() throws Exception {
WorkerThread[] mts = new WorkerThread[] {
new WorkerThread(1, 5)
};
concurrencyUniversal("Test031", "sequence-unbound.xml", 10000L, mts, true);
}
@Test
public void test032_TwoThreadsReturning() throws Exception {
WorkerThread[] mts = new WorkerThread[] {
new WorkerThread(1, 5),
new WorkerThread(2, 5)
};
concurrencyUniversal("Test032", "sequence-unbound.xml", 10000L, mts, true);
}
@Test
public void test033_TenThreadsReturning() throws Exception {
WorkerThread[] mts = new WorkerThread[] {
new WorkerThread(1, 5),
new WorkerThread(2, 5),
new WorkerThread(3, 2),
new WorkerThread(4, 2),
new WorkerThread(5, 2),
new WorkerThread(6, 2),
new WorkerThread(7, 2),
new WorkerThread(8, 2),
new WorkerThread(9, 0),
new WorkerThread(10, 0)
};
concurrencyUniversal("Test033", "sequence-unbound.xml", 10000L, mts, true);
}
private void concurrencyUniversal(String name, String sequenceFileName, long duration, WorkerThread[] workerThreads, boolean alwaysOrder) throws Exception {
Session session = getFactory().openSession();
session.doWork(new Work() {
@Override
public void execute(Connection connection) throws SQLException {
System.out.println(">>>>" + connection.getTransactionIsolation());
}
});
session.close();
final File file = new File(TEST_DIR + sequenceFileName);
PrismObject<SequenceType> sequence = prismContext.parseObject(file);
sequence.asObjectable().setName(new PolyStringType(name));
OperationResult result = new OperationResult("Concurrency Test");
String oid = repositoryService.addObject(sequence, null, result);
LOGGER.info("*** Object added: " + oid + " ***");
LOGGER.info("*** Starting modifier threads ***");
for (WorkerThread t : workerThreads) {
t.setOid(oid);
t.start();
}
LOGGER.info("*** Waiting " + duration + " ms ***");
Thread.sleep(duration);
for (WorkerThread t : workerThreads) {
t.stop = true;
}
long endTime = System.currentTimeMillis() + STOP_TIMEOUT;
for (;;) {
long remaining = endTime - System.currentTimeMillis();
if (remaining <= 0) {
break;
}
for (WorkerThread t : workerThreads) {
t.join(remaining);
remaining = endTime - System.currentTimeMillis();
if (remaining <= 0) {
break;
}
}
}
for (WorkerThread t : workerThreads) {
LOGGER.info("Worker thread {} finished after {} iterations with result: {}", t.id, t.counter, t.threadResult != null ? t.threadResult : "OK");
}
for (WorkerThread t : workerThreads) {
if (t.threadResult != null) {
throw new AssertionError("Worker thread " + t.id + " finished with an exception: " + t.threadResult, t.threadResult);
}
}
List<Long> allValues = new ArrayList<>();
for (WorkerThread t : workerThreads) {
allValues.addAll(t.values);
}
if (alwaysOrder || workerThreads.length > 1) {
Collections.sort(allValues);
}
LOGGER.trace("Checking a list of {} values", allValues.size());
for (int i = 0; i < allValues.size(); i++) {
if (allValues.get(i) != i) {
LOGGER.error("Incorrect value at position {}: {}", i, allValues.get(i));
for (WorkerThread t : workerThreads) {
LOGGER.info("Thread {}: {}", t.id, t.values);
}
fail("Incorrect value at position " + i + ": " + allValues.get(i));
}
}
}
class WorkerThread extends Thread {
int id;
String oid; // sequence to use
List<Long> values = new ArrayList<>();
volatile Throwable threadResult;
volatile int counter = 0;
int returnEach;
int countToReturn;
WorkerThread(int id, int returnEach) {
this.id = id;
this.returnEach = returnEach;
this.countToReturn = returnEach;
}
WorkerThread(int id) {
this(id, 0);
}
public volatile boolean stop = false;
@Override
public void run() {
try {
while (!stop) {
runOnce();
counter++;
}
} catch (Throwable t) {
LoggingUtils.logException(LOGGER, "Unexpected exception: " + t, t);
threadResult = t;
}
}
public void runOnce() throws SchemaException, ObjectNotFoundException {
OperationResult result = new OperationResult("run");
long value = repositoryService.advanceSequence(oid, result);
LOGGER.debug("Advance sequence returned {}", value);
values.add(value);
if (returnEach > 0) {
if (countToReturn > 0) {
countToReturn--;
} else {
countToReturn = returnEach;
int i = (int) (Math.random() * values.size());
long v = values.remove(i);
repositoryService.returnUnusedValuesToSequence(oid, Arrays.asList(v), result);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
value = repositoryService.advanceSequence(oid, result);
LOGGER.debug("Advance sequence returned {} (after return)", value);
values.add(value);
}
}
}
public void setOid(String oid) {
this.oid = oid;
}
}
}