package org.juxtapose.streamline.stm; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; import org.juxtapose.streamline.producer.ISTMEntryKey; import org.juxtapose.streamline.producer.ISTMEntryProducer; import org.juxtapose.streamline.producer.ISTMEntryProducerService; import org.juxtapose.streamline.producer.executor.IExecutor; import org.juxtapose.streamline.tools.STMAssertionUtil; import org.juxtapose.streamline.util.ISTMEntrySubscriber; import org.juxtapose.streamline.util.ISTMEntry; import org.juxtapose.streamline.util.Status; import org.juxtapose.streamline.util.data.DataType; import org.juxtapose.streamline.util.data.DataTypeRef; import com.trifork.clj_ds.IPersistentMap; /** * @author Pontus J�rgne * Jan 6, 2012 * Copyright (c) Pontus J�rgne. All rights reserved * STM implementation that uses locking as synchronization method around transactions */ public class BlockingSTM extends STM { //Fair locking implies that first come, first serve. Fair locking = false may lead to unwanted behavior on highly contended data public final boolean FAIR_LOCKING = true; private final ConcurrentHashMap<String, ReentrantLock> m_keyToLock = new ConcurrentHashMap<String, ReentrantLock>(); /** * @param inKey */ protected void lock( String inKey ) { boolean set = false; do { ReentrantLock lock = m_keyToLock.get( inKey ); if( lock != null ) { lock.lock(); set = m_keyToLock.replace( inKey, lock, lock ); if( ! set ) lock.unlock(); } else { lock = new ReentrantLock( FAIR_LOCKING ); lock.lock(); set = null == m_keyToLock.putIfAbsent( inKey, lock ); } }while( !set ); } /** * @param inKey */ protected void unlock( String inKey ) { ReentrantLock lock = m_keyToLock.get( inKey ); if( lock != null ) { lock.unlock(); } else { logError("Tried to unlock already disposed lock"); } } /* (non-Javadoc) * @see org.juxtapose.streamline.stm.impl.STM#commit(org.juxtapose.streamline.stm.impl.Transaction) */ public void commit( STMTransaction inTransaction ) { ISTMEntryKey dataKey = inTransaction.getDataKey(); ISTMEntry newData = null; ReferenceLink[] addedLinks = null; ReferenceLink[] removedLinks = null; TemporaryController[] removedDependencies = null; lock( dataKey.getKey() ); try { ISTMEntry existingData = keyToData.get( dataKey.getKey() ); if( existingData == null ) { //data has been removed due to lack of interest, transaction is discarded return; } if( !STMAssertionUtil.validateProducerToData(existingData, inTransaction) ) { logError( "Wrong version DataProducer tried to update data: "+dataKey ); //The producer for this data is of the wrong version, Transaction is discarded return; } inTransaction.putInitDataState( existingData.getDataMap(), existingData.getStatus() ); inTransaction.execute(); if( inTransaction.isDisposed() ) { return; } IPersistentMap<String, DataType<?>> inst = inTransaction.getStateInstruction(); Set<String> delta = inTransaction.getDeltaState(); if( !existingData.isCompleteVersion() ) { /**If previous update was a partial update we need to merge the deltas**/ delta.addAll( existingData.getDeltaSet() ); } newData = existingData.setUpdatedData( inst, delta, inTransaction.isCompleteStateTransition() ); keyToData.put( dataKey.getKey(), newData ); if( inTransaction.containesReferenceInstructions() ) { //Init reference links Map< String, DataTypeRef > dataReferences = inTransaction.getAddedReferences(); addedLinks = dataReferences == null ? null : new ReferenceLink[ dataReferences.size() ]; if( dataReferences == null || !dataReferences.isEmpty() ) { ISTMEntryProducer producer = newData.getProducer(); if( producer == null ) logError( "Tried to add reference to data with null producer" ); else { int i = 0; for( String fieldKey : dataReferences.keySet() ) { DataTypeRef ref = dataReferences.get( fieldKey ); ReferenceLink refLink = new ReferenceLink( producer, this, fieldKey, ref ); producer.addDataReferences( fieldKey, refLink ); addedLinks[i] = refLink; i++; } } } //Dispose reference links List< String > removedReferences = inTransaction.getRemovedReferences(); removedLinks = removedReferences == null ? null : new ReferenceLink[ removedReferences.size() ]; if( removedReferences != null && !removedReferences.isEmpty() ) { ISTMEntryProducer producer = newData.getProducer(); if( producer == null ) logError( "Tried to remove reference from data with null producer" ); else { int i = 0; for( String fieldKey : dataReferences.keySet() ) { ReferenceLink refLink = producer.removeReferenceLink( fieldKey ); if( refLink == null ) { logError( "Tried to remove reference Link that is not stored in the producer" ); } removedLinks[i] = refLink; i++; } } } } if( inTransaction instanceof DependencyTransaction ) { ISTMEntryProducer producer = newData.getProducer(); if( producer == null ) { logError( "Tried to add dependency to data with null producer" ); return; } removedDependencies = prepareDependencies( (DependencyTransaction)inTransaction, producer ); } }catch( Throwable t ) { logError( t.getMessage(), t ); } finally { unlock( dataKey.getKey() ); } if( newData != null ) { /**Incomplete stateTransition does not go out as an update to subscribers**/ if( inTransaction.isCompleteStateTransition() ) { newData.updateSubscribers( dataKey, inTransaction.isFullUpdate() ); } } if( inTransaction.containesReferenceInstructions() ) { if( addedLinks != null ) { for( ReferenceLink link : addedLinks ) { link.init(); } } if( removedLinks != null ) { for( ReferenceLink link : removedLinks ) { link.dispose(); } } } if( inTransaction instanceof DependencyTransaction ) { executeDependencies( (DependencyTransaction)inTransaction, newData.getProducer(), removedDependencies ); } } /** * @param inTransaction * @param inProducer * @return */ private TemporaryController[] prepareDependencies( DependencyTransaction inTransaction, ISTMEntryProducer inProducer ) { Map< String, TemporaryController > dependencies = inTransaction.getAddedDependencies(); for( String key : dependencies.keySet() ) { TemporaryController controller = dependencies.get( key ); inProducer.addDependency( key, controller ); } List< String > removedDependencies = inTransaction.getRemovedDependencies(); TemporaryController[] removedControllers = new TemporaryController[removedDependencies.size()]; int i = 0; for( String key : removedDependencies ) { TemporaryController controller = inProducer.removeDependency( key ); removedControllers[i] = controller; i++; } return removedControllers; } /** * @param inTransaction * @param inProducer */ private void executeDependencies( DependencyTransaction inTransaction, ISTMEntryProducer inProducer, TemporaryController[] inRemovedDependencies ) { Map< String, TemporaryController > dependencies = inTransaction.getAddedDependencies(); for( TemporaryController controller : dependencies.values() ) { controller.init(); } for( TemporaryController controller : inRemovedDependencies ) { controller.dispose(); } } }