/* * Copyright 2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package io.atomix.variables; import io.atomix.catalyst.buffer.BufferInput; import io.atomix.catalyst.buffer.BufferOutput; import io.atomix.catalyst.concurrent.Listener; import io.atomix.catalyst.serializer.CatalystSerializable; import io.atomix.catalyst.serializer.Serializer; import io.atomix.copycat.client.CopycatClient; import io.atomix.resource.AbstractResource; import io.atomix.resource.ReadConsistency; import io.atomix.variables.internal.ValueCommands; import java.time.Duration; import java.util.Properties; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; /** * Base class for distributed atomic variables. * <p> * This class provides functionality shared by {@link DistributedValue} and {@link DistributedLong}, including * {@link #set(Object) set}, {@link #get() get}, and atomic {@link #compareAndSet(Object, Object) compare-and-set} * operations. The methods are closely modeled on those of Java's {@link java.util.concurrent.atomic.AtomicReference}. * Operations that modify the resource state are atomic. Operations that read resource state may be atomic * depending on the configured {@link ReadConsistency read consistency level}. * * @author <a href="http://github.com/kuujo>Jordan Halterman</a> */ @SuppressWarnings("unchecked") public abstract class AbstractDistributedValue<T extends AbstractDistributedValue<T, U>, U> extends AbstractResource<T> { protected AbstractDistributedValue(CopycatClient client, Properties options) { super(client, options); } /** * Registers a listener to be called when the value changes. * * @param callback The callback to be called when the value changes. * @return The change event. */ public synchronized CompletableFuture<Listener<ChangeEvent<U>>> onChange(Consumer<ChangeEvent<U>> callback) { return onEvent(Events.CHANGE, callback); } /** * Gets the current value. * <p> * The value will be read using the configured {@link ReadConsistency read consistency} level for * the resource. For {@link ReadConsistency#ATOMIC atomic reads}, reads will be forwarded to the cluster * leader. Weaker reads may be evaluated on non-leader nodes. However, consistency constraints guarantee * that state will never go back in time. * * @return A completable future to be completed with the current value. */ public CompletableFuture<U> get() { return client.submit(new ValueCommands.Get<>()); } /** * Gets the current value. * <p> * The value will be read using the provided {@link ReadConsistency read consistency} level. * For {@link ReadConsistency#ATOMIC atomic reads}, reads will be forwarded to the cluster leader. * Weaker reads may be evaluated on non-leader nodes. However, consistency constraints guarantee * that state will never go back in time. * * @param consistency The read consistency level. * @return A completable future to be completed with the current value. */ public CompletableFuture<U> get(ReadConsistency consistency) { return client.submit(new ValueCommands.Get<>(consistency.level())); } /** * Sets the value. * <p> * This is an atomic operation that is guaranteed to take place exactly once between the invocation * of the method call and the completion of the returned {@link CompletableFuture}. Once the returned * {@link CompletableFuture} is completed, all other instances of the resource can see the state change * when writing or reading using {@link ReadConsistency#ATOMIC atomic consistency}. * * @param value The current value. * @return A completable future to be completed once the value has been set. */ public CompletableFuture<Void> set(U value) { return client.submit(new ValueCommands.Set(value)); } /** * Sets the value with a time-to-live. * <p> * This is an atomic operation that is guaranteed to take place exactly once between the invocation * of the method call and the completion of the returned {@link CompletableFuture}. Once the returned * {@link CompletableFuture} is completed, all other instances of the resource can see the state change * when writing or reading using {@link ReadConsistency#ATOMIC atomic consistency}. * <p> * The value set will be expired after the given {@link Duration} so long as it's not overridden * by a later call from this or another instance of the resource. The time-to-live on the value * is coarse grained, and the value is only guaranteed to expire up to a session timeout after * the given time-to-live has expired. * * @param value The value to set. * @param ttl The time after which to expire the value. * @return A completable future to be completed once the value has been set. */ public CompletableFuture<Void> set(U value, Duration ttl) { return client.submit(new ValueCommands.Set(value, ttl.toMillis())); } /** * Gets the current value and updates it. * <p> * This is an atomic operation that is guaranteed to take place exactly once between the invocation * of the method call and the completion of the returned {@link CompletableFuture}. Once the returned * {@link CompletableFuture} is completed, all other instances of the resource can see the state change * when writing or reading using {@link ReadConsistency#ATOMIC atomic consistency}. * <p> * Once the operation is complete, the returned {@link CompletableFuture} will be completed with the * value of the resource prior to setting the updated value. * * @param value The updated value. * @return A completable future to be completed with the previous value. */ public CompletableFuture<U> getAndSet(U value) { return client.submit(new ValueCommands.GetAndSet<>(value)); } /** * Gets the current value and updates it, setting a time-to-live on the updated value. * <p> * This is an atomic operation that is guaranteed to take place exactly once between the invocation * of the method call and the completion of the returned {@link CompletableFuture}. Once the returned * {@link CompletableFuture} is completed, all other instances of the resource can see the state change * when writing or reading using {@link ReadConsistency#ATOMIC atomic consistency}. * <p> * Once the operation is complete, the returned {@link CompletableFuture} will be completed with the * value of the resource prior to setting the updated value. The value set will be expired after the * given {@link Duration} so long as it's not overridden by a later call from this or another instance * of the resource. The time-to-live on the value is coarse grained, and the value is only guaranteed * to expire up to a session timeout after the given time-to-live has expired. * * @param value The updated value. * @param ttl The time after which to expire the value. * @return A completable future to be completed with the previous value. */ public CompletableFuture<U> getAndSet(U value, Duration ttl) { return client.submit(new ValueCommands.GetAndSet<>(value, ttl.toMillis())); } /** * Compares the current value and updated it if expected value equals the current value. * <p> * This is an atomic operation that is guaranteed to take place exactly once between the invocation * of the method call and the completion of the returned {@link CompletableFuture}. If the {@code expect} * value is equal to the current resource value, the value will be updated and the returned * {@link CompletableFuture} will be completed {@code true}, otherwise the value will not be updated and * the returned {@link CompletableFuture} will be completed {@code false}. In contrast to Java's atomic * variables, value comparisons are done with {@link Object#equals(Object)} rather than {@code ==}. This * is necessary because of serialization. Once the returned future is completed, all other instances of * the resource can see the state change when writing or reading using * {@link ReadConsistency#ATOMIC atomic consistency}. * * @param expect The expected value. * @param update The updated value. * @return A completable future to be completed with a boolean value indicating whether the value was updated. */ public CompletableFuture<Boolean> compareAndSet(U expect, U update) { return client.submit(new ValueCommands.CompareAndSet(expect, update)); } /** * Compares the current value and updated it if expected value equals the current value, setting a time-to-live * on the updated value. * <p> * This is an atomic operation that is guaranteed to take place exactly once between the invocation * of the method call and the completion of the returned {@link CompletableFuture}. If the {@code expect} * value is equal to the current resource value, the value will be updated and the returned * {@link CompletableFuture} will be completed {@code true}, otherwise the value will not be updated and * the returned {@link CompletableFuture} will be completed {@code false}. If the value is not updated then * the provided time-to-live will not apply to the existing value. In contrast to Java's atomic * variables, value comparisons are done with {@link Object#equals(Object)} rather than {@code ==}. This * is necessary because of serialization. Once the returned future is completed, all other instances of * the resource can see the state change when writing or reading using * {@link ReadConsistency#ATOMIC atomic consistency}. * <p> * If the value is changed, the updated value will be expired after the given {@link Duration} so long * as it's not overridden by a later call from this or another instance of the resource. The time-to-live * on the value is coarse grained, and the value is only guaranteed to expire up to a session timeout after * the given time-to-live has expired. * * @param expect The expected value. * @param update The updated value. * @param ttl The time after which to expire the value. * @return A completable future to be completed with a boolean value indicating whether the value was updated. */ public CompletableFuture<Boolean> compareAndSet(U expect, U update, Duration ttl) { return client.submit(new ValueCommands.CompareAndSet(expect, update, ttl.toMillis())); } /** * Distributed value event types. */ public enum Events implements EventType { CHANGE; @Override public int id() { return ordinal(); } } /** * Distributed value change event. */ public static class ChangeEvent<T> implements Event, CatalystSerializable { private T oldValue; private T newValue; public ChangeEvent() { } public ChangeEvent(T oldValue, T newValue) { this.oldValue = oldValue; this.newValue = newValue; } @Override public EventType type() { return Events.CHANGE; } /** * Returns the old value. * * @return The old value. */ public T oldValue() { return oldValue; } /** * Returns the new value. * * @return The new value. */ public T newValue() { return newValue; } @Override public void writeObject(BufferOutput<?> buffer, Serializer serializer) { serializer.writeObject(oldValue, buffer); serializer.writeObject(newValue, buffer); } @Override public void readObject(BufferInput<?> buffer, Serializer serializer) { oldValue = serializer.readObject(buffer); newValue = serializer.readObject(buffer); } @Override public String toString() { return String.format("%s[oldValue=%s, newValue=%s]", getClass().getSimpleName(), oldValue, newValue); } } }