private static void checkTimestamp(long timestampLowerBound, long freshTimestamp) { if (freshTimestamp <= timestampLowerBound) { throw clocksWentBackwards(timestampLowerBound, freshTimestamp); } }
@Override public long getFreshTimestamp() { return checkAndUpdateLowerBound(delegate::getFreshTimestamp, x -> x, x -> x); }
public static TimelockService create(TimelockService delegate) { return new TimestampCorroboratingTimelockService(delegate); }
private <T> T checkAndUpdateLowerBound(Supplier<T> timestampContainerSupplier, ToLongFunction<T> lowerBoundExtractor, ToLongFunction<T> upperBoundExtractor) { long threadLocalLowerBound = lowerBound.get(); T timestampContainer = timestampContainerSupplier.get(); checkTimestamp(threadLocalLowerBound, lowerBoundExtractor.applyAsLong(timestampContainer)); updateLowerBound(upperBoundExtractor.applyAsLong(timestampContainer)); return timestampContainer; }
@Before public void setUp() { rawTimelockService = mock(TimelockService.class); timelockService = TimestampCorroboratingTimelockService.create(rawTimelockService); }
@Test public void resilientUnderMultipleThreads() throws InterruptedException { BlockingTimestamp blockingTimestampReturning1 = new BlockingTimestamp(1); when(rawTimelockService.getFreshTimestamp()) .thenAnswer(blockingTimestampReturning1) .thenReturn(2L); Future<Void> blockingGetFreshTimestampCall = CompletableFuture.runAsync(timelockService::getFreshTimestamp); blockingTimestampReturning1.waitForFirstCallToBlock(); assertThat(timelockService.getFreshTimestamp()) .as("This should have updated the lower bound to 2") .isEqualTo(2L); // we want to now resume the blocked call, which will return timestamp of 1 and not throw blockingTimestampReturning1.countdown(); assertThatCode(blockingGetFreshTimestampCall::get) .doesNotThrowAnyException(); }
@Test public void getFreshTimestampShouldFail() { when(rawTimelockService.getFreshTimestamp()).thenReturn(1L); assertThrowsOnSecondCall(timelockService::getFreshTimestamp); }
private <T> T checkAndUpdateLowerBound(Supplier<T> timestampContainerSupplier, ToLongFunction<T> lowerBoundExtractor, ToLongFunction<T> upperBoundExtractor) { long threadLocalLowerBound = lowerBound.get(); T timestampContainer = timestampContainerSupplier.get(); checkTimestamp(threadLocalLowerBound, lowerBoundExtractor.applyAsLong(timestampContainer)); updateLowerBound(upperBoundExtractor.applyAsLong(timestampContainer)); return timestampContainer; }
@Override public StartAtlasDbTransactionResponse startAtlasDbTransaction(IdentifiedTimeLockRequest request) { return checkAndUpdateLowerBound(() -> delegate.startAtlasDbTransaction(request), StartAtlasDbTransactionResponse::freshTimestamp, StartAtlasDbTransactionResponse::freshTimestamp); }
private static LockAndTimestampServices withCorroboratingTimestampService( LockAndTimestampServices lockAndTimestampServices) { TimelockService timelockService = TimestampCorroboratingTimelockService .create(lockAndTimestampServices.timelock()); TimestampService corroboratingTimestampService = new TimelockTimestampServiceAdapter(timelockService); return ImmutableLockAndTimestampServices.builder() .from(lockAndTimestampServices) .timelock(timelockService) .timestamp(corroboratingTimestampService) .build(); }
private static void checkTimestamp(long timestampLowerBound, long freshTimestamp) { if (freshTimestamp <= timestampLowerBound) { throw clocksWentBackwards(timestampLowerBound, freshTimestamp); } }
public static TimelockService create(TimelockService delegate) { return new TimestampCorroboratingTimelockService(delegate); }
@Test public void startAtlasDbTransactionShouldFail() { StartAtlasDbTransactionResponse response = StartAtlasDbTransactionResponse.of( mock(LockImmutableTimestampResponse.class), 1L); when(rawTimelockService.startAtlasDbTransaction(any())).thenReturn(response); assertThrowsOnSecondCall(() -> timelockService.startAtlasDbTransaction(IDENTIFIED_TIME_LOCK_REQUEST)); }
@Override public TimestampRange getFreshTimestamps(int numTimestampsRequested) { return checkAndUpdateLowerBound(() -> delegate.getFreshTimestamps(numTimestampsRequested), TimestampRange::getLowerBound, TimestampRange::getUpperBound); }
@Test public void getFreshTimestampsShouldFail() { TimestampRange timestampRange = TimestampRange.createInclusiveRange(1, 2); when(rawTimelockService.getFreshTimestamps(anyInt())).thenReturn(timestampRange); assertThrowsOnSecondCall(() -> timelockService.getFreshTimestamps(1)); }
@Override public StartIdentifiedAtlasDbTransactionResponse startIdentifiedAtlasDbTransaction( StartIdentifiedAtlasDbTransactionRequest request) { return checkAndUpdateLowerBound(() -> delegate.startIdentifiedAtlasDbTransaction(request), r -> r.startTimestampAndPartition().timestamp(), r -> r.startTimestampAndPartition().timestamp()); }
@Test public void startIdentifiedAtlasDbTransactionShouldFail() { StartIdentifiedAtlasDbTransactionResponse startIdentifiedAtlasDbTransactionResponse = StartIdentifiedAtlasDbTransactionResponse.of(LOCK_IMMUTABLE_TIMESTAMP_RESPONSE, TimestampAndPartition.of(1L, 0)); when(rawTimelockService.startIdentifiedAtlasDbTransaction(any())) .thenReturn(startIdentifiedAtlasDbTransactionResponse); assertThrowsOnSecondCall(() -> timelockService.startIdentifiedAtlasDbTransaction( StartIdentifiedAtlasDbTransactionRequest.createForRequestor(UUID.randomUUID()))); }
@Override public long getFreshTimestamp() { return checkAndUpdateLowerBound(delegate::getFreshTimestamp, x -> x, x -> x); }
@Override public StartAtlasDbTransactionResponse startAtlasDbTransaction(IdentifiedTimeLockRequest request) { return checkAndUpdateLowerBound(() -> delegate.startAtlasDbTransaction(request), StartAtlasDbTransactionResponse::freshTimestamp, StartAtlasDbTransactionResponse::freshTimestamp); }
@Override public TimestampRange getFreshTimestamps(int numTimestampsRequested) { return checkAndUpdateLowerBound(() -> delegate.getFreshTimestamps(numTimestampsRequested), TimestampRange::getLowerBound, TimestampRange::getUpperBound); }