@Override public CheckAndSetResult<ValueAndBound<T>> transformAgreedValue(Function<ValueAndBound<T>, T> transform) { Optional<SequenceAndBound> coordinationValue = getCoordinationValue(); ValueAndBound<T> extantValueAndBound = ValueAndBound.of(coordinationValue.flatMap( sequenceAndBound -> getValue(sequenceAndBound.sequence())), coordinationValue.map(SequenceAndBound::bound).orElse(SequenceAndBound.INVALID_BOUND)); T targetValue = transform.apply(extantValueAndBound); SequenceAndBound newSequenceAndBound = determineNewSequenceAndBound(coordinationValue, extantValueAndBound, targetValue); CheckAndSetResult<SequenceAndBound> casResult = checkAndSetCoordinationValue( coordinationValue, newSequenceAndBound); return extractRelevantValues(targetValue, newSequenceAndBound.bound(), casResult); }
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") // Passed from other operations returning Optional private SequenceAndBound determineNewSequenceAndBound( Optional<SequenceAndBound> coordinationValue, ValueAndBound<T> extantValueAndBound, T targetValue) { long sequenceNumber; long newBound; if (shouldReuseExtantValue(coordinationValue, extantValueAndBound.value(), targetValue)) { // Safe as we're only on this branch if the value is present sequenceNumber = coordinationValue.get().sequence(); newBound = getNewBound(sequenceNumberSupplier.getAsLong()); } else { sequenceNumber = sequenceNumberSupplier.getAsLong(); putUnlessValueExists(sequenceNumber, targetValue); newBound = getNewBound(sequenceNumber); } return SequenceAndBound.of(sequenceNumber, newBound); }
public static <T> CoordinationStore<T> create( ObjectMapper objectMapper, KeyValueService kvs, byte[] coordinationRow, LongSupplier sequenceNumberSupplier, Class<T> clazz, boolean initializeAsync) { KeyValueServiceCoordinationStore<T> coordinationStore = new KeyValueServiceCoordinationStore<>( objectMapper, kvs, coordinationRow, sequenceNumberSupplier, clazz); coordinationStore.wrapper.initialize(initializeAsync); return coordinationStore.isInitialized() ? coordinationStore : coordinationStore.wrapper; }
@VisibleForTesting Optional<T> getValue(long sequenceNumber) { Preconditions.checkState( sequenceNumber > 0, "Only positive sequence numbers are supported, but found %s", sequenceNumber); return readFromCoordinationTable(getCellForSequence(sequenceNumber)) .map(Value::getContents) .map(this::deserializeValue); }
@Override public Optional<ValueAndBound<T>> getAgreedValue() { return getCoordinationValue() .map(sequenceAndBound -> ValueAndBound.of( getValue(sequenceAndBound.sequence()), sequenceAndBound.bound())); }
@Test public void multipleStoresCanCoexist() { byte[] otherCoordinationKey = PtBytes.toBytes("bbbbb"); CoordinationStore<String> otherCoordinationStore = KeyValueServiceCoordinationStore.create( ObjectMappers.newServerObjectMapper(), keyValueService, otherCoordinationKey, timestampSequence::incrementAndGet, String.class, false); coordinationStore.transformAgreedValue(unused -> VALUE_1); otherCoordinationStore.transformAgreedValue(unused -> VALUE_2); assertThat(coordinationStore.getAgreedValue().get().value()).contains(VALUE_1); assertThat(otherCoordinationStore.getAgreedValue().get().value()).contains(VALUE_2); } }
public static CoordinationService<InternalSchemaMetadata> createDefault( KeyValueService keyValueService, LongSupplier timestampSupplier, boolean initializeAsync) { CoordinationService<VersionedInternalSchemaMetadata> versionedService = new CoordinationServiceImpl<>( KeyValueServiceCoordinationStore.create( ObjectMappers.newServerObjectMapper(), keyValueService, AtlasDbConstants.DEFAULT_METADATA_COORDINATION_KEY, timestampSupplier, VersionedInternalSchemaMetadata.class, initializeAsync)); return wrapHidingVersionSerialization(versionedService); } }
@VisibleForTesting void putUnlessValueExists(long sequenceNumber, T value) { Preconditions.checkState( sequenceNumber > 0, "Only positive sequence numbers are supported, but found %s", sequenceNumber); try { kvs.putUnlessExists(AtlasDbConstants.COORDINATION_TABLE, ImmutableMap.of(getCellForSequence(sequenceNumber), serializeValue(value))); } catch (KeyAlreadyExistsException e) { throw new SafeIllegalStateException("The coordination store failed a putUnlessExists. This is unexpected" + " as it implies timestamps may have been reused, or a writer to the store behaved badly." + " The offending sequence number was {}. " + " Please contact support - DO NOT ATTEMPT TO FIX THIS YOURSELF.", e, SafeArg.of("sequenceNumber", sequenceNumber)); } }
@Test public void valuePreservingTransformationsDoNotWriteTheSameValueAgain() { coordinationStore.transformAgreedValue(unused -> VALUE_1); SequenceAndBound firstSequenceAndBound = coordinationStore.getCoordinationValue().get(); coordinationStore.transformAgreedValue(VALUE_PRESERVING_FUNCTION); SequenceAndBound secondSequenceAndBound = coordinationStore.getCoordinationValue().get(); assertThat(firstSequenceAndBound.sequence()).isEqualTo(secondSequenceAndBound.sequence()); assertThat(firstSequenceAndBound.bound()).isLessThan(secondSequenceAndBound.bound()); }
private CheckAndSetResult<ValueAndBound<T>> extractRelevantValues(T targetValue, long newBound, CheckAndSetResult<SequenceAndBound> casResult) { if (casResult.successful()) { return CheckAndSetResult.of(true, ImmutableList.of(ValueAndBound.of(Optional.of(targetValue), newBound))); } return CheckAndSetResult.of( false, casResult.existingValues() .stream() .map(value -> ValueAndBound.of(getValue(value.sequence()), value.bound())) .collect(Collectors.toList())); }
@Test public void throwsIfAttemptingToPutTwice() { coordinationStore.putUnlessValueExists(SEQUENCE_NUMBER_1, VALUE_1); assertThatThrownBy(() -> coordinationStore.putUnlessValueExists(SEQUENCE_NUMBER_1, VALUE_2)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("The coordination store failed a putUnlessExists. This is unexpected" + " as it implies timestamps may have been reused, or a writer to the store behaved badly."); }
@Test public void canCheckAndSetBetweenValues() { coordinationStore.checkAndSetCoordinationValue(Optional.empty(), SEQUENCE_AND_BOUND_1); assertThat(coordinationStore.checkAndSetCoordinationValue( Optional.of(SEQUENCE_AND_BOUND_1), SEQUENCE_AND_BOUND_2)) .isEqualTo(ImmutableCheckAndSetResult.of(true, ImmutableList.of(SEQUENCE_AND_BOUND_2))); }
private T deserializeValue(byte[] contents) { return deserializeData(contents, clazz, VALUE_DESCRIPTION); }
private Cell getCoordinationValueCell() { return getCellForSequence(0); }
@Override public Optional<ValueAndBound<T>> getAgreedValue() { return getCoordinationValue() .map(sequenceAndBound -> ValueAndBound.of( getValue(sequenceAndBound.sequence()), sequenceAndBound.bound())); }
private static TransactionSchemaManager createTransactionSchemaManager(TimestampService ts) { CoordinationServiceImpl<VersionedInternalSchemaMetadata> rawService = new CoordinationServiceImpl<>( KeyValueServiceCoordinationStore.create( ObjectMappers.newServerObjectMapper(), new InMemoryKeyValueService(true), PtBytes.toBytes("aaa"), ts::getFreshTimestamp, VersionedInternalSchemaMetadata.class, false)); return new TransactionSchemaManager(CoordinationServices.wrapHidingVersionSerialization(rawService)); }
@VisibleForTesting Optional<T> getValue(long sequenceNumber) { Preconditions.checkState( sequenceNumber > 0, "Only positive sequence numbers are supported, but found %s", sequenceNumber); return readFromCoordinationTable(getCellForSequence(sequenceNumber)) .map(Value::getContents) .map(this::deserializeValue); }
@VisibleForTesting void putUnlessValueExists(long sequenceNumber, T value) { Preconditions.checkState( sequenceNumber > 0, "Only positive sequence numbers are supported, but found %s", sequenceNumber); try { kvs.putUnlessExists(AtlasDbConstants.COORDINATION_TABLE, ImmutableMap.of(getCellForSequence(sequenceNumber), serializeValue(value))); } catch (KeyAlreadyExistsException e) { throw new SafeIllegalStateException("The coordination store failed a putUnlessExists. This is unexpected" + " as it implies timestamps may have been reused, or a writer to the store behaved badly." + " The offending sequence number was {}. " + " Please contact support - DO NOT ATTEMPT TO FIX THIS YOURSELF.", e, SafeArg.of("sequenceNumber", sequenceNumber)); } }
@Test public void throwsIfAttemptingToGetAtNegativeSequenceNumber() { assertThatThrownBy(() -> coordinationStore.getValue(-1)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Only positive sequence numbers are supported"); }
@Test public void throwsIfAttemptingToPutAtNegativeSequenceNumber() { assertThatThrownBy(() -> coordinationStore.putUnlessValueExists(-1, VALUE_1)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Only positive sequence numbers are supported"); }