package com.ldbc.driver.runtime.coordination;
import com.ldbc.driver.runtime.ConcurrentErrorReporter;
import com.ldbc.driver.runtime.DefaultQueues;
import com.ldbc.driver.runtime.QueueEventSubmitter;
import com.ldbc.driver.runtime.scheduling.Spinner;
import com.ldbc.driver.temporal.TemporalUtil;
import com.ldbc.driver.temporal.TimeSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import static java.lang.String.format;
public class ThreadedQueuedCompletionTimeService implements CompletionTimeService
{
private static final long SHUTDOWN_WAIT_TIMEOUT_AS_MILLI = TimeUnit.SECONDS.toMillis( 10 );
private final TimeSource timeSource;
private final QueueEventSubmitter<CompletionTimeEvent> queueEventSubmitter;
private final AtomicLong sharedGctReference;
private final AtomicLong sharedWriteEventCountReference;
private final ThreadedQueuedConcurrentCompletionTimeServiceThread
threadedQueuedConcurrentCompletionTimeServiceThread;
private final AtomicBoolean sharedIsShuttingDownReference = new AtomicBoolean( false );
private final ConcurrentErrorReporter errorReporter;
private final List<LocalCompletionTimeWriter> writers = new ArrayList<>();
ThreadedQueuedCompletionTimeService( TimeSource timeSource,
Set<String> peerIds,
ConcurrentErrorReporter errorReporter ) throws CompletionTimeException
{
this.timeSource = timeSource;
this.errorReporter = errorReporter;
Queue<CompletionTimeEvent> completionTimeEventQueue = DefaultQueues.newBlockingBounded( 10000 );
this.queueEventSubmitter = QueueEventSubmitter.queueEventSubmitterFor( completionTimeEventQueue );
this.sharedGctReference = new AtomicLong( -1 );
this.sharedWriteEventCountReference = new AtomicLong( 0 );
threadedQueuedConcurrentCompletionTimeServiceThread = new ThreadedQueuedConcurrentCompletionTimeServiceThread(
completionTimeEventQueue,
errorReporter,
peerIds,
sharedGctReference );
threadedQueuedConcurrentCompletionTimeServiceThread.start();
}
@Override
public long globalCompletionTimeAsMilli()
{
return sharedGctReference.get();
}
@Override
public LocalCompletionTimeWriter newLocalCompletionTimeWriter() throws CompletionTimeException
{
long futureTimeoutDurationAsMilli = TimeUnit.MINUTES.toMillis( 1 );
try
{
LocalCompletionTimeWriterFuture future = new LocalCompletionTimeWriterFuture( timeSource );
queueEventSubmitter.submitEventToQueue( CompletionTimeEvent.newLocalCompletionTimeWriter( future ) );
int writerId;
try
{
writerId = future.get( futureTimeoutDurationAsMilli, TimeUnit.MILLISECONDS );
LocalCompletionTimeWriter writer = new ThreadedQueuedLocalCompletionTimeWriter(
writerId,
sharedIsShuttingDownReference,
sharedWriteEventCountReference,
queueEventSubmitter );
writers.add( writer );
return writer;
}
catch ( TimeoutException e )
{
// do nothing
throw new CompletionTimeException( "Timeout while waiting for creation of completion time writer" );
}
}
catch ( Exception e )
{
String errMsg = format( "Error requesting new local completion time writer" );
throw new CompletionTimeException( errMsg, e );
}
}
@Override
synchronized public Future<Long> globalCompletionTimeAsMilliFuture() throws CompletionTimeException
{
try
{
GlobalCompletionTimeFuture future = new GlobalCompletionTimeFuture( timeSource );
queueEventSubmitter.submitEventToQueue( CompletionTimeEvent.globalCompletionTimeFuture( future ) );
return future;
}
catch ( Exception e )
{
String errMsg = format( "Error requesting GCT future" );
throw new CompletionTimeException( errMsg, e );
}
}
@Override
public List<LocalCompletionTimeWriter> getAllWriters() throws CompletionTimeException
{
return writers;
}
@Override
synchronized public void submitPeerCompletionTime( String peerId, long timeAsMilli ) throws CompletionTimeException
{
try
{
sharedWriteEventCountReference.incrementAndGet();
queueEventSubmitter
.submitEventToQueue( CompletionTimeEvent.writeExternalCompletionTime( peerId, timeAsMilli ) );
}
catch ( Exception e )
{
String errMsg = format( "Error submitting external completion time for PeerID[%s] Time[%s]", peerId,
timeAsMilli );
throw new CompletionTimeException( errMsg, e );
}
}
@Override
synchronized public void shutdown() throws CompletionTimeException
{
if ( sharedIsShuttingDownReference.get() )
{
return;
}
sharedIsShuttingDownReference.set( true );
long pollingIntervalAsMilli = 100;
long shutdownTimeoutTimeAsMilli = timeSource.nowAsMilli() + SHUTDOWN_WAIT_TIMEOUT_AS_MILLI;
try
{
queueEventSubmitter
.submitEventToQueue( CompletionTimeEvent.terminateService( sharedWriteEventCountReference.get() ) );
}
catch ( InterruptedException e )
{
throw new CompletionTimeException( "Encountered error while writing TERMINATE event to queue" );
}
while ( timeSource.nowAsMilli() < shutdownTimeoutTimeAsMilli )
{
if ( threadedQueuedConcurrentCompletionTimeServiceThread.shutdownComplete() )
{
return;
}
if ( errorReporter.errorEncountered() )
{
errorReporter.reportError( this, "Error encountered while shutting down" );
throw new CompletionTimeException( "Error encountered while shutting down" );
}
Spinner.powerNap( pollingIntervalAsMilli );
}
throw new CompletionTimeException( "Service took too long to shutdown" );
}
public static class ThreadedQueuedLocalCompletionTimeWriter implements LocalCompletionTimeWriter
{
private final int writerId;
private final AtomicBoolean sharedIsShuttingDownReference;
private final AtomicLong sharedWriteEventCountReference;
private final QueueEventSubmitter<CompletionTimeEvent> queueEventSubmitter;
ThreadedQueuedLocalCompletionTimeWriter( int writerId,
AtomicBoolean sharedIsShuttingDownReference,
AtomicLong sharedWriteEventCountReference,
QueueEventSubmitter<CompletionTimeEvent> queueEventSubmitter )
{
this.writerId = writerId;
this.sharedIsShuttingDownReference = sharedIsShuttingDownReference;
this.sharedWriteEventCountReference = sharedWriteEventCountReference;
this.queueEventSubmitter = queueEventSubmitter;
}
@Override
public void submitLocalInitiatedTime( long timeAsMilli ) throws CompletionTimeException
{
if ( sharedIsShuttingDownReference.get() )
{
throw new CompletionTimeException( "Can not submit initiated time after calling shutdown" );
}
try
{
sharedWriteEventCountReference.incrementAndGet();
queueEventSubmitter
.submitEventToQueue( CompletionTimeEvent.writeLocalInitiatedTime( writerId, timeAsMilli ) );
}
catch ( Exception e )
{
String errMsg = format( "Error submitting initiated time for Time[%s]", timeAsMilli );
throw new CompletionTimeException( errMsg, e );
}
}
@Override
public void submitLocalCompletedTime( long timeAsMilli ) throws CompletionTimeException
{
try
{
sharedWriteEventCountReference.incrementAndGet();
queueEventSubmitter
.submitEventToQueue( CompletionTimeEvent.writeLocalCompletedTime( writerId, timeAsMilli ) );
}
catch ( Exception e )
{
String errMsg = format( "Error submitting completed time for Time[%s]", timeAsMilli );
throw new CompletionTimeException( errMsg, e );
}
}
@Override
public String toString()
{
return "ThreadedQueuedLocalCompletionTimeWriter{" +
"writerId=" + writerId +
'}';
}
}
public static class GlobalCompletionTimeFuture implements Future<Long>
{
private static final TemporalUtil TEMPORAL_UTIL = new TemporalUtil();
private final TimeSource timeSource;
private final AtomicBoolean done = new AtomicBoolean( false );
private final AtomicLong globalCompletionTimeReference = new AtomicLong( -1 );
private GlobalCompletionTimeFuture( TimeSource timeSource )
{
this.timeSource = timeSource;
}
synchronized void set( long timeAsMilli ) throws CompletionTimeException
{
if ( done.get() )
{ throw new CompletionTimeException( "Value has already been set" ); }
this.globalCompletionTimeReference.set( timeAsMilli );
done.set( true );
}
@Override
public boolean cancel( boolean mayInterruptIfRunning )
{
throw new UnsupportedOperationException();
}
@Override
public boolean isCancelled()
{
return false;
}
@Override
public boolean isDone()
{
return done.get();
}
@Override
public Long get()
{
while ( done.get() == false )
{
// wait for value to be set
}
return globalCompletionTimeReference.get();
}
@Override
public Long get( long timeout, TimeUnit unit ) throws TimeoutException
{
long timeoutDurationAsMilli = unit.toMillis( timeout );
long timeoutTimeAsMilli = timeSource.nowAsMilli() + timeoutDurationAsMilli;
while ( timeSource.nowAsMilli() < timeoutTimeAsMilli )
{
// wait for value to be set
if ( done.get() )
{ return globalCompletionTimeReference.get(); }
}
throw new TimeoutException( "Could not complete future in time" );
}
}
public static class LocalCompletionTimeWriterFuture implements Future<Integer>
{
private final TimeSource timeSource;
private final AtomicBoolean done = new AtomicBoolean( false );
private final AtomicInteger writerId = new AtomicInteger();
private LocalCompletionTimeWriterFuture( TimeSource timeSource )
{
this.timeSource = timeSource;
}
synchronized void set( int value ) throws CompletionTimeException
{
if ( done.get() )
{ throw new CompletionTimeException( "Value has already been set" ); }
this.writerId.set( value );
done.set( true );
}
@Override
public boolean cancel( boolean mayInterruptIfRunning )
{
throw new UnsupportedOperationException();
}
@Override
public boolean isCancelled()
{
return false;
}
@Override
public boolean isDone()
{
return done.get();
}
@Override
public Integer get()
{
while ( done.get() == false )
{
// wait for value to be set
}
return writerId.get();
}
@Override
public Integer get( long timeout, TimeUnit unit ) throws TimeoutException
{
long timeoutDurationAsMilli = unit.toMillis( timeout );
long timeoutTimeAsMilli = timeSource.nowAsMilli() + timeoutDurationAsMilli;
while ( timeSource.nowAsMilli() < timeoutTimeAsMilli )
{
// wait for value to be set
if ( done.get() )
{ return writerId.get(); }
}
throw new TimeoutException( "Could not complete future in time" );
}
}
}