package com.ldbc.driver.runtime.coordination; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; public class SynchronizedCompletionTimeService implements CompletionTimeService { private final GlobalCompletionTimeStateManager globalCompletionTimeStateManager; private final MultiWriterLocalCompletionTimeConcurrentStateManager localCompletionTimeConcurrentStateManager; private final List<LocalCompletionTimeWriter> localCompletionTimeWriters; private enum Event { READ_GLOBAL_COMPLETION_TIME, READ_FUTURE_GLOBAL_COMPLETION_TIME, WRITE_EXTERNAL_COMPLETION_TIME, CREATE_NEW_LOCAL_COMPLETION_TIME_WRITER, GET_ALL_WRITERS } SynchronizedCompletionTimeService(Set<String> peerIds) throws CompletionTimeException { this.localCompletionTimeConcurrentStateManager = new MultiWriterLocalCompletionTimeConcurrentStateManager(); this.localCompletionTimeWriters = new ArrayList<>(); 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; this.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 submits 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 ); } @Override public Future<Long> globalCompletionTimeAsMilliFuture() throws CompletionTimeException { return (GlobalCompletionTimeAsMilliFuture) processEvent(Event.READ_FUTURE_GLOBAL_COMPLETION_TIME, null, -1); } @Override public List<LocalCompletionTimeWriter> getAllWriters() throws CompletionTimeException { return (List<LocalCompletionTimeWriter>) processEvent(Event.GET_ALL_WRITERS, null, -1); } @Override public long globalCompletionTimeAsMilli() throws CompletionTimeException { return (long) processEvent(Event.READ_GLOBAL_COMPLETION_TIME, null, -1); } @Override public LocalCompletionTimeWriter newLocalCompletionTimeWriter() throws CompletionTimeException { return (LocalCompletionTimeWriter) processEvent(Event.CREATE_NEW_LOCAL_COMPLETION_TIME_WRITER, null, -1); } @Override public void submitPeerCompletionTime(String peerId, long timeAsMilli) throws CompletionTimeException { processEvent(Event.WRITE_EXTERNAL_COMPLETION_TIME, peerId, timeAsMilli); } @Override public void shutdown() throws CompletionTimeException { } private Object processEvent(Event event, String peerId, long timeAsMilli) throws CompletionTimeException { synchronized (globalCompletionTimeStateManager) { switch (event) { case READ_GLOBAL_COMPLETION_TIME: { return globalCompletionTimeStateManager.globalCompletionTimeAsMilli(); } case READ_FUTURE_GLOBAL_COMPLETION_TIME: { return new GlobalCompletionTimeAsMilliFuture(globalCompletionTimeStateManager.globalCompletionTimeAsMilli()); } case WRITE_EXTERNAL_COMPLETION_TIME: { globalCompletionTimeStateManager.submitPeerCompletionTime(peerId, timeAsMilli); return null; } case CREATE_NEW_LOCAL_COMPLETION_TIME_WRITER: { LocalCompletionTimeWriter localCompletionTimeWriter = localCompletionTimeConcurrentStateManager.newLocalCompletionTimeWriter(); localCompletionTimeWriters.add(localCompletionTimeWriter); return localCompletionTimeWriter; } case GET_ALL_WRITERS: { return localCompletionTimeWriters; } default: { throw new CompletionTimeException("Unrecognized event type: " + event.name()); } } } } private static class GlobalCompletionTimeAsMilliFuture implements Future<Long> { private final long globalCompletionTimeValueAsMilli; public GlobalCompletionTimeAsMilliFuture(long globalCompletionTimeValueAsMilli) { this.globalCompletionTimeValueAsMilli = globalCompletionTimeValueAsMilli; } @Override public boolean cancel(boolean mayInterruptIfRunning) { throw new UnsupportedOperationException(); } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return true; } @Override public Long get() { return globalCompletionTimeValueAsMilli; } @Override public Long get(long timeout, TimeUnit unit) { return globalCompletionTimeValueAsMilli; } } }