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.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
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.ISTMEntry;
import org.juxtapose.streamline.util.ISTMEntrySubscriber;
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 2, 2012
* Copyright (c) Pontus J�rgne. All rights reserved
*
* NonBlockngSTM is experimental ant not complete. It exhibits strange behavior and does not support DataTypeRef
* Use BlockingSTM
*/
/**
* @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 NonBlockingSTM extends STM
{
private static final int LOCK_ARR_SIZE = 512;
private static final int HASH_MASK = LOCK_ARR_SIZE -1;
private final AtomicReference<ReentrantSTMLock>[] m_hashLocks = new AtomicReference[LOCK_ARR_SIZE];
public void init( IExecutor inExecutor, boolean inMaster )
{
for( int i = 0; i < m_hashLocks.length; i++ )
{
ReentrantSTMLock lock = new ReentrantSTMLock();
AtomicReference<ReentrantSTMLock> lockRef = new AtomicReference<ReentrantSTMLock>( lock );
m_hashLocks[i] = lockRef;
}
super.init( inExecutor, inMaster );
}
private int getHash( String inString )
{
int hash = inString.hashCode();
return hash & HASH_MASK;
}
/**
* @param inKey
*/
protected void lock( String inKey )
{
lockOrUnlock( inKey, true );
}
/**
* @param inKey
*/
protected void unlock( String inKey )
{
lockOrUnlock( inKey, false );
}
/**
* @param inKey
* @param inLock
*/
private void lockOrUnlock( String inKey, boolean inLock )
{
int hash = getHash( inKey );
AtomicReference<ReentrantSTMLock> lockRef = m_hashLocks[hash];
ReentrantSTMLock newLock = null;
ReentrantSTMLock lock;
do
{
do
{
lock = lockRef.get();
newLock = inLock ? lock.accuire() : lock.release();
}
while( newLock == null );
}
while( !lockRef.compareAndSet( lock, newLock ));
}
/* (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();
}
}
public void updateSubscriberPriority( ISTMEntryKey inDataKey, ISTMEntrySubscriber inSubscriber )
{
lock( inDataKey.getKey() );
ISTMEntryProducer producer = null;
int newPriority = 0;
HashSet<TemporaryController> dependencies = null;
try
{
ISTMEntry existingData = keyToData.get( inDataKey.getKey() );
if( existingData == null )
{
//All Subscribers has left the building
return;
}
ISTMEntry newEntry = existingData.changeSubscriberPriority( inSubscriber, inSubscriber.getPriority() );
if( newEntry == null )
{
//Subscriber has left the entry
return;
}
if( existingData.getPriority() == newEntry.getPriority() )
{
//No side effects
return;
}
newPriority = newEntry.getPriority();
producer = newEntry.getProducer();
if( producer == null || producer.isDisposed() )
{
//producer is disposed
return;
}
dependencies = producer.getDependencyControllers();
}
catch( Throwable t )
{
logError( t.getMessage(), t );
}
finally
{
unlock( inDataKey.getKey() );
}
if( dependencies != null )
{
for( TemporaryController tc : dependencies )
{
tc.setPriority( newPriority );
}
}
}
}