package com.ldbc.driver.runtime.coordination;
import java.util.ArrayList;
import java.util.List;
import static java.lang.String.format;
/**
* Completion time is the point in time AT which there are no uncompleted events.
* It is not possible that there are uncompleted events AT that time.
* <p/>
* Approximately --> Completion Time = min( min(Initiated Events), max(Completed Events) )
* <p/>
* But not exactly, as Completion Time is ALWAYS lower than min(Initiated Events).
* <p/>
* This class performs the logic of tracking local completion time.
* It can be written to by multiple threads in a thread-safe manner.
*/
public class MultiWriterLocalCompletionTimeConcurrentStateManager implements LocalCompletionTimeReader
{
private enum Event
{
READ_LAST_KNOWN_LIT,
READ_LCT,
WRITE_LIT,
WRITE_LCT,
ADD_WRITER
}
private final List<LocalCompletionTimeReaderWriter> localCompletionTimeReaderWriters = new ArrayList<>();
private long localCompletionTimeAsMilli = -1;
private long localInitiationTimeAsMilli = -1;
MultiWriterLocalCompletionTimeConcurrentStateManager()
{
}
@Override
public long lastKnownLowestInitiatedTimeAsMilli() throws CompletionTimeException
{
return (long) processEvent( Event.READ_LAST_KNOWN_LIT, -1, -1 );
}
@Override
public long localCompletionTimeAsMilli() throws CompletionTimeException
{
return (long) processEvent( Event.READ_LCT, -1, -1 );
}
void submitLocalInitiatedTime( int writerId, long scheduledStartTimeAsMilli ) throws CompletionTimeException
{
processEvent( Event.WRITE_LIT, writerId, scheduledStartTimeAsMilli );
}
void submitLocalCompletedTime( int writerId, long scheduledStartTimeAsMilli ) throws CompletionTimeException
{
processEvent( Event.WRITE_LCT, writerId, scheduledStartTimeAsMilli );
}
/**
* IMPORTANT: not safe to call after LIT/LCT times have been submitted, as it will likely put LCT in invalid state
*
* @return new writer
* @throws CompletionTimeException
*/
LocalCompletionTimeWriter newLocalCompletionTimeWriter() throws CompletionTimeException
{
return (LocalCompletionTimeWriter) processEvent( Event.ADD_WRITER, -1, -1 );
}
synchronized private Object processEvent( Event event, int writerId, long scheduledStartTimeAsMilli )
throws CompletionTimeException
{
switch ( event )
{
case READ_LAST_KNOWN_LIT:
{
return localInitiationTimeAsMilli;
}
case READ_LCT:
{
return localCompletionTimeAsMilli;
}
case WRITE_LIT:
{
LocalCompletionTimeWriter localCompletionTimeWriter = localCompletionTimeReaderWriters.get( writerId );
if ( null == localCompletionTimeWriter )
{ throw new CompletionTimeException( format( "Writer ID %s does not exist", writerId ) ); }
localCompletionTimeWriter.submitLocalInitiatedTime( scheduledStartTimeAsMilli );
updateCompletionTime();
return null;
}
case WRITE_LCT:
{
LocalCompletionTimeWriter localCompletionTimeWriter = localCompletionTimeReaderWriters.get( writerId );
if ( null == localCompletionTimeWriter )
{ throw new CompletionTimeException( format( "Writer ID %s does not exist", writerId ) ); }
localCompletionTimeWriter.submitLocalCompletedTime( scheduledStartTimeAsMilli );
updateCompletionTime();
return null;
}
case ADD_WRITER:
{
int nextWriterId = localCompletionTimeReaderWriters.size();
LocalCompletionTimeReaderWriter localCompletionTimeReaderWriter = new LocalCompletionTimeStateManager();
LocalCompletionTimeWriter localCompletionTimeWriter =
new MultiWriterLocalCompletionTimeConcurrentStateManagerWriter( nextWriterId, this );
localCompletionTimeReaderWriters.add( localCompletionTimeReaderWriter );
return localCompletionTimeWriter;
}
default:
{
throw new CompletionTimeException( "This should never happen" );
}
}
}
private void updateCompletionTime() throws CompletionTimeException
{
long tempLocalInitiationTimeAsMilli = -1;
for ( int i = 0; i < localCompletionTimeReaderWriters.size(); i++ )
{
LocalCompletionTimeReader reader = localCompletionTimeReaderWriters.get( i );
long readerLocalInitiationTimeAsMilli = reader.lastKnownLowestInitiatedTimeAsMilli();
if ( -1 == readerLocalInitiationTimeAsMilli )
{
// if any initiation times are null, local initiation time and local completion time are undefined
return;
}
else if ( -1 == tempLocalInitiationTimeAsMilli ||
readerLocalInitiationTimeAsMilli < tempLocalInitiationTimeAsMilli )
{
tempLocalInitiationTimeAsMilli = readerLocalInitiationTimeAsMilli;
}
else
{
// reader has initiation time, but it is greater than minimum initiation time
}
}
localInitiationTimeAsMilli = tempLocalInitiationTimeAsMilli;
long tempLocalCompletionTimeAsMilli = localCompletionTimeAsMilli;
for ( int i = 0; i < localCompletionTimeReaderWriters.size(); i++ )
{
LocalCompletionTimeReader reader = localCompletionTimeReaderWriters.get( i );
long readerLocalCompletionTimeAsMilli = reader.localCompletionTimeAsMilli();
if ( -1 == readerLocalCompletionTimeAsMilli )
{
// reader has non-null initiation time and null completion time
// if at least one reader has non-null completion time it is still possible that local completion
// time is non-null
// initiation time already tells us that no more times will arrive BELOW that time
// continue checking completion times of other readers
}
else if ( readerLocalCompletionTimeAsMilli < tempLocalInitiationTimeAsMilli )
{
if ( -1 == tempLocalCompletionTimeAsMilli ||
readerLocalCompletionTimeAsMilli > tempLocalCompletionTimeAsMilli )
{
tempLocalCompletionTimeAsMilli = readerLocalCompletionTimeAsMilli;
}
}
else
{
// completion time must be lower than initiation time
// continue checking completion times of other readers
}
}
localCompletionTimeAsMilli = tempLocalCompletionTimeAsMilli;
}
}