/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* 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 org.pentaho.di.concurrency;
import org.junit.Test;
import org.pentaho.di.trans.Trans;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* In this test we add new elements to shared transformation concurrently
* and get added elements from this transformation concurrently.
*
* When working with {@link java.util.HashMap} with default loadFactor this test will fail
* when HashMap will try to rearrange it's elements (it will happen when number of elements in map will be equal to
* capacity/loadFactor).
*
* Map will be in inconsistent state, because in the same time, when rearrange happens other threads will be adding
* new elements to map.
* This will lead to unpredictable result of executing {@link java.util.HashMap#size()} method (as a result there
* would be an error in {@link Getter#call()} ).
*
*/
public class ActiveSubTransformationsConcurrencyTest {
private static final int NUMBER_OF_GETTERS = 10;
private static final int NUMBER_OF_CREATES = 10;
private static final int NUMBER_OF_CREATE_CYCLES = 20;
private static final int INITIAL_NUMBER_OF_TRANS = 100;
private static final String TRANS_NAME = "transformation";
private final Object lock = new Object();
@Test
public void getAndCreateConcurrently() throws Exception {
AtomicBoolean condition = new AtomicBoolean( true );
Trans trans = new Trans();
createSubTransformations( trans );
List<Getter> getters = generateGetters( trans, condition );
List<Creator> creators = generateCreators( trans, condition );
ConcurrencyTestRunner.runAndCheckNoExceptionRaised( creators, getters, condition );
}
private void createSubTransformations( Trans trans ) {
for ( int i = 0; i < INITIAL_NUMBER_OF_TRANS; i++ ) {
trans.addActiveSubTransformation( createTransName( i ), new Trans() );
}
}
private List<Getter> generateGetters( Trans trans, AtomicBoolean condition ) {
List<Getter> getters = new ArrayList<>();
for ( int i = 0; i < NUMBER_OF_GETTERS; i++ ) {
getters.add( new Getter( trans, condition ) );
}
return getters;
}
private List<Creator> generateCreators( Trans trans, AtomicBoolean condition ) {
List<Creator> creators = new ArrayList<Creator>();
for ( int i = 0; i < NUMBER_OF_CREATES; i++ ) {
creators.add( new Creator( trans, condition ) );
}
return creators;
}
private class Getter extends StopOnErrorCallable<Object> {
private final Trans trans;
private final Random random;
Getter( Trans trans, AtomicBoolean condition ) {
super( condition );
this.trans = trans;
random = new Random();
}
@Override
Object doCall() throws Exception {
while ( condition.get() ) {
final String activeSubTransName = createTransName( random.nextInt( INITIAL_NUMBER_OF_TRANS ) );
Trans subTrans = trans.getActiveSubTransformation( activeSubTransName );
if ( subTrans == null ) {
throw new IllegalStateException(
String.format(
"Returned transformation must not be null. Transformation name = %s",
activeSubTransName ) );
}
}
return null;
}
}
private class Creator extends StopOnErrorCallable<Object> {
private final Trans trans;
private final Random random;
Creator( Trans trans, AtomicBoolean condition ) {
super( condition );
this.trans = trans;
random = new Random();
}
@Override
Object doCall() throws Exception {
for ( int i = 0; i < NUMBER_OF_CREATE_CYCLES; i++ ) {
synchronized ( lock ) {
String transName = createTransName( randomInt( INITIAL_NUMBER_OF_TRANS, Integer.MAX_VALUE ) );
trans.addActiveSubTransformation( transName, new Trans() );
}
}
return null;
}
private int randomInt( int min, int max ) {
return random.nextInt( max - min ) + min;
}
}
private String createTransName( int id ) {
return TRANS_NAME + " - " + id;
}
}