package com.ldbc.driver.runtime.coordination; import com.ldbc.driver.runtime.ConcurrentErrorReporter; import com.ldbc.driver.runtime.QueueEventFetcher; import com.ldbc.driver.temporal.TemporalUtil; import java.util.HashMap; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import static java.lang.String.format; public class ThreadedQueuedConcurrentCompletionTimeServiceThread extends Thread { /** * LocalCompletionTime: Completion Time of local instance, ignoring times received from peers * - WRITE here, RECEIVE from Workers, READ by PeerCommunicator * ExternalCompletionTime: Completion Time of peers instances, ignoring times from local workers * - RECEIVE from PeerCommunicator, WRITE here, READ here * GlobalCompletionTime: minimum of LocalCompletionTime & ExternalCompletionTime * - WRITE here, READ by Workers * Note: * - shared memory READS/WRITES can later be converted req/resp messages between actors */ private final TemporalUtil temporalUtil = new TemporalUtil(); private final GlobalCompletionTimeStateManager globalCompletionTimeStateManager; private final MultiWriterLocalCompletionTimeConcurrentStateManager localCompletionTimeConcurrentStateManager; private final AtomicLong globalCompletionTimeSharedReference; private final QueueEventFetcher<CompletionTimeEvent> completionTimeEventQueueEventFetcher; private final ConcurrentErrorReporter errorReporter; private Long processedWriteEventCount = 0l; private Long expectedEventCount = null; private final Map<Integer,LocalCompletionTimeWriter> localCompletionTimeWriters; private final AtomicBoolean shutdownComplete = new AtomicBoolean( false ); ThreadedQueuedConcurrentCompletionTimeServiceThread( Queue<CompletionTimeEvent> completionTimeQueue, ConcurrentErrorReporter errorReporter, Set<String> peerIds, AtomicLong globalCompletionTimeSharedReference ) throws CompletionTimeException { super( ThreadedQueuedConcurrentCompletionTimeServiceThread.class.getSimpleName() + "-" + System.currentTimeMillis() ); localCompletionTimeConcurrentStateManager = new MultiWriterLocalCompletionTimeConcurrentStateManager(); this.localCompletionTimeWriters = new HashMap<>(); ExternalCompletionTimeStateManager externalCompletionTimeStateManager = new ExternalCompletionTimeStateManager( peerIds ); ExternalCompletionTimeReader externalCompletionTimeReader = (peerIds.isEmpty()) // prevents GCT from blocking in the case when there are no peers (because ECT would not advance) ? new LocalCompletionTimeReaderToExternalCompletionTimeReader( localCompletionTimeConcurrentStateManager ) : externalCompletionTimeStateManager; globalCompletionTimeStateManager = new GlobalCompletionTimeStateManager( // *** LCT Reader *** // Local Completion Time will only get read from MultiConsumerLocalCompletionTimeConcurrentStateManager, // and its internal Local Completion Time values will be written to by multiple instances of // MultiConsumerLocalCompletionTimeConcurrentStateManagerConsumer, retrieved via // newLocalCompletionTimeWriter() localCompletionTimeConcurrentStateManager, // *** LCT Writer *** // it is not safe to write Local Completion Time directly through GlobalCompletionTimeStateManager, // because there are, potentially, many Local Completion Time writers. // every Local Completion Time writing thread must have its own LocalCompletionTimeWriter, // to avoid race conditions where one thread tries to submit an Initiated Time, // another thread submits a higher Completed Time first, and then Local Completion Time advances, // which will result in an error when the lower Initiated Time is finally submitted. // MultiConsumerLocalCompletionTimeConcurrentStateManagerConsumer instances, // via newLocalCompletionTimeWriter(), will perform the Local Completion Time writing null, // *** ECT Reader *** externalCompletionTimeReader, // *** ECT Writer *** externalCompletionTimeStateManager ); this.completionTimeEventQueueEventFetcher = QueueEventFetcher.queueEventFetcherFor( completionTimeQueue ); this.errorReporter = errorReporter; this.globalCompletionTimeSharedReference = globalCompletionTimeSharedReference; this.globalCompletionTimeSharedReference.set( globalCompletionTimeStateManager.globalCompletionTimeAsMilli() ); } @Override public void run() { while ( null == expectedEventCount || processedWriteEventCount < expectedEventCount ) { try { CompletionTimeEvent event = completionTimeEventQueueEventFetcher.fetchNextEvent(); switch ( event.type() ) { case WRITE_LOCAL_INITIATED_TIME: { CompletionTimeEvent.LocalInitiatedTimeEvent localInitiatedTimeEvent = (CompletionTimeEvent.LocalInitiatedTimeEvent) event; long initiatedTimeAsMilli = localInitiatedTimeEvent.timeAsMilli(); int writerId = localInitiatedTimeEvent.localCompletionTimeWriterId(); LocalCompletionTimeWriter writer = localCompletionTimeWriters.get( writerId ); writer.submitLocalInitiatedTime( initiatedTimeAsMilli ); updateGlobalCompletionTime(); processedWriteEventCount++; break; } case WRITE_LOCAL_COMPLETED_TIME: { CompletionTimeEvent.LocalCompletedTimeEvent localCompletedTimeEvent = (CompletionTimeEvent.LocalCompletedTimeEvent) event; long completedTimeAsMilli = localCompletedTimeEvent.timeAsMilli(); int writerId = localCompletedTimeEvent.localCompletionTimeWriterId(); LocalCompletionTimeWriter writer = localCompletionTimeWriters.get( writerId ); writer.submitLocalCompletedTime( completedTimeAsMilli ); updateGlobalCompletionTime(); processedWriteEventCount++; break; } case WRITE_EXTERNAL_COMPLETION_TIME: { String peerId = ((CompletionTimeEvent.ExternalCompletionTimeEvent) event).peerId(); long peerCompletionTimeAsMilli = ((CompletionTimeEvent.ExternalCompletionTimeEvent) event).timeAsMilli(); globalCompletionTimeStateManager.submitPeerCompletionTime( peerId, peerCompletionTimeAsMilli ); updateGlobalCompletionTime(); processedWriteEventCount++; break; } case READ_GCT_FUTURE: { ThreadedQueuedCompletionTimeService.GlobalCompletionTimeFuture future = ((CompletionTimeEvent.GlobalCompletionTimeFutureEvent) event).future(); future.set( globalCompletionTimeSharedReference.get() ); break; } case NEW_LOCAL_COMPLETION_TIME_WRITER: { ThreadedQueuedCompletionTimeService.LocalCompletionTimeWriterFuture future = ((CompletionTimeEvent.NewLocalCompletionTimeWriterEvent) event).future(); MultiWriterLocalCompletionTimeConcurrentStateManagerWriter localCompletionTimeWriter = (MultiWriterLocalCompletionTimeConcurrentStateManagerWriter) localCompletionTimeConcurrentStateManager .newLocalCompletionTimeWriter(); localCompletionTimeWriters.put( localCompletionTimeWriter.id(), localCompletionTimeWriter ); future.set( localCompletionTimeWriter.id() ); break; } case TERMINATE_SERVICE: { if ( null == expectedEventCount ) { expectedEventCount = ((CompletionTimeEvent.TerminationServiceEvent) event).expectedEventCount(); } else { errorReporter.reportError( this, format( "Encountered multiple %s events. First expectedEventCount[%s]. Second " + "expectedEventCount[%s]", CompletionTimeEvent.CompletionTimeEventType.TERMINATE_SERVICE.name(), expectedEventCount, ((CompletionTimeEvent.TerminationServiceEvent) event).expectedEventCount() ) ); } break; } default: { errorReporter.reportError( this, format( "Encountered unexpected event type: %s", event.type().name() ) ); return; } } } catch ( CompletionTimeException e ) { errorReporter.reportError( this, format( "Encountered completion time related error\n%s", ConcurrentErrorReporter.stackTraceToString( e ) ) ); return; } catch ( Throwable e ) { errorReporter.reportError( this, format( "Encountered unexpected exception\n%s", ConcurrentErrorReporter.stackTraceToString( e ) ) ); return; } } shutdownComplete.set( true ); } boolean shutdownComplete() { return shutdownComplete.get(); } private void updateGlobalCompletionTime() throws CompletionTimeException { long newGlobalCompletionTimeAsMilli = globalCompletionTimeStateManager.globalCompletionTimeAsMilli(); if ( -1 == newGlobalCompletionTimeAsMilli ) { // Either Completion Time has not been received from one or more peers, or no local Completion Time has // been receive // Until both of the above have occurred there is no way of knowing what the lowest global time is return; } long prevGlobalCompletionTimeAsMilli = globalCompletionTimeSharedReference.get(); if ( -1 != prevGlobalCompletionTimeAsMilli && newGlobalCompletionTimeAsMilli < prevGlobalCompletionTimeAsMilli ) { errorReporter.reportError( this, format( "New GCT %s / %s smaller than previous GCT %s / %s", temporalUtil.milliTimeToDateTimeString( newGlobalCompletionTimeAsMilli ), newGlobalCompletionTimeAsMilli, temporalUtil.milliTimeToDateTimeString( prevGlobalCompletionTimeAsMilli ), prevGlobalCompletionTimeAsMilli ) ); } else { globalCompletionTimeSharedReference.set( newGlobalCompletionTimeAsMilli ); } } }