package com.ldbc.driver.runtime.coordination;
import com.google.common.collect.Lists;
import com.google.common.collect.MinMaxPriorityQueue;
import com.google.common.collect.TreeMultiset;
import com.ldbc.driver.temporal.TemporalUtil;
import com.ldbc.driver.util.Function1;
import com.ldbc.driver.util.Function2;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
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 completion time. It is NOT thread-safe.
*/
public class LocalCompletionTimeStateManager implements LocalCompletionTimeReaderWriter
{
private long localCompletionTimeAsMilli = -1;
private final LocalInitiatedTimeTracker localInitiatedTimeTracker =
LocalInitiatedTimeTrackerImpl.createUsingTreeMultiSet();
private final LocalCompletedTimeTracker localCompletedTimeTracker =
LocalCompletedTimeTrackerImpl.createUsingTreeMultiSet();
private long lastKnownLowestInitiatedTimeAsMilli = -1;
LocalCompletionTimeStateManager()
{
}
@Override
public long lastKnownLowestInitiatedTimeAsMilli() throws CompletionTimeException
{
return lastKnownLowestInitiatedTimeAsMilli;
}
@Override
public long localCompletionTimeAsMilli()
{
return localCompletionTimeAsMilli;
}
/**
* Logs the new initiated time and updates completion time accordingly.
* NOTE, initiated times MUST be applied in ascending order!
*
* @param timeAsMilli
*/
@Override
public void submitLocalInitiatedTime( long timeAsMilli ) throws CompletionTimeException
{
lastKnownLowestInitiatedTimeAsMilli =
localInitiatedTimeTracker.addInitiatedTimeAndReturnLastKnownLowestTimeAsMilli( timeAsMilli );
updateCompletionTime();
}
/**
* Logs the new completed time and updates completion time accordingly.
*
* @param timeAsMilli
* @throws com.ldbc.driver.runtime.coordination.CompletionTimeException
*/
@Override
public void submitLocalCompletedTime( long timeAsMilli ) throws CompletionTimeException
{
lastKnownLowestInitiatedTimeAsMilli =
localInitiatedTimeTracker.removeTimeAndReturnLastKnownLowestTimeAsMilli( timeAsMilli );
localCompletedTimeTracker.addCompletedTimeAsMilli( timeAsMilli );
updateCompletionTime();
}
private void updateCompletionTime()
{
long highestSafeCompletedTimeAsMilli = localCompletedTimeTracker
.removeTimesLowerThanAndReturnHighestRemoved( lastKnownLowestInitiatedTimeAsMilli );
if ( -1 != highestSafeCompletedTimeAsMilli )
{ localCompletionTimeAsMilli = highestSafeCompletedTimeAsMilli; }
}
interface LocalCompletedTimeTracker
{
void addCompletedTimeAsMilli( long completedTimeAsMilli );
long removeTimesLowerThanAndReturnHighestRemoved( long timeAsMilli );
}
public interface LocalInitiatedTimeTracker
{
long addInitiatedTimeAndReturnLastKnownLowestTimeAsMilli( long initiatedTimeAsMilli )
throws CompletionTimeException;
long removeTimeAndReturnLastKnownLowestTimeAsMilli( long timeAsMilli ) throws CompletionTimeException;
long highestInitiatedTimeAsMilli();
int uncompletedInitiatedTimes();
}
static class LocalCompletedTimeTrackerImpl<INITIATED_TIMES_CONTAINER_TYPE extends Collection<Long>>
implements LocalCompletedTimeTracker
{
private final INITIATED_TIMES_CONTAINER_TYPE completedTimesAsMilli;
private final Function2<INITIATED_TIMES_CONTAINER_TYPE,Long,Long,RuntimeException>
removeTimesLowerThanAndReturnHighestRemovedFun;
static LocalCompletedTimeTrackerImpl createUsingTreeMultiSet()
{
Function2<TreeMultiset<Long>,Long,Long,RuntimeException> removeTimesLowerThanAndReturnHighestRemovedFun =
new Function2<TreeMultiset<Long>,Long,Long,RuntimeException>()
{
@Override
public Long apply( TreeMultiset<Long> completedTimesAsMilli, Long timeAsMilli )
{
long highestRemovedAsMilli = -1;
for ( long completedTimeAsMilli : completedTimesAsMilli )
{
if ( completedTimeAsMilli < timeAsMilli )
{
completedTimesAsMilli.remove( completedTimeAsMilli );
highestRemovedAsMilli = completedTimeAsMilli;
}
else
{
break;
}
}
return highestRemovedAsMilli;
}
};
return new LocalCompletedTimeTrackerImpl(
TreeMultiset.<Long>create(),
removeTimesLowerThanAndReturnHighestRemovedFun
);
}
static LocalCompletedTimeTrackerImpl createUsingArrayList()
{
Function2<ArrayList<Long>,Long,Long,RuntimeException> removeTimesLowerThanAndReturnHighestRemovedFun =
new Function2<ArrayList<Long>,Long,Long,RuntimeException>()
{
@Override
public Long apply( ArrayList<Long> completedTimes, Long timeAsMilli )
{
long highestRemovedAsMilli = -1;
Iterator<Long> completedTimesIterator = completedTimes.iterator();
while ( completedTimesIterator.hasNext() )
{
long completedTimeAsMilli = completedTimesIterator.next();
if ( completedTimeAsMilli < timeAsMilli )
{
completedTimesIterator.remove();
if ( -1 == highestRemovedAsMilli || completedTimeAsMilli > highestRemovedAsMilli )
{
highestRemovedAsMilli = completedTimeAsMilli;
}
}
}
return highestRemovedAsMilli;
}
};
return new LocalCompletedTimeTrackerImpl(
Lists.<Long>newArrayList(),
removeTimesLowerThanAndReturnHighestRemovedFun
);
}
private LocalCompletedTimeTrackerImpl(
INITIATED_TIMES_CONTAINER_TYPE completedTimesAsMilli,
Function2<INITIATED_TIMES_CONTAINER_TYPE,Long,Long,RuntimeException>
removeTimesLowerThanAndReturnHighestRemovedFun )
{
this.completedTimesAsMilli = completedTimesAsMilli;
this.removeTimesLowerThanAndReturnHighestRemovedFun = removeTimesLowerThanAndReturnHighestRemovedFun;
}
@Override
public void addCompletedTimeAsMilli( long completedTimeAsMilli )
{
completedTimesAsMilli.add( completedTimeAsMilli );
}
@Override
public long removeTimesLowerThanAndReturnHighestRemoved( long timeAsMilli )
{
return removeTimesLowerThanAndReturnHighestRemovedFun.apply( completedTimesAsMilli, timeAsMilli );
}
@Override
public String toString()
{
return "LocalCompletedTimeTrackerImpl{" +
"completedTimesAsMilli=" + completedTimesAsMilli.toString() +
'}';
}
}
static class LocalInitiatedTimeTrackerImpl<INITIATED_TIMES_CONTAINER_TYPE extends Collection<Long>>
implements LocalInitiatedTimeTracker
{
private final TemporalUtil temporalUtil = new TemporalUtil();
private final INITIATED_TIMES_CONTAINER_TYPE initiatedTimesAsMilli;
private final Function1<INITIATED_TIMES_CONTAINER_TYPE,Long,RuntimeException>
getLastKnownLowestInitiatedTimeFun;
private long lastKnownLowestInitiatedTimeAsMilli = -1;
private long highestInitiatedTimeAsMilli = -1;
private int uncompletedInitiatedTimes = 0;
static LocalInitiatedTimeTrackerImpl createUsingTreeMultiSet()
{
Function1<TreeMultiset<Long>,Long,RuntimeException> getLastKnownLowestInitiatedTimeFun =
new Function1<TreeMultiset<Long>,Long,RuntimeException>()
{
@Override
public Long apply( TreeMultiset<Long> initiatedTimesAsMilli )
{
return initiatedTimesAsMilli.firstEntry().getElement();
}
};
return new LocalInitiatedTimeTrackerImpl(
TreeMultiset.<Long>create(),
getLastKnownLowestInitiatedTimeFun
);
}
static LocalInitiatedTimeTrackerImpl createUsingArrayList()
{
Function1<List<Long>,Long,RuntimeException> getLastKnownLowestInitiatedTimeFun =
new Function1<List<Long>,Long,RuntimeException>()
{
@Override
public Long apply( List<Long> initiatedTimesAsMilli )
{
return initiatedTimesAsMilli.get( 0 );
}
};
return new LocalInitiatedTimeTrackerImpl(
Lists.<Long>newArrayList(),
getLastKnownLowestInitiatedTimeFun
);
}
static LocalInitiatedTimeTrackerImpl createUsingMinMaxPriorityQueue()
{
// TODO this seems to have a bug for larger values, don't use it
System.err.println( format(
"LocalInitiatedTimeTrackerImpl.createUsingMinMaxPriorityQueue() is buggy. When collection size is" +
" very large values seem to get dropped. DO NOT USE!" ) );
Function1<MinMaxPriorityQueue<Long>,Long,RuntimeException> getLastKnownLowestInitiatedTimeFun =
new Function1<MinMaxPriorityQueue<Long>,Long,RuntimeException>()
{
@Override
public Long apply( MinMaxPriorityQueue<Long> initiatedTimesAsMilli )
{
return initiatedTimesAsMilli.peekFirst();
}
};
return new LocalInitiatedTimeTrackerImpl(
MinMaxPriorityQueue.create(),
getLastKnownLowestInitiatedTimeFun
);
}
private LocalInitiatedTimeTrackerImpl(
INITIATED_TIMES_CONTAINER_TYPE initiatedTimesAsMilli,
Function1<INITIATED_TIMES_CONTAINER_TYPE,Long,RuntimeException> getLastKnownLowestInitiatedTimeFun )
{
this.initiatedTimesAsMilli = initiatedTimesAsMilli;
this.getLastKnownLowestInitiatedTimeFun = getLastKnownLowestInitiatedTimeFun;
}
@Override
public long addInitiatedTimeAndReturnLastKnownLowestTimeAsMilli( long initiatedTimeAsMilli )
throws CompletionTimeException
{
if ( -1 != highestInitiatedTimeAsMilli && initiatedTimeAsMilli < highestInitiatedTimeAsMilli )
{
String errMsg = format( "Submitted initiated time is lower than previously submitted initiated time\n"
+ " Submitted: %s (%s ms)\n"
+ " Previous: %s (%s ms)",
temporalUtil.milliTimeToDateTimeString( initiatedTimeAsMilli ), initiatedTimeAsMilli,
temporalUtil.milliTimeToDateTimeString( highestInitiatedTimeAsMilli ),
highestInitiatedTimeAsMilli
);
throw new CompletionTimeException( errMsg );
}
highestInitiatedTimeAsMilli = initiatedTimeAsMilli;
if ( 0 == uncompletedInitiatedTimes )
{ lastKnownLowestInitiatedTimeAsMilli = initiatedTimeAsMilli; }
initiatedTimesAsMilli.add( initiatedTimeAsMilli );
uncompletedInitiatedTimes++;
return lastKnownLowestInitiatedTimeAsMilli;
}
@Override
public long removeTimeAndReturnLastKnownLowestTimeAsMilli( long timeAsMilli ) throws CompletionTimeException
{
if ( initiatedTimesAsMilli.remove( timeAsMilli ) )
{
uncompletedInitiatedTimes--;
if ( 0 == uncompletedInitiatedTimes )
{ lastKnownLowestInitiatedTimeAsMilli = highestInitiatedTimeAsMilli; }
else
{
lastKnownLowestInitiatedTimeAsMilli =
getLastKnownLowestInitiatedTimeFun.apply( initiatedTimesAsMilli );
}
return lastKnownLowestInitiatedTimeAsMilli;
}
else
{
throw new CompletionTimeException( format(
"Initiated time [%s] of completed event does not map to any uncompleted operation",
timeAsMilli ) );
}
}
@Override
public long highestInitiatedTimeAsMilli()
{
return highestInitiatedTimeAsMilli;
}
@Override
public int uncompletedInitiatedTimes()
{
return uncompletedInitiatedTimes;
}
@Override
public String toString()
{
return "LocalInitiatedTimeTrackerImpl{" +
"initiatedTimesAsMilli=" + initiatedTimesAsMilli.toString() +
", lastKnownLowestInitiatedTimeAsMilli=" + lastKnownLowestInitiatedTimeAsMilli +
", lastKnownLowestInitiatedTimeAsMilli=" +
temporalUtil.milliTimeToDateTimeString( lastKnownLowestInitiatedTimeAsMilli ) +
", highestInitiatedTimeAsMilli=" + highestInitiatedTimeAsMilli +
", highestInitiatedTimeAsMilli=" +
temporalUtil.milliTimeToDateTimeString( highestInitiatedTimeAsMilli ) +
", uncompletedInitiatedTimes=" + uncompletedInitiatedTimes +
'}';
}
}
}