From 267f7202a1c7643b88729ca9d98a098175ad7d28 Mon Sep 17 00:00:00 2001 From: Jordan Halterman Date: Mon, 27 Jul 2015 02:35:52 -0700 Subject: [PATCH] Add interfaces for all resources and encapsulate state machines and commands in internal packages. --- .../kuujo/copycat/atomic/AsyncReference.java | 257 ++++ .../copycat/atomic/DistributedReference.java | 878 +------------- .../atomic/state/ReferenceCommands.java | 509 ++++++++ .../copycat/atomic/state/ReferenceState.java | 179 +++ .../net.kuujo.alleycat.AlleycatSerializable | 10 +- .../atomic/DistributedReferenceTest.java | 6 +- .../raft/client/state/ClientContext.java | 2 +- .../kuujo/copycat/collections/AsyncMap.java | 254 ++++ .../kuujo/copycat/collections/AsyncSet.java | 155 +++ .../kuujo/copycat/collections/AsyncTopic.java | 46 + .../copycat/collections/DistributedMap.java | 1029 +---------------- .../copycat/collections/DistributedSet.java | 692 +---------- .../copycat/collections/DistributedTopic.java | 198 +--- .../collections/state/MapCommands.java | 619 ++++++++++ .../copycat/collections/state/MapState.java | 214 ++++ .../collections/state/SetCommands.java | 452 ++++++++ .../copycat/collections/state/SetState.java | 159 +++ .../collections/state/TopicCommands.java | 155 +++ .../copycat/collections/state/TopicState.java | 68 ++ .../net.kuujo.alleycat.AlleycatSerializable | 33 +- .../collections/DistributedMapTest.java | 39 +- .../collections/DistributedSetTest.java | 40 +- .../coordination/AsyncLeaderElection.java | 38 + .../kuujo/copycat/coordination/AsyncLock.java | 66 ++ .../coordination/DistributedElection.java | 253 ---- .../DistributedLeaderElection.java | 87 ++ .../copycat/coordination/DistributedLock.java | 273 +---- .../state/LeaderElectionCommands.java | 121 ++ .../state/LeaderElectionState.java | 104 ++ .../coordination/state/LockCommands.java | 174 +++ .../copycat/coordination/state/LockState.java | 103 ++ .../net.kuujo.alleycat.AlleycatSerializable | 8 +- ...ava => DistributedLeaderElectionTest.java} | 4 +- .../main/java/net/kuujo/copycat/Copycat.java | 15 +- pom.xml | 2 +- 35 files changed, 4021 insertions(+), 3221 deletions(-) create mode 100644 atomic/src/main/java/net/kuujo/copycat/atomic/AsyncReference.java create mode 100644 atomic/src/main/java/net/kuujo/copycat/atomic/state/ReferenceCommands.java create mode 100644 atomic/src/main/java/net/kuujo/copycat/atomic/state/ReferenceState.java create mode 100644 collections/src/main/java/net/kuujo/copycat/collections/AsyncMap.java create mode 100644 collections/src/main/java/net/kuujo/copycat/collections/AsyncSet.java create mode 100644 collections/src/main/java/net/kuujo/copycat/collections/AsyncTopic.java create mode 100644 collections/src/main/java/net/kuujo/copycat/collections/state/MapCommands.java create mode 100644 collections/src/main/java/net/kuujo/copycat/collections/state/MapState.java create mode 100644 collections/src/main/java/net/kuujo/copycat/collections/state/SetCommands.java create mode 100644 collections/src/main/java/net/kuujo/copycat/collections/state/SetState.java create mode 100644 collections/src/main/java/net/kuujo/copycat/collections/state/TopicCommands.java create mode 100644 collections/src/main/java/net/kuujo/copycat/collections/state/TopicState.java create mode 100644 coordination/src/main/java/net/kuujo/copycat/coordination/AsyncLeaderElection.java create mode 100644 coordination/src/main/java/net/kuujo/copycat/coordination/AsyncLock.java delete mode 100644 coordination/src/main/java/net/kuujo/copycat/coordination/DistributedElection.java create mode 100644 coordination/src/main/java/net/kuujo/copycat/coordination/DistributedLeaderElection.java create mode 100644 coordination/src/main/java/net/kuujo/copycat/coordination/state/LeaderElectionCommands.java create mode 100644 coordination/src/main/java/net/kuujo/copycat/coordination/state/LeaderElectionState.java create mode 100644 coordination/src/main/java/net/kuujo/copycat/coordination/state/LockCommands.java create mode 100644 coordination/src/main/java/net/kuujo/copycat/coordination/state/LockState.java rename coordination/src/test/java/net/kuujo/copycat/coordination/{DistributedElectionTest.java => DistributedLeaderElectionTest.java} (94%) diff --git a/atomic/src/main/java/net/kuujo/copycat/atomic/AsyncReference.java b/atomic/src/main/java/net/kuujo/copycat/atomic/AsyncReference.java new file mode 100644 index 0000000000..f883ea5fe6 --- /dev/null +++ b/atomic/src/main/java/net/kuujo/copycat/atomic/AsyncReference.java @@ -0,0 +1,257 @@ +/* + * 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 net.kuujo.copycat.atomic; + +import net.kuujo.copycat.Listener; +import net.kuujo.copycat.ListenerContext; +import net.kuujo.copycat.Mode; +import net.kuujo.copycat.raft.ConsistencyLevel; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * Asynchronous atomic reference. + * + * @author Jordan Halterman + */ +public interface AsyncReference { + + /** + * Sets the default read consistency level. + * + * @param consistency The default read consistency level. + * @throws java.lang.NullPointerException If the consistency level is {@code null} + */ + void setDefaultConsistencyLevel(ConsistencyLevel consistency); + + /** + * Sets the default consistency level, returning the resource for method chaining. + * + * @param consistency The default read consistency level. + * @return The reference. + * @throws java.lang.NullPointerException If the consistency level is {@code null} + */ + DistributedReference withDefaultConsistencyLevel(ConsistencyLevel consistency); + + /** + * Returns the default consistency level. + * + * @return The default consistency level. + */ + ConsistencyLevel getDefaultConsistencyLevel(); + + /** + * Gets the current value. + * + * @return A completable future to be completed with the current value. + */ + CompletableFuture get(); + + /** + * Gets the current value. + * + * @param consistency The read consistency level. + * @return A completable future to be completed with the current value. + */ + CompletableFuture get(ConsistencyLevel consistency); + + /** + * Sets the current value. + * + * @param value The current value. + * @return A completable future to be completed once the value has been set. + */ + CompletableFuture set(T value); + + /** + * Sets the value with a TTL. + * + * @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. + */ + CompletableFuture set(T value, long ttl); + + /** + * Sets the value with a TTL. + * + * @param value The value to set. + * @param ttl The time after which to expire the value. + * @param unit The expiration time unit. + * @return A completable future to be completed once the value has been set. + */ + CompletableFuture set(T value, long ttl, TimeUnit unit); + + /** + * Sets the value with a write mode. + * + * @param value The value to set. + * @param mode The write mode. + * @return A completable future to be completed once the value has been set. + */ + CompletableFuture set(T value, Mode mode); + + /** + * Sets the value with a write mode. + * + * @param value The value to set. + * @param ttl The time after which to expire the value. + * @param mode The write mode. + * @return A completable future to be completed once the value has been set. + */ + CompletableFuture set(T value, long ttl, Mode mode); + + /** + * Sets the value with a write mode. + * + * @param value The value to set. + * @param ttl The time after which to expire the value. + * @param unit The expiration time unit. + * @param mode The write mode. + * @return A completable future to be completed once the value has been set. + */ + CompletableFuture set(T value, long ttl, TimeUnit unit, Mode mode); + + /** + * Gets the current value and updates it. + * + * @param value The updated value. + * @return A completable future to be completed with the previous value. + */ + CompletableFuture getAndSet(T value); + + /** + * Gets the current value and updates it. + * + * @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. + */ + CompletableFuture getAndSet(T value, long ttl); + + /** + * Gets the current value and updates it. + * + * @param value The updated value. + * @param ttl The time after which to expire the value. + * @param unit The expiration time unit. + * @return A completable future to be completed with the previous value. + */ + CompletableFuture getAndSet(T value, long ttl, TimeUnit unit); + + /** + * Gets the current value and updates it. + * + * @param value The updated value. + * @param mode The write mode. + * @return A completable future to be completed with the previous value. + */ + CompletableFuture getAndSet(T value, Mode mode); + + /** + * Gets the current value and updates it. + * + * @param value The updated value. + * @param ttl The time after which to expire the value. + * @param mode The write mode. + * @return A completable future to be completed with the previous value. + */ + CompletableFuture getAndSet(T value, long ttl, Mode mode); + + /** + * Gets the current value and updates it. + * + * @param value The updated value. + * @param ttl The time after which to expire the value. + * @param unit The expiration time unit. + * @param mode The write mode. + * @return A completable future to be completed with the previous value. + */ + CompletableFuture getAndSet(T value, long ttl, TimeUnit unit, Mode mode); + + /** + * Compares the current value and updated it if expected value == the current value. + * + * @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. + */ + CompletableFuture compareAndSet(T expect, T update); + + /** + * Compares the current value and updated it if expected value == the current value. + * + * @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. + */ + CompletableFuture compareAndSet(T expect, T update, long ttl); + + /** + * Compares the current value and updated it if expected value == the current value. + * + * @param expect The expected value. + * @param update The updated value. + * @param ttl The time after which to expire the value. + * @param unit The expiration time unit. + * @return A completable future to be completed with a boolean value indicating whether the value was updated. + */ + CompletableFuture compareAndSet(T expect, T update, long ttl, TimeUnit unit); + + /** + * Compares the current value and updated it if expected value == the current value. + * + * @param expect The expected value. + * @param update The updated value. + * @param mode The write mode. + * @return A completable future to be completed with a boolean value indicating whether the value was updated. + */ + CompletableFuture compareAndSet(T expect, T update, Mode mode); + + /** + * Compares the current value and updated it if expected value == the current value. + * + * @param expect The expected value. + * @param update The updated value. + * @param ttl The time after which to expire the value. + * @param mode The write mode. + * @return A completable future to be completed with a boolean value indicating whether the value was updated. + */ + CompletableFuture compareAndSet(T expect, T update, long ttl, Mode mode); + + /** + * Compares the current value and updated it if expected value == the current value. + * + * @param expect The expected value. + * @param update The updated value. + * @param ttl The time after which to expire the value. + * @param unit The expiration time unit. + * @param mode The write mode. + * @return A completable future to be completed with a boolean value indicating whether the value was updated. + */ + CompletableFuture compareAndSet(T expect, T update, long ttl, TimeUnit unit, Mode mode); + + /** + * Registers a change listener. + * + * @param listener The change listener. + * @return A completable future to be completed once the change listener has been registered. + */ + CompletableFuture> onChange(Listener listener); + +} diff --git a/atomic/src/main/java/net/kuujo/copycat/atomic/DistributedReference.java b/atomic/src/main/java/net/kuujo/copycat/atomic/DistributedReference.java index 1f27d09a0e..e7c98de499 100644 --- a/atomic/src/main/java/net/kuujo/copycat/atomic/DistributedReference.java +++ b/atomic/src/main/java/net/kuujo/copycat/atomic/DistributedReference.java @@ -15,32 +15,24 @@ */ package net.kuujo.copycat.atomic; -import net.kuujo.alleycat.Alleycat; -import net.kuujo.alleycat.AlleycatSerializable; -import net.kuujo.alleycat.SerializeWith; -import net.kuujo.alleycat.io.BufferInput; -import net.kuujo.alleycat.io.BufferOutput; import net.kuujo.copycat.*; -import net.kuujo.copycat.log.Compaction; -import net.kuujo.copycat.raft.*; -import net.kuujo.copycat.raft.server.Apply; -import net.kuujo.copycat.raft.server.Commit; -import net.kuujo.copycat.raft.server.Filter; +import net.kuujo.copycat.atomic.state.ReferenceCommands; +import net.kuujo.copycat.atomic.state.ReferenceState; +import net.kuujo.copycat.raft.ConsistencyLevel; +import net.kuujo.copycat.raft.Raft; import java.util.Collections; -import java.util.HashSet; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; /** * Asynchronous atomic value. * * @author Jordan Halterman */ -@Stateful(DistributedReference.StateMachine.class) -public class DistributedReference extends Resource { +@Stateful(ReferenceState.class) +public class DistributedReference extends Resource implements AsyncReference { private ConsistencyLevel defaultConsistency = ConsistencyLevel.LINEARIZABLE_LEASE; private final java.util.Set> changeListeners = Collections.newSetFromMap(new ConcurrentHashMap<>()); @@ -53,310 +45,172 @@ public DistributedReference(Raft protocol) { }); } - /** - * Sets the default read consistency level. - * - * @param consistency The default read consistency level. - * @throws java.lang.NullPointerException If the consistency level is {@code null} - */ + @Override public void setDefaultConsistencyLevel(ConsistencyLevel consistency) { if (consistency == null) throw new NullPointerException("consistency cannot be null"); this.defaultConsistency = consistency; } - /** - * Sets the default consistency level, returning the resource for method chaining. - * - * @param consistency The default read consistency level. - * @return The reference. - * @throws java.lang.NullPointerException If the consistency level is {@code null} - */ + @Override public DistributedReference withDefaultConsistencyLevel(ConsistencyLevel consistency) { setDefaultConsistencyLevel(consistency); return this; } - /** - * Returns the default consistency level. - * - * @return The default consistency level. - */ + @Override public ConsistencyLevel getDefaultConsistencyLevel() { return defaultConsistency; } - /** - * Gets the current value. - * - * @return A completable future to be completed with the current value. - */ + @Override public CompletableFuture get() { return get(defaultConsistency); } - /** - * Gets the current value. - * - * @param consistency The read consistency level. - * @return A completable future to be completed with the current value. - */ + @Override public CompletableFuture get(ConsistencyLevel consistency) { - return submit(Get.builder() + return submit(ReferenceCommands.Get.builder() .withConsistency(consistency) .build()); } - /** - * Sets the current value. - * - * @param value The current value. - * @return A completable future to be completed once the value has been set. - */ + @Override public CompletableFuture set(T value) { - return submit(Set.builder() + return submit(ReferenceCommands.Set.builder() .withValue(value) .build()); } - /** - * Sets the value with a TTL. - * - * @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. - */ + @Override public CompletableFuture set(T value, long ttl) { - return submit(Set.builder() + return submit(ReferenceCommands.Set.builder() .withValue(value) .withTtl(ttl) .build()); } - /** - * Sets the value with a TTL. - * - * @param value The value to set. - * @param ttl The time after which to expire the value. - * @param unit The expiration time unit. - * @return A completable future to be completed once the value has been set. - */ + @Override public CompletableFuture set(T value, long ttl, TimeUnit unit) { - return submit(Set.builder() + return submit(ReferenceCommands.Set.builder() .withValue(value) .withTtl(ttl, unit) .build()); } - /** - * Sets the value with a write mode. - * - * @param value The value to set. - * @param mode The write mode. - * @return A completable future to be completed once the value has been set. - */ + @Override public CompletableFuture set(T value, Mode mode) { - return submit(Set.builder() + return submit(ReferenceCommands.Set.builder() .withValue(value) .withMode(mode) .build()); } - /** - * Sets the value with a write mode. - * - * @param value The value to set. - * @param ttl The time after which to expire the value. - * @param mode The write mode. - * @return A completable future to be completed once the value has been set. - */ + @Override public CompletableFuture set(T value, long ttl, Mode mode) { - return submit(Set.builder() + return submit(ReferenceCommands.Set.builder() .withValue(value) .withTtl(ttl) .withMode(mode) .build()); } - /** - * Sets the value with a write mode. - * - * @param value The value to set. - * @param ttl The time after which to expire the value. - * @param unit The expiration time unit. - * @param mode The write mode. - * @return A completable future to be completed once the value has been set. - */ + @Override public CompletableFuture set(T value, long ttl, TimeUnit unit, Mode mode) { - return submit(Set.builder() + return submit(ReferenceCommands.Set.builder() .withValue(value) .withTtl(ttl, unit) .withMode(mode) .build()); } - /** - * Gets the current value and updates it. - * - * @param value The updated value. - * @return A completable future to be completed with the previous value. - */ + @Override public CompletableFuture getAndSet(T value) { - return submit(GetAndSet.builder() + return submit(ReferenceCommands.GetAndSet.builder() .withValue(value) .build()); } - /** - * Gets the current value and updates it. - * - * @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. - */ + @Override public CompletableFuture getAndSet(T value, long ttl) { - return submit(GetAndSet.builder() + return submit(ReferenceCommands.GetAndSet.builder() .withValue(value) .withTtl(ttl) .build()); } - /** - * Gets the current value and updates it. - * - * @param value The updated value. - * @param ttl The time after which to expire the value. - * @param unit The expiration time unit. - * @return A completable future to be completed with the previous value. - */ + @Override public CompletableFuture getAndSet(T value, long ttl, TimeUnit unit) { - return submit(GetAndSet.builder() + return submit(ReferenceCommands.GetAndSet.builder() .withValue(value) .withTtl(ttl, unit) .build()); } - /** - * Gets the current value and updates it. - * - * @param value The updated value. - * @param mode The write mode. - * @return A completable future to be completed with the previous value. - */ + @Override public CompletableFuture getAndSet(T value, Mode mode) { - return submit(GetAndSet.builder() + return submit(ReferenceCommands.GetAndSet.builder() .withValue(value) .withMode(mode) .build()); } - /** - * Gets the current value and updates it. - * - * @param value The updated value. - * @param ttl The time after which to expire the value. - * @param mode The write mode. - * @return A completable future to be completed with the previous value. - */ + @Override public CompletableFuture getAndSet(T value, long ttl, Mode mode) { - return submit(GetAndSet.builder() + return submit(ReferenceCommands.GetAndSet.builder() .withValue(value) .withTtl(ttl) .withMode(mode) .build()); } - /** - * Gets the current value and updates it. - * - * @param value The updated value. - * @param ttl The time after which to expire the value. - * @param unit The expiration time unit. - * @param mode The write mode. - * @return A completable future to be completed with the previous value. - */ + @Override public CompletableFuture getAndSet(T value, long ttl, TimeUnit unit, Mode mode) { - return submit(GetAndSet.builder() + return submit(ReferenceCommands.GetAndSet.builder() .withValue(value) .withTtl(ttl, unit) .withMode(mode) .build()); } - /** - * Compares the current value and updated it if expected value == the current value. - * - * @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. - */ + @Override public CompletableFuture compareAndSet(T expect, T update) { - return submit(CompareAndSet.builder() + return submit(ReferenceCommands.CompareAndSet.builder() .withExpect(expect) .withUpdate(update) .build()); } - /** - * Compares the current value and updated it if expected value == the current value. - * - * @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. - */ + @Override public CompletableFuture compareAndSet(T expect, T update, long ttl) { - return submit(CompareAndSet.builder() + return submit(ReferenceCommands.CompareAndSet.builder() .withExpect(expect) .withUpdate(update) .withTtl(ttl) .build()); } - /** - * Compares the current value and updated it if expected value == the current value. - * - * @param expect The expected value. - * @param update The updated value. - * @param ttl The time after which to expire the value. - * @param unit The expiration time unit. - * @return A completable future to be completed with a boolean value indicating whether the value was updated. - */ + @Override public CompletableFuture compareAndSet(T expect, T update, long ttl, TimeUnit unit) { - return submit(CompareAndSet.builder() + return submit(ReferenceCommands.CompareAndSet.builder() .withExpect(expect) .withUpdate(update) .withTtl(ttl, unit) .build()); } - /** - * Compares the current value and updated it if expected value == the current value. - * - * @param expect The expected value. - * @param update The updated value. - * @param mode The write mode. - * @return A completable future to be completed with a boolean value indicating whether the value was updated. - */ + @Override public CompletableFuture compareAndSet(T expect, T update, Mode mode) { - return submit(CompareAndSet.builder() + return submit(ReferenceCommands.CompareAndSet.builder() .withExpect(expect) .withUpdate(update) .withMode(mode) .build()); } - /** - * Compares the current value and updated it if expected value == the current value. - * - * @param expect The expected value. - * @param update The updated value. - * @param ttl The time after which to expire the value. - * @param mode The write mode. - * @return A completable future to be completed with a boolean value indicating whether the value was updated. - */ + @Override public CompletableFuture compareAndSet(T expect, T update, long ttl, Mode mode) { - return submit(CompareAndSet.builder() + return submit(ReferenceCommands.CompareAndSet.builder() .withExpect(expect) .withUpdate(update) .withTtl(ttl) @@ -364,18 +218,9 @@ public CompletableFuture compareAndSet(T expect, T update, long ttl, Mo .build()); } - /** - * Compares the current value and updated it if expected value == the current value. - * - * @param expect The expected value. - * @param update The updated value. - * @param ttl The time after which to expire the value. - * @param unit The expiration time unit. - * @param mode The write mode. - * @return A completable future to be completed with a boolean value indicating whether the value was updated. - */ + @Override public CompletableFuture compareAndSet(T expect, T update, long ttl, TimeUnit unit, Mode mode) { - return submit(CompareAndSet.builder() + return submit(ReferenceCommands.CompareAndSet.builder() .withExpect(expect) .withUpdate(update) .withTtl(ttl, unit) @@ -383,12 +228,7 @@ public CompletableFuture compareAndSet(T expect, T update, long ttl, Ti .build()); } - /** - * Registers a change listener. - * - * @param listener The change listener. - * @return A completable future to be completed once the change listener has been registered. - */ + @Override public synchronized CompletableFuture> onChange(Listener listener) { if (!changeListeners.isEmpty()) { changeListeners.add(listener); @@ -396,7 +236,7 @@ public synchronized CompletableFuture> onChange(Listener l } changeListeners.add(listener); - return submit(ChangeListen.builder().build()) + return submit(ReferenceCommands.Listen.builder().build()) .thenApply(v -> new ChangeListenerContext(listener)); } @@ -420,626 +260,10 @@ public void close() { synchronized (DistributedReference.this) { changeListeners.remove(listener); if (changeListeners.isEmpty()) { - submit(ChangeUnlisten.builder().build()); + submit(ReferenceCommands.Unlisten.builder().build()); } } } } - /** - * Abstract reference command. - */ - public static abstract class ReferenceCommand implements Command, AlleycatSerializable { - protected Mode mode = Mode.PERSISTENT; - protected long ttl; - - /** - * Returns the persistence mode. - * - * @return The persistence mode. - */ - public Mode mode() { - return mode; - } - - /** - * Returns the time to live in milliseconds. - * - * @return The time to live in milliseconds. - */ - public long ttl() { - return ttl; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - buffer.writeByte(mode.ordinal()) - .writeLong(ttl); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - mode = Mode.values()[buffer.readByte()]; - ttl = buffer.readLong(); - } - - /** - * Base reference command builder. - */ - public static abstract class Builder, U extends ReferenceCommand, V> extends Command.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - - /** - * Sets the persistence mode. - * - * @param mode The persistence mode. - * @return The command builder. - */ - @SuppressWarnings("unchecked") - public T withMode(Mode mode) { - if (mode == null) - throw new NullPointerException("mode cannot be null"); - command.mode = mode; - return (T) this; - } - - /** - * Sets the time to live. - * - * @param ttl The time to live in milliseconds.. - * @return The command builder. - */ - @SuppressWarnings("unchecked") - public T withTtl(long ttl) { - command.ttl = ttl; - return (T) this; - } - - /** - * Sets the time to live. - * - * @param ttl The time to live. - * @param unit The time to live unit. - * @return The command builder. - */ - @SuppressWarnings("unchecked") - public T withTtl(long ttl, TimeUnit unit) { - command.ttl = unit.toMillis(ttl); - return (T) this; - } - } - } - - /** - * Abstract reference query. - */ - public static abstract class ReferenceQuery implements Query, AlleycatSerializable { - protected ConsistencyLevel consistency = ConsistencyLevel.LINEARIZABLE_LEASE; - - @Override - public ConsistencyLevel consistency() { - return consistency; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - buffer.writeByte(consistency.ordinal()); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - consistency = ConsistencyLevel.values()[buffer.readByte()]; - } - - /** - * Base reference query builder. - */ - public static abstract class Builder, U extends ReferenceQuery, V> extends Query.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - - /** - * Sets the query consistency level. - * - * @param consistency The query consistency level. - * @return The query builder. - */ - @SuppressWarnings("unchecked") - public T withConsistency(ConsistencyLevel consistency) { - if (consistency == null) - throw new NullPointerException("consistency cannot be null"); - query.consistency = consistency; - return (T) this; - } - } - } - - /** - * Get query. - */ - @SerializeWith(id=460) - public static class Get extends ReferenceQuery { - - /** - * Returns a new get query builder. - * - * @return A new get query builder. - */ - @SuppressWarnings("unchecked") - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Get query builder. - */ - public static class Builder extends ReferenceQuery.Builder, Get, T> { - public Builder(BuilderPool, Get> pool) { - super(pool); - } - - @Override - protected Get create() { - return new Get<>(); - } - } - } - - /** - * Set command. - */ - @SerializeWith(id=461) - public static class Set extends ReferenceCommand { - - /** - * Returns a new set command builder. - * - * @return A new set command builder. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - private Object value; - - /** - * Returns the command value. - * - * @return The command value. - */ - public Object value() { - return value; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - alleycat.writeObject(value, buffer); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - value = alleycat.readObject(buffer); - } - - @Override - public String toString() { - return String.format("%s[value=%s]", getClass().getSimpleName(), value); - } - - /** - * Put command builder. - */ - public static class Builder extends ReferenceCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Set create() { - return new Set(); - } - - /** - * Sets the command value. - * - * @param value The command value. - * @return The command builder. - */ - public Builder withValue(Object value) { - command.value = value; - return this; - } - } - } - - /** - * Compare and set command. - */ - @SerializeWith(id=462) - public static class CompareAndSet extends ReferenceCommand { - - /** - * Returns a new compare and set command builder. - * - * @return A new compare and set command builder. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - private Object expect; - private Object update; - - /** - * Returns the expected value. - * - * @return The expected value. - */ - public Object expect() { - return expect; - } - - /** - * Returns the updated value. - * - * @return The updated value. - */ - public Object update() { - return update; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - alleycat.writeObject(expect, buffer); - alleycat.writeObject(update, buffer); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - expect = alleycat.readObject(buffer); - update = alleycat.readObject(buffer); - } - - @Override - public String toString() { - return String.format("%s[expect=%s, update=%s]", getClass().getSimpleName(), expect, update); - } - - /** - * Compare and set command builder. - */ - public static class Builder extends ReferenceCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected CompareAndSet create() { - return new CompareAndSet(); - } - - /** - * Sets the expected value. - * - * @param expect The expected value. - * @return The command builder. - */ - public Builder withExpect(Object expect) { - command.expect = expect; - return this; - } - - /** - * Sets the updated value. - * - * @param update The updated value. - * @return The command builder. - */ - public Builder withUpdate(Object update) { - command.update = update; - return this; - } - } - } - - /** - * Get and set command. - */ - @SerializeWith(id=463) - public static class GetAndSet extends ReferenceCommand { - - /** - * Returns a new get and set command builder. - * - * @return A new get and set command builder. - */ - @SuppressWarnings("unchecked") - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - private Object value; - - /** - * Returns the command value. - * - * @return The command value. - */ - public Object value() { - return value; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - alleycat.writeObject(value, buffer); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - value = alleycat.readObject(buffer); - } - - @Override - public String toString() { - return String.format("%s[value=%s]", getClass().getSimpleName(), value); - } - - /** - * Put command builder. - */ - public static class Builder extends ReferenceCommand.Builder, GetAndSet, T> { - public Builder(BuilderPool, GetAndSet> pool) { - super(pool); - } - - @Override - protected GetAndSet create() { - return new GetAndSet<>(); - } - - /** - * Sets the command value. - * - * @param value The command value. - * @return The command builder. - */ - public Builder withValue(Object value) { - command.value = value; - return this; - } - } - } - - /** - * Change listen. - */ - @SerializeWith(id=464) - public static class ChangeListen implements Command, AlleycatSerializable { - - /** - * Returns a new change listen builder. - * - * @return A new change listen builder. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - - } - - /** - * Change listen builder. - */ - public static class Builder extends Command.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected ChangeListen create() { - return new ChangeListen(); - } - } - } - - /** - * Change unlisten. - */ - @SerializeWith(id=465) - public static class ChangeUnlisten implements Command, AlleycatSerializable { - - /** - * Returns a new change unlisten builder. - * - * @return A new change unlisten builder. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - - } - - /** - * Change unlisten builder. - */ - public static class Builder extends Command.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected ChangeUnlisten create() { - return new ChangeUnlisten(); - } - } - } - - /** - * Async reference state machine. - */ - public static class StateMachine extends net.kuujo.copycat.raft.server.StateMachine { - private final java.util.Set sessions = new HashSet<>(); - private final java.util.Set listeners = new HashSet<>(); - private final AtomicReference value = new AtomicReference<>(); - private Commit command; - private long version; - private long time; - - /** - * Updates the state machine timestamp. - */ - private void updateTime(Commit commit) { - this.time = Math.max(time, commit.timestamp()); - } - - @Override - public void register(Session session) { - sessions.add(session.id()); - } - - @Override - public void expire(Session session) { - sessions.remove(session.id()); - } - - @Override - public void close(Session session) { - sessions.remove(session.id()); - } - - /** - * Returns a boolean value indicating whether the given commit is active. - */ - private boolean isActive(Commit commit) { - if (commit == null) { - return false; - } else if (commit.operation().mode() == Mode.EPHEMERAL && !sessions.contains(commit.session().id())) { - return false; - } else if (commit.operation().ttl() != 0 && commit.operation().ttl() < time - commit.timestamp()) { - return false; - } - return true; - } - - /** - * Handles a listen commit. - */ - @Apply(ChangeListen.class) - protected void listen(Commit commit) { - updateTime(commit); - listeners.add(commit.session()); - } - - /** - * Handles an unlisten commit. - */ - @Apply(ChangeUnlisten.class) - protected void unlisten(Commit commit) { - updateTime(commit); - listeners.remove(commit.session()); - } - - /** - * Triggers a change event. - */ - private void change(Object value) { - for (Session session : listeners) { - session.publish(value); - } - } - - /** - * Handles a get commit. - */ - @Apply(Get.class) - protected Object get(Commit commit) { - updateTime(commit); - return value.get(); - } - - /** - * Applies a set commit. - */ - @Apply(Set.class) - protected void set(Commit commit) { - updateTime(commit); - value.set(commit.operation().value()); - command = commit; - version = commit.index(); - change(value.get()); - } - - /** - * Handles a compare and set commit. - */ - @Apply(CompareAndSet.class) - protected boolean compareAndSet(Commit commit) { - updateTime(commit); - if (isActive(command)) { - if (value.compareAndSet(commit.operation().expect(), commit.operation().update())) { - command = commit; - change(value.get()); - return true; - } - return false; - } else if (commit.operation().expect() == null) { - value.set(null); - command = commit; - version = commit.index(); - change(null); - return true; - } else { - return false; - } - } - - /** - * Handles a get and set commit. - */ - @Apply(GetAndSet.class) - protected Object getAndSet(Commit commit) { - updateTime(commit); - if (isActive(command)) { - Object result = value.getAndSet(commit.operation().value()); - command = commit; - version = commit.index(); - change(value.get()); - return result; - } else { - value.set(commit.operation().value()); - command = commit; - version = commit.index(); - change(value.get()); - return null; - } - } - - /** - * Filters all entries. - */ - @Filter(Filter.All.class) - protected boolean filterAll(Commit> commit, Compaction compaction) { - return commit.index() >= version && isActive(commit); - } - } - } diff --git a/atomic/src/main/java/net/kuujo/copycat/atomic/state/ReferenceCommands.java b/atomic/src/main/java/net/kuujo/copycat/atomic/state/ReferenceCommands.java new file mode 100644 index 0000000000..4f0683dd31 --- /dev/null +++ b/atomic/src/main/java/net/kuujo/copycat/atomic/state/ReferenceCommands.java @@ -0,0 +1,509 @@ +/* + * 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 net.kuujo.copycat.atomic.state; + +import net.kuujo.alleycat.Alleycat; +import net.kuujo.alleycat.AlleycatSerializable; +import net.kuujo.alleycat.SerializeWith; +import net.kuujo.alleycat.io.BufferInput; +import net.kuujo.alleycat.io.BufferOutput; +import net.kuujo.copycat.BuilderPool; +import net.kuujo.copycat.Mode; +import net.kuujo.copycat.raft.Command; +import net.kuujo.copycat.raft.ConsistencyLevel; +import net.kuujo.copycat.raft.Operation; +import net.kuujo.copycat.raft.Query; + +import java.util.concurrent.TimeUnit; + +/** + * Atomic reference commands. + * + * @author Jordan Halterman + */ +public class ReferenceCommands { + + private ReferenceCommands() { + } + + /** + * Abstract reference command. + */ + public static abstract class ReferenceCommand implements Command, AlleycatSerializable { + protected Mode mode = Mode.PERSISTENT; + protected long ttl; + + /** + * Returns the persistence mode. + * + * @return The persistence mode. + */ + public Mode mode() { + return mode; + } + + /** + * Returns the time to live in milliseconds. + * + * @return The time to live in milliseconds. + */ + public long ttl() { + return ttl; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + buffer.writeByte(mode.ordinal()) + .writeLong(ttl); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + mode = Mode.values()[buffer.readByte()]; + ttl = buffer.readLong(); + } + + /** + * Base reference command builder. + */ + public static abstract class Builder, U extends ReferenceCommand, V> extends Command.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + + /** + * Sets the persistence mode. + * + * @param mode The persistence mode. + * @return The command builder. + */ + @SuppressWarnings("unchecked") + public T withMode(Mode mode) { + if (mode == null) + throw new NullPointerException("mode cannot be null"); + command.mode = mode; + return (T) this; + } + + /** + * Sets the time to live. + * + * @param ttl The time to live in milliseconds.. + * @return The command builder. + */ + @SuppressWarnings("unchecked") + public T withTtl(long ttl) { + command.ttl = ttl; + return (T) this; + } + + /** + * Sets the time to live. + * + * @param ttl The time to live. + * @param unit The time to live unit. + * @return The command builder. + */ + @SuppressWarnings("unchecked") + public T withTtl(long ttl, TimeUnit unit) { + command.ttl = unit.toMillis(ttl); + return (T) this; + } + } + } + + /** + * Abstract reference query. + */ + public static abstract class ReferenceQuery implements Query, AlleycatSerializable { + protected ConsistencyLevel consistency = ConsistencyLevel.LINEARIZABLE_LEASE; + + @Override + public ConsistencyLevel consistency() { + return consistency; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + buffer.writeByte(consistency.ordinal()); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + consistency = ConsistencyLevel.values()[buffer.readByte()]; + } + + /** + * Base reference query builder. + */ + public static abstract class Builder, U extends ReferenceQuery, V> extends Query.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + + /** + * Sets the query consistency level. + * + * @param consistency The query consistency level. + * @return The query builder. + */ + @SuppressWarnings("unchecked") + public T withConsistency(ConsistencyLevel consistency) { + if (consistency == null) + throw new NullPointerException("consistency cannot be null"); + query.consistency = consistency; + return (T) this; + } + } + } + + /** + * Get query. + */ + @SerializeWith(id=460) + public static class Get extends ReferenceQuery { + + /** + * Returns a new get query builder. + * + * @return A new get query builder. + */ + @SuppressWarnings("unchecked") + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Get query builder. + */ + public static class Builder extends ReferenceQuery.Builder, Get, T> { + public Builder(BuilderPool, Get> pool) { + super(pool); + } + + @Override + protected Get create() { + return new Get<>(); + } + } + } + + /** + * Set command. + */ + @SerializeWith(id=461) + public static class Set extends ReferenceCommand { + + /** + * Returns a new set command builder. + * + * @return A new set command builder. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + private Object value; + + /** + * Returns the command value. + * + * @return The command value. + */ + public Object value() { + return value; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + alleycat.writeObject(value, buffer); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + value = alleycat.readObject(buffer); + } + + @Override + public String toString() { + return String.format("%s[value=%s]", getClass().getSimpleName(), value); + } + + /** + * Put command builder. + */ + public static class Builder extends ReferenceCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Set create() { + return new Set(); + } + + /** + * Sets the command value. + * + * @param value The command value. + * @return The command builder. + */ + public Builder withValue(Object value) { + command.value = value; + return this; + } + } + } + + /** + * Compare and set command. + */ + @SerializeWith(id=462) + public static class CompareAndSet extends ReferenceCommand { + + /** + * Returns a new compare and set command builder. + * + * @return A new compare and set command builder. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + private Object expect; + private Object update; + + /** + * Returns the expected value. + * + * @return The expected value. + */ + public Object expect() { + return expect; + } + + /** + * Returns the updated value. + * + * @return The updated value. + */ + public Object update() { + return update; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + alleycat.writeObject(expect, buffer); + alleycat.writeObject(update, buffer); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + expect = alleycat.readObject(buffer); + update = alleycat.readObject(buffer); + } + + @Override + public String toString() { + return String.format("%s[expect=%s, update=%s]", getClass().getSimpleName(), expect, update); + } + + /** + * Compare and set command builder. + */ + public static class Builder extends ReferenceCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected CompareAndSet create() { + return new CompareAndSet(); + } + + /** + * Sets the expected value. + * + * @param expect The expected value. + * @return The command builder. + */ + public Builder withExpect(Object expect) { + command.expect = expect; + return this; + } + + /** + * Sets the updated value. + * + * @param update The updated value. + * @return The command builder. + */ + public Builder withUpdate(Object update) { + command.update = update; + return this; + } + } + } + + /** + * Get and set command. + */ + @SerializeWith(id=463) + public static class GetAndSet extends ReferenceCommand { + + /** + * Returns a new get and set command builder. + * + * @return A new get and set command builder. + */ + @SuppressWarnings("unchecked") + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + private Object value; + + /** + * Returns the command value. + * + * @return The command value. + */ + public Object value() { + return value; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + alleycat.writeObject(value, buffer); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + value = alleycat.readObject(buffer); + } + + @Override + public String toString() { + return String.format("%s[value=%s]", getClass().getSimpleName(), value); + } + + /** + * Put command builder. + */ + public static class Builder extends ReferenceCommand.Builder, GetAndSet, T> { + public Builder(BuilderPool, GetAndSet> pool) { + super(pool); + } + + @Override + protected GetAndSet create() { + return new GetAndSet<>(); + } + + /** + * Sets the command value. + * + * @param value The command value. + * @return The command builder. + */ + public Builder withValue(Object value) { + command.value = value; + return this; + } + } + } + + /** + * Change listen. + */ + @SerializeWith(id=464) + public static class Listen implements Command, AlleycatSerializable { + + /** + * Returns a new change listen builder. + * + * @return A new change listen builder. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + + } + + /** + * Change listen builder. + */ + public static class Builder extends Command.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Listen create() { + return new Listen(); + } + } + } + + /** + * Change unlisten. + */ + @SerializeWith(id=465) + public static class Unlisten implements Command, AlleycatSerializable { + + /** + * Returns a new change unlisten builder. + * + * @return A new change unlisten builder. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + + } + + /** + * Change unlisten builder. + */ + public static class Builder extends Command.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Unlisten create() { + return new Unlisten(); + } + } + } + +} diff --git a/atomic/src/main/java/net/kuujo/copycat/atomic/state/ReferenceState.java b/atomic/src/main/java/net/kuujo/copycat/atomic/state/ReferenceState.java new file mode 100644 index 0000000000..7b7d571e2a --- /dev/null +++ b/atomic/src/main/java/net/kuujo/copycat/atomic/state/ReferenceState.java @@ -0,0 +1,179 @@ +/* + * 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 net.kuujo.copycat.atomic.state; + +import net.kuujo.copycat.Mode; +import net.kuujo.copycat.log.Compaction; +import net.kuujo.copycat.raft.Session; +import net.kuujo.copycat.raft.server.Apply; +import net.kuujo.copycat.raft.server.Commit; +import net.kuujo.copycat.raft.server.Filter; +import net.kuujo.copycat.raft.server.StateMachine; + +import java.util.HashSet; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Atomic reference state machine. + * + * @author Jordan Halterman + */ +public class ReferenceState extends StateMachine { + private final java.util.Set sessions = new HashSet<>(); + private final java.util.Set listeners = new HashSet<>(); + private final AtomicReference value = new AtomicReference<>(); + private Commit command; + private long version; + private long time; + + /** + * Updates the state machine timestamp. + */ + private void updateTime(Commit commit) { + this.time = Math.max(time, commit.timestamp()); + } + + @Override + public void register(Session session) { + sessions.add(session.id()); + } + + @Override + public void expire(Session session) { + sessions.remove(session.id()); + } + + @Override + public void close(Session session) { + sessions.remove(session.id()); + } + + /** + * Returns a boolean value indicating whether the given commit is active. + */ + private boolean isActive(Commit commit) { + if (commit == null) { + return false; + } else if (commit.operation().mode() == Mode.EPHEMERAL && !sessions.contains(commit.session().id())) { + return false; + } else if (commit.operation().ttl() != 0 && commit.operation().ttl() < time - commit.timestamp()) { + return false; + } + return true; + } + + /** + * Handles a listen commit. + */ + @Apply(ReferenceCommands.Listen.class) + protected void listen(Commit commit) { + updateTime(commit); + listeners.add(commit.session()); + } + + /** + * Handles an unlisten commit. + */ + @Apply(ReferenceCommands.Unlisten.class) + protected void unlisten(Commit commit) { + updateTime(commit); + listeners.remove(commit.session()); + } + + /** + * Triggers a change event. + */ + private void change(Object value) { + for (Session session : listeners) { + session.publish(value); + } + } + + /** + * Handles a get commit. + */ + @Apply(ReferenceCommands.Get.class) + protected Object get(Commit commit) { + updateTime(commit); + return value.get(); + } + + /** + * Applies a set commit. + */ + @Apply(ReferenceCommands.Set.class) + protected void set(Commit commit) { + updateTime(commit); + value.set(commit.operation().value()); + command = commit; + version = commit.index(); + change(value.get()); + } + + /** + * Handles a compare and set commit. + */ + @Apply(ReferenceCommands.CompareAndSet.class) + protected boolean compareAndSet(Commit commit) { + updateTime(commit); + if (isActive(command)) { + if (value.compareAndSet(commit.operation().expect(), commit.operation().update())) { + command = commit; + change(value.get()); + return true; + } + return false; + } else if (commit.operation().expect() == null) { + value.set(null); + command = commit; + version = commit.index(); + change(null); + return true; + } else { + return false; + } + } + + /** + * Handles a get and set commit. + */ + @Apply(ReferenceCommands.GetAndSet.class) + protected Object getAndSet(Commit commit) { + updateTime(commit); + if (isActive(command)) { + Object result = value.getAndSet(commit.operation().value()); + command = commit; + version = commit.index(); + change(value.get()); + return result; + } else { + value.set(commit.operation().value()); + command = commit; + version = commit.index(); + change(value.get()); + return null; + } + } + + /** + * Filters all entries. + */ + @Filter(Filter.All.class) + protected boolean filterAll(Commit> commit, Compaction compaction) { + return commit.index() >= version && isActive(commit); + } + +} diff --git a/atomic/src/main/resources/META-INF/services/net.kuujo.alleycat.AlleycatSerializable b/atomic/src/main/resources/META-INF/services/net.kuujo.alleycat.AlleycatSerializable index a70b7e9f55..26551a36b6 100644 --- a/atomic/src/main/resources/META-INF/services/net.kuujo.alleycat.AlleycatSerializable +++ b/atomic/src/main/resources/META-INF/services/net.kuujo.alleycat.AlleycatSerializable @@ -13,7 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # -net.kuujo.copycat.atomic.DistributedReference$CompareAndSet -net.kuujo.copycat.atomic.DistributedReference$Get -net.kuujo.copycat.atomic.DistributedReference$GetAndSet -net.kuujo.copycat.atomic.DistributedReference$Set +net.kuujo.copycat.atomic.state.ReferenceCommands$CompareAndSet +net.kuujo.copycat.atomic.state.ReferenceCommands$Get +net.kuujo.copycat.atomic.state.ReferenceCommands$GetAndSet +net.kuujo.copycat.atomic.state.ReferenceCommands$Set +net.kuujo.copycat.atomic.state.ReferenceCommands$Listen +net.kuujo.copycat.atomic.state.ReferenceCommands$Unlisten diff --git a/atomic/src/test/java/net/kuujo/copycat/atomic/DistributedReferenceTest.java b/atomic/src/test/java/net/kuujo/copycat/atomic/DistributedReferenceTest.java index 14f3f52bab..16799635e3 100644 --- a/atomic/src/test/java/net/kuujo/copycat/atomic/DistributedReferenceTest.java +++ b/atomic/src/test/java/net/kuujo/copycat/atomic/DistributedReferenceTest.java @@ -245,7 +245,7 @@ public void testCompareAndSet() throws Throwable { private List createCopycats(int nodes) throws Throwable { LocalServerRegistry registry = new LocalServerRegistry(); - List active = new ArrayList<>(); + List copycats = new ArrayList<>(); expectResumes(nodes); @@ -274,12 +274,12 @@ private List createCopycats(int nodes) throws Throwable { copycat.open().thenRun(this::resume); - active.add(copycat); + copycats.add(copycat); } await(); - return active; + return copycats; } } diff --git a/client/src/main/java/net/kuujo/copycat/raft/client/state/ClientContext.java b/client/src/main/java/net/kuujo/copycat/raft/client/state/ClientContext.java index fc0ce37ec2..60fe7abdd1 100644 --- a/client/src/main/java/net/kuujo/copycat/raft/client/state/ClientContext.java +++ b/client/src/main/java/net/kuujo/copycat/raft/client/state/ClientContext.java @@ -656,7 +656,7 @@ private void startKeepAliveTimer() { */ private void keepAlive() { if (keepAlive.compareAndSet(false, true) && getSessionId() != 0) { - keepAlive(members.members()).whenComplete((result, error) -> keepAlive.set(false)); + keepAlive(new ArrayList<>(members.members())).whenComplete((result, error) -> keepAlive.set(false)); } } diff --git a/collections/src/main/java/net/kuujo/copycat/collections/AsyncMap.java b/collections/src/main/java/net/kuujo/copycat/collections/AsyncMap.java new file mode 100644 index 0000000000..e0624423c9 --- /dev/null +++ b/collections/src/main/java/net/kuujo/copycat/collections/AsyncMap.java @@ -0,0 +1,254 @@ +/* + * 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 net.kuujo.copycat.collections; + +import net.kuujo.copycat.Mode; +import net.kuujo.copycat.raft.ConsistencyLevel; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * Asynchronous map. + * + * @author Jordan Halterman + */ +public interface AsyncMap { + + /** + * Checks whether the map is empty. + * + * @return A completable future to be completed with a boolean value indicating whether the map is empty. + */ + CompletableFuture isEmpty(); + + /** + * Checks whether the map is empty. + * + * @param consistency The query consistency level. + * @return A completable future to be completed with a boolean value indicating whether the map is empty. + */ + CompletableFuture isEmpty(ConsistencyLevel consistency); + + /** + * Gets the size of the map. + * + * @return A completable future to be completed with the number of entries in the map. + */ + CompletableFuture size(); + + /** + * Gets the size of the map. + * + * @param consistency The query consistency level. + * @return A completable future to be completed with the number of entries in the map. + */ + CompletableFuture size(ConsistencyLevel consistency); + + /** + * Checks whether the map contains a key. + * + * @param key The key to check. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture containsKey(Object key); + + /** + * Checks whether the map contains a key. + * + * @param key The key to check. + * @param consistency The query consistency level. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture containsKey(Object key, ConsistencyLevel consistency); + + /** + * Gets a value from the map. + * + * @param key The key to get. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture get(Object key); + + /** + * Gets a value from the map. + * + * @param key The key to get. + * @param consistency The query consistency level. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture get(Object key, ConsistencyLevel consistency); + + /** + * Puts a value in the map. + * + * @param key The key to set. + * @param value The value to set. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture put(K key, V value); + + /** + * Puts a value in the map. + * + * @param key The key to set. + * @param value The value to set. + * @param mode The mode in which to set the key. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture put(K key, V value, Mode mode); + + /** + * Puts a value in the map. + * + * @param key The key to set. + * @param value The value to set. + * @param ttl The time to live in milliseconds. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture put(K key, V value, long ttl); + + /** + * Puts a value in the map. + * + * @param key The key to set. + * @param value The value to set. + * @param ttl The time to live in milliseconds. + * @param mode The mode in which to set the key. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture put(K key, V value, long ttl, Mode mode); + + /** + * Puts a value in the map. + * + * @param key The key to set. + * @param value The value to set. + * @param ttl The time to live in milliseconds. + * @param unit The time to live unit. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture put(K key, V value, long ttl, TimeUnit unit); + + /** + * Puts a value in the map. + * + * @param key The key to set. + * @param value The value to set. + * @param ttl The time to live in milliseconds. + * @param unit The time to live unit. + * @param mode The mode in which to set the key. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture put(K key, V value, long ttl, TimeUnit unit, Mode mode); + + /** + * Removes a value from the map. + * + * @param key The key to remove. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture remove(Object key); + + /** + * Gets the value of a key or the given default value if the key does not exist. + * + * @param key The key to get. + * @param defaultValue The default value to return if the key does not exist. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture getOrDefault(Object key, V defaultValue); + + /** + * Gets the value of a key or the given default value if the key does not exist. + * + * @param key The key to get. + * @param defaultValue The default value to return if the key does not exist. + * @param consistency The query consistency level. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture getOrDefault(Object key, V defaultValue, ConsistencyLevel consistency); + + /** + * Puts a value in the map if the given key does not exist. + * + * @param key The key to set. + * @param value The value to set if the given key does not exist. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture putIfAbsent(K key, V value); + + /** + * Puts a value in the map if the given key does not exist. + * + * @param key The key to set. + * @param value The value to set if the given key does not exist. + * @param ttl The time to live in milliseconds. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture putIfAbsent(K key, V value, long ttl); + + /** + * Puts a value in the map if the given key does not exist. + * + * @param key The key to set. + * @param value The value to set if the given key does not exist. + * @param ttl The time to live in milliseconds. + * @param mode The mode in which to set the key. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture putIfAbsent(K key, V value, long ttl, Mode mode); + + /** + * Puts a value in the map if the given key does not exist. + * + * @param key The key to set. + * @param value The value to set if the given key does not exist. + * @param ttl The time to live. + * @param unit The time to live unit. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture putIfAbsent(K key, V value, long ttl, TimeUnit unit); + + /** + * Puts a value in the map if the given key does not exist. + * + * @param key The key to set. + * @param value The value to set if the given key does not exist. + * @param ttl The time to live. + * @param unit The time to live unit. + * @param mode The mode in which to set the key. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture putIfAbsent(K key, V value, long ttl, TimeUnit unit, Mode mode); + + /** + * Removes a key and value from the map. + * + * @param key The key to remove. + * @param value The value to remove. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture remove(Object key, Object value); + + /** + * Removes all entries from the map. + * + * @return A completable future to be completed once the operation is complete. + */ + CompletableFuture clear(); + +} diff --git a/collections/src/main/java/net/kuujo/copycat/collections/AsyncSet.java b/collections/src/main/java/net/kuujo/copycat/collections/AsyncSet.java new file mode 100644 index 0000000000..f650f89ca5 --- /dev/null +++ b/collections/src/main/java/net/kuujo/copycat/collections/AsyncSet.java @@ -0,0 +1,155 @@ +/* + * 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 net.kuujo.copycat.collections; + +import net.kuujo.copycat.Mode; +import net.kuujo.copycat.raft.ConsistencyLevel; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * Asynchronous set. + * + * @author Jordan Halterman + */ +public interface AsyncSet { + + /** + * Adds a value to the set. + * + * @param value The value to add. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture add(T value); + + /** + * Adds a value to the set with a TTL. + * + * @param value The value to add. + * @param mode The persistence mode. + * @return A completable future to be completed with the result once complete. + */ + @SuppressWarnings("unchecked") + CompletableFuture add(T value, Mode mode); + + /** + * Adds a value to the set with a TTL. + * + * @param value The value to add. + * @param ttl The time to live in milliseconds. + * @return A completable future to be completed with the result once complete. + */ + @SuppressWarnings("unchecked") + CompletableFuture add(T value, long ttl); + + /** + * Adds a value to the set with a TTL. + * + * @param value The value to add. + * @param ttl The time to live. + * @param unit The time to live unit. + * @return A completable future to be completed with the result once complete. + */ + @SuppressWarnings("unchecked") + CompletableFuture add(T value, long ttl, TimeUnit unit); + + /** + * Adds a value to the set with a TTL. + * + * @param value The value to add. + * @param ttl The time to live in milliseconds. + * @param mode The persistence mode. + * @return A completable future to be completed with the result once complete. + */ + @SuppressWarnings("unchecked") + CompletableFuture add(T value, long ttl, Mode mode); + + /** + * Adds a value to the set with a TTL. + * + * @param value The value to add. + * @param ttl The time to live. + * @param unit The time to live unit. + * @param mode The persistence mode. + * @return A completable future to be completed with the result once complete. + */ + @SuppressWarnings("unchecked") + CompletableFuture add(T value, long ttl, TimeUnit unit, Mode mode); + + /** + * Removes a value from the set. + * + * @param value The value to remove. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture remove(T value); + + /** + * Checks whether the set contains a value. + * + * @param value The value to check. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture contains(Object value); + + /** + * Checks whether the set contains a value. + * + * @param value The value to check. + * @param consistency The query consistency level. + * @return A completable future to be completed with the result once complete. + */ + CompletableFuture contains(Object value, ConsistencyLevel consistency); + + /** + * Gets the set size. + * + * @return A completable future to be completed with the set size. + */ + CompletableFuture size(); + + /** + * Gets the set size. + * + * @param consistency The query consistency level. + * @return A completable future to be completed with the set size. + */ + CompletableFuture size(ConsistencyLevel consistency); + + /** + * Checks whether the set is empty. + * + * @return A completable future to be completed with a boolean value indicating whether the set is empty. + */ + CompletableFuture isEmpty(); + + /** + * Checks whether the set is empty. + * + * @param consistency The query consistency level. + * @return A completable future to be completed with a boolean value indicating whether the set is empty. + */ + CompletableFuture isEmpty(ConsistencyLevel consistency); + + /** + * Removes all values from the set. + * + * @return A completable future to be completed once the operation is complete. + */ + CompletableFuture clear(); + +} diff --git a/collections/src/main/java/net/kuujo/copycat/collections/AsyncTopic.java b/collections/src/main/java/net/kuujo/copycat/collections/AsyncTopic.java new file mode 100644 index 0000000000..752c10d7ba --- /dev/null +++ b/collections/src/main/java/net/kuujo/copycat/collections/AsyncTopic.java @@ -0,0 +1,46 @@ +/* + * 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 net.kuujo.copycat.collections; + +import net.kuujo.copycat.Listener; +import net.kuujo.copycat.ListenerContext; + +import java.util.concurrent.CompletableFuture; + +/** + * Asynchronous topic. + * + * @author Jordan Halterman + */ +public interface AsyncTopic { + + /** + * Publishes a message to the topic. + * + * @param message The message to publish. + * @return A completable future to be completed once the message has been published. + */ + CompletableFuture publish(T message); + + /** + * Sets a message listener on the topic. + * + * @param listener The message listener. + * @return The listener context. + */ + ListenerContext onMessage(Listener listener); + +} diff --git a/collections/src/main/java/net/kuujo/copycat/collections/DistributedMap.java b/collections/src/main/java/net/kuujo/copycat/collections/DistributedMap.java index 00b8cb618f..55f65e43e3 100644 --- a/collections/src/main/java/net/kuujo/copycat/collections/DistributedMap.java +++ b/collections/src/main/java/net/kuujo/copycat/collections/DistributedMap.java @@ -15,25 +15,14 @@ */ package net.kuujo.copycat.collections; -import net.kuujo.alleycat.Alleycat; -import net.kuujo.alleycat.AlleycatSerializable; -import net.kuujo.alleycat.SerializeWith; -import net.kuujo.alleycat.io.BufferInput; -import net.kuujo.alleycat.io.BufferOutput; -import net.kuujo.copycat.BuilderPool; import net.kuujo.copycat.Mode; import net.kuujo.copycat.Resource; import net.kuujo.copycat.Stateful; -import net.kuujo.copycat.log.Compaction; -import net.kuujo.copycat.raft.*; -import net.kuujo.copycat.raft.server.Apply; -import net.kuujo.copycat.raft.server.Commit; -import net.kuujo.copycat.raft.server.Filter; +import net.kuujo.copycat.collections.state.MapCommands; +import net.kuujo.copycat.collections.state.MapState; +import net.kuujo.copycat.raft.ConsistencyLevel; +import net.kuujo.copycat.raft.Raft; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -44,134 +33,81 @@ * @param The map entry type. * @author Jordan Halterman */ -@Stateful(DistributedMap.StateMachine.class) -public class DistributedMap extends Resource { +@Stateful(MapState.class) +public class DistributedMap extends Resource implements AsyncMap { public DistributedMap(Raft protocol) { super(protocol); } - /** - * Checks whether the map is empty. - * - * @return A completable future to be completed with a boolean value indicating whether the map is empty. - */ + @Override public CompletableFuture isEmpty() { - return submit(IsEmpty.builder().build()); + return submit(MapCommands.IsEmpty.builder().build()); } - /** - * Checks whether the map is empty. - * - * @param consistency The query consistency level. - * @return A completable future to be completed with a boolean value indicating whether the map is empty. - */ + @Override public CompletableFuture isEmpty(ConsistencyLevel consistency) { - return submit(IsEmpty.builder().withConsistency(consistency).build()); + return submit(MapCommands.IsEmpty.builder().withConsistency(consistency).build()); } - /** - * Gets the size of the map. - * - * @return A completable future to be completed with the number of entries in the map. - */ + @Override public CompletableFuture size() { - return submit(Size.builder().build()); + return submit(MapCommands.Size.builder().build()); } - /** - * Gets the size of the map. - * - * @param consistency The query consistency level. - * @return A completable future to be completed with the number of entries in the map. - */ + @Override public CompletableFuture size(ConsistencyLevel consistency) { - return submit(Size.builder().withConsistency(consistency).build()); + return submit(MapCommands.Size.builder().withConsistency(consistency).build()); } - /** - * Checks whether the map contains a key. - * - * @param key The key to check. - * @return A completable future to be completed with the result once complete. - */ + @Override public CompletableFuture containsKey(Object key) { - return submit(ContainsKey.builder() + return submit(MapCommands.ContainsKey.builder() .withKey(key) .build()); } - /** - * Checks whether the map contains a key. - * - * @param key The key to check. - * @param consistency The query consistency level. - * @return A completable future to be completed with the result once complete. - */ + @Override public CompletableFuture containsKey(Object key, ConsistencyLevel consistency) { - return submit(ContainsKey.builder() + return submit(MapCommands.ContainsKey.builder() .withKey(key) .withConsistency(consistency) .build()); } - /** - * Gets a value from the map. - * - * @param key The key to get. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture get(Object key) { - return submit(Get.builder() + return submit(MapCommands.Get.builder() .withKey(key) .build()) .thenApply(result -> (V) result); } - /** - * Gets a value from the map. - * - * @param key The key to get. - * @param consistency The query consistency level. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture get(Object key, ConsistencyLevel consistency) { - return submit(Get.builder() + return submit(MapCommands.Get.builder() .withKey(key) .withConsistency(consistency) .build()) .thenApply(result -> (V) result); } - /** - * Puts a value in the map. - * - * @param key The key to set. - * @param value The value to set. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture put(K key, V value) { - return submit(Put.builder() + return submit(MapCommands.Put.builder() .withKey(key) .withValue(value) .build()) .thenApply(result -> (V) result); } - /** - * Puts a value in the map. - * - * @param key The key to set. - * @param value The value to set. - * @param mode The mode in which to set the key. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture put(K key, V value, Mode mode) { - return submit(Put.builder() + return submit(MapCommands.Put.builder() .withKey(key) .withValue(value) .withMode(mode) @@ -179,17 +115,10 @@ public CompletableFuture put(K key, V value, Mode mode) { .thenApply(result -> (V) result); } - /** - * Puts a value in the map. - * - * @param key The key to set. - * @param value The value to set. - * @param ttl The time to live in milliseconds. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture put(K key, V value, long ttl) { - return submit(Put.builder() + return submit(MapCommands.Put.builder() .withKey(key) .withValue(value) .withTtl(ttl) @@ -197,18 +126,10 @@ public CompletableFuture put(K key, V value, long ttl) { .thenApply(result -> (V) result); } - /** - * Puts a value in the map. - * - * @param key The key to set. - * @param value The value to set. - * @param ttl The time to live in milliseconds. - * @param mode The mode in which to set the key. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture put(K key, V value, long ttl, Mode mode) { - return submit(Put.builder() + return submit(MapCommands.Put.builder() .withKey(key) .withValue(value) .withTtl(ttl) @@ -217,18 +138,10 @@ public CompletableFuture put(K key, V value, long ttl, Mode mode) { .thenApply(result -> (V) result); } - /** - * Puts a value in the map. - * - * @param key The key to set. - * @param value The value to set. - * @param ttl The time to live in milliseconds. - * @param unit The time to live unit. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture put(K key, V value, long ttl, TimeUnit unit) { - return submit(Put.builder() + return submit(MapCommands.Put.builder() .withKey(key) .withValue(value) .withTtl(ttl, unit) @@ -236,19 +149,10 @@ public CompletableFuture put(K key, V value, long ttl, TimeUnit unit) { .thenApply(result -> (V) result); } - /** - * Puts a value in the map. - * - * @param key The key to set. - * @param value The value to set. - * @param ttl The time to live in milliseconds. - * @param unit The time to live unit. - * @param mode The mode in which to set the key. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture put(K key, V value, long ttl, TimeUnit unit, Mode mode) { - return submit(Put.builder() + return submit(MapCommands.Put.builder() .withKey(key) .withValue(value) .withTtl(ttl, unit) @@ -257,47 +161,29 @@ public CompletableFuture put(K key, V value, long ttl, TimeUnit unit, Mode mo .thenApply(result -> (V) result); } - /** - * Removes a value from the map. - * - * @param key The key to remove. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture remove(Object key) { - return submit(Remove.builder() + return submit(MapCommands.Remove.builder() .withKey(key) .build()) .thenApply(result -> (V) result); } - /** - * Gets the value of a key or the given default value if the key does not exist. - * - * @param key The key to get. - * @param defaultValue The default value to return if the key does not exist. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture getOrDefault(Object key, V defaultValue) { - return submit(GetOrDefault.builder() + return submit(MapCommands.GetOrDefault.builder() .withKey(key) .withDefaultValue(defaultValue) .build()) .thenApply(result -> (V) result); } - /** - * Gets the value of a key or the given default value if the key does not exist. - * - * @param key The key to get. - * @param defaultValue The default value to return if the key does not exist. - * @param consistency The query consistency level. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture getOrDefault(Object key, V defaultValue, ConsistencyLevel consistency) { - return submit(GetOrDefault.builder() + return submit(MapCommands.GetOrDefault.builder() .withKey(key) .withDefaultValue(defaultValue) .withConsistency(consistency) @@ -305,33 +191,20 @@ public CompletableFuture getOrDefault(Object key, V defaultValue, Consistency .thenApply(result -> (V) result); } - /** - * Puts a value in the map if the given key does not exist. - * - * @param key The key to set. - * @param value The value to set if the given key does not exist. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture putIfAbsent(K key, V value) { - return submit(PutIfAbsent.builder() + return submit(MapCommands.PutIfAbsent.builder() .withKey(key) .withValue(value) .build()) .thenApply(result -> (V) result); } - /** - * Puts a value in the map if the given key does not exist. - * - * @param key The key to set. - * @param value The value to set if the given key does not exist. - * @param ttl The time to live in milliseconds. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture putIfAbsent(K key, V value, long ttl) { - return submit(PutIfAbsent.builder() + return submit(MapCommands.PutIfAbsent.builder() .withKey(key) .withValue(value) .withTtl(ttl) @@ -339,18 +212,10 @@ public CompletableFuture putIfAbsent(K key, V value, long ttl) { .thenApply(result -> (V) result); } - /** - * Puts a value in the map if the given key does not exist. - * - * @param key The key to set. - * @param value The value to set if the given key does not exist. - * @param ttl The time to live in milliseconds. - * @param mode The mode in which to set the key. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture putIfAbsent(K key, V value, long ttl, Mode mode) { - return submit(PutIfAbsent.builder() + return submit(MapCommands.PutIfAbsent.builder() .withKey(key) .withValue(value) .withTtl(ttl) @@ -359,18 +224,10 @@ public CompletableFuture putIfAbsent(K key, V value, long ttl, Mode mode) { .thenApply(result -> (V) result); } - /** - * Puts a value in the map if the given key does not exist. - * - * @param key The key to set. - * @param value The value to set if the given key does not exist. - * @param ttl The time to live. - * @param unit The time to live unit. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture putIfAbsent(K key, V value, long ttl, TimeUnit unit) { - return submit(PutIfAbsent.builder() + return submit(MapCommands.PutIfAbsent.builder() .withKey(key) .withValue(value) .withTtl(ttl, unit) @@ -378,19 +235,10 @@ public CompletableFuture putIfAbsent(K key, V value, long ttl, TimeUnit unit) .thenApply(result -> (V) result); } - /** - * Puts a value in the map if the given key does not exist. - * - * @param key The key to set. - * @param value The value to set if the given key does not exist. - * @param ttl The time to live. - * @param unit The time to live unit. - * @param mode The mode in which to set the key. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture putIfAbsent(K key, V value, long ttl, TimeUnit unit, Mode mode) { - return submit(PutIfAbsent.builder() + return submit(MapCommands.PutIfAbsent.builder() .withKey(key) .withValue(value) .withTtl(ttl, unit) @@ -399,787 +247,18 @@ public CompletableFuture putIfAbsent(K key, V value, long ttl, TimeUnit unit, .thenApply(result -> (V) result); } - /** - * Removes a key and value from the map. - * - * @param key The key to remove. - * @param value The value to remove. - * @return A completable future to be completed with the result once complete. - */ + @Override public CompletableFuture remove(Object key, Object value) { - return submit(Remove.builder() + return submit(MapCommands.Remove.builder() .withKey(key) .withValue(value) .build()) .thenApply(result -> (boolean) result); } - /** - * Removes all entries from the map. - * - * @return A completable future to be completed once the operation is complete. - */ + @Override public CompletableFuture clear() { - return submit(Clear.builder().build()); - } - - /** - * Abstract map command. - */ - public static abstract class MapCommand implements Command, AlleycatSerializable { - - /** - * Base map command builder. - */ - public static abstract class Builder, U extends MapCommand, V> extends Command.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - } - } - - /** - * Abstract map query. - */ - public static abstract class MapQuery implements Query, AlleycatSerializable { - protected ConsistencyLevel consistency = ConsistencyLevel.LINEARIZABLE_LEASE; - - @Override - public ConsistencyLevel consistency() { - return consistency; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - buffer.writeByte(consistency.ordinal()); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - consistency = ConsistencyLevel.values()[buffer.readByte()]; - } - - /** - * Base map query builder. - */ - public static abstract class Builder, U extends MapQuery, V> extends Query.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - - /** - * Sets the query consistency level. - * - * @param consistency The query consistency level. - * @return The query builder. - */ - @SuppressWarnings("unchecked") - public T withConsistency(ConsistencyLevel consistency) { - if (consistency == null) - throw new NullPointerException("consistency cannot be null"); - query.consistency = consistency; - return (T) this; - } - } - } - - /** - * Abstract key-based command. - */ - public static abstract class KeyCommand extends MapCommand { - protected Object key; - - /** - * Returns the key. - */ - public Object key() { - return key; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - alleycat.writeObject(key, buffer); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - key = alleycat.readObject(buffer); - } - - /** - * Base key command builder. - */ - public static abstract class Builder, U extends KeyCommand, V> extends MapCommand.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - - /** - * Sets the command key. - * - * @param key The command key - * @return The command builder. - */ - @SuppressWarnings("unchecked") - public T withKey(Object key) { - command.key = key; - return (T) this; - } - } - } - - /** - * Abstract key-based query. - */ - public static abstract class KeyQuery extends MapQuery { - protected Object key; - - /** - * Returns the key. - */ - public Object key() { - return key; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - super.writeObject(buffer, alleycat); - alleycat.writeObject(key, buffer); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - super.readObject(buffer, alleycat); - key = alleycat.readObject(buffer); - } - - /** - * Base key query builder. - */ - public static abstract class Builder, U extends KeyQuery, V> extends MapQuery.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - - /** - * Sets the query key. - * - * @param key The query key - * @return The query builder. - */ - @SuppressWarnings("unchecked") - public T withKey(Object key) { - query.key = key; - return (T) this; - } - } - } - - /** - * Contains key command. - */ - @SerializeWith(id=440) - public static class ContainsKey extends KeyQuery { - - /** - * Returns a builder for this command. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Contains key builder. - */ - public static class Builder extends KeyQuery.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected ContainsKey create() { - return new ContainsKey(); - } - } - } - - /** - * Key/value command. - */ - public static abstract class KeyValueCommand extends KeyCommand { - protected Object value; - - /** - * Returns the command value. - */ - public Object value() { - return value; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - super.writeObject(buffer, alleycat); - alleycat.writeObject(value, buffer); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - super.readObject(buffer, alleycat); - value = alleycat.readObject(buffer); - } - - /** - * Key/value command builder. - */ - public static abstract class Builder, U extends KeyValueCommand, V> extends KeyCommand.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - - /** - * Sets the command value. - * - * @param value The command value. - * @return The command builder. - */ - @SuppressWarnings("unchecked") - public T withValue(Object value) { - command.value = value; - return (T) this; - } - } - } - - /** - * TTL command. - */ - public static abstract class TtlCommand extends KeyValueCommand { - protected Mode mode = Mode.PERSISTENT; - protected long ttl; - - /** - * Returns the persistence mode. - * - * @return The persistence mode. - */ - public Mode mode() { - return mode; - } - - /** - * Returns the time to live in milliseconds. - * - * @return The time to live in milliseconds. - */ - public long ttl() { - return ttl; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - super.writeObject(buffer, alleycat); - buffer.writeByte(mode.ordinal()).writeLong(ttl); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - super.readObject(buffer, alleycat); - mode = Mode.values()[buffer.readByte()]; - ttl = buffer.readLong(); - } - - /** - * TTL command builder. - */ - public static abstract class Builder, U extends TtlCommand, V> extends KeyValueCommand.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - - /** - * Sets the persistence mode. - * - * @param mode The persistence mode. - * @return The command builder. - */ - public Builder withMode(Mode mode) { - command.mode = mode; - return this; - } - - /** - * Sets the time to live. - * - * @param ttl The time to live in milliseconds.. - * @return The command builder. - */ - public Builder withTtl(long ttl) { - command.ttl = ttl; - return this; - } - - /** - * Sets the time to live. - * - * @param ttl The time to live. - * @param unit The time to live unit. - * @return The command builder. - */ - public Builder withTtl(long ttl, TimeUnit unit) { - command.ttl = unit.toMillis(ttl); - return this; - } - } - } - - /** - * Put command. - */ - @SerializeWith(id=441) - public static class Put extends TtlCommand { - - /** - * Returns a builder for this command. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Put command builder. - */ - public static class Builder extends TtlCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Put create() { - return new Put(); - } - } - } - - /** - * Put if absent command. - */ - @SerializeWith(id=442) - public static class PutIfAbsent extends TtlCommand { - - /** - * Returns a builder for this command. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Put command builder. - */ - public static class Builder extends TtlCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected PutIfAbsent create() { - return new PutIfAbsent(); - } - } - } - - /** - * Get query. - */ - @SerializeWith(id=443) - public static class Get extends KeyQuery { - - /** - * Returns a builder for this query. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Get query builder. - */ - public static class Builder extends KeyQuery.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Get create() { - return new Get(); - } - } - } - - /** - * Get or default query. - */ - @SerializeWith(id=444) - public static class GetOrDefault extends KeyQuery { - - /** - * Returns a builder for this query. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - private Object defaultValue; - - /** - * Returns the default value. - * - * @return The default value. - */ - public Object defaultValue() { - return defaultValue; - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - super.readObject(buffer, alleycat); - defaultValue = alleycat.readObject(buffer); - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - super.writeObject(buffer, alleycat); - alleycat.writeObject(defaultValue, buffer); - } - - /** - * Get command builder. - */ - public static class Builder extends KeyQuery.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected GetOrDefault create() { - return new GetOrDefault(); - } - - /** - * Sets the default value. - * - * @param defaultValue The default value. - * @return The query builder. - */ - public Builder withDefaultValue(Object defaultValue) { - query.defaultValue = defaultValue; - return this; - } - } - } - - /** - * Remove command. - */ - @SerializeWith(id=445) - public static class Remove extends KeyValueCommand { - - /** - * Returns a builder for this command. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Get command builder. - */ - public static class Builder extends KeyValueCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Remove create() { - return new Remove(); - } - } - } - - /** - * Is empty query. - */ - @SerializeWith(id=446) - public static class IsEmpty extends MapQuery { - - /** - * Returns a builder for this command. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Is empty command builder. - */ - public static class Builder extends MapQuery.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected IsEmpty create() { - return new IsEmpty(); - } - } - } - - /** - * Size query. - */ - @SerializeWith(id=447) - public static class Size extends MapQuery { - - /** - * Returns a builder for this command. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Is empty command builder. - */ - public static class Builder extends MapQuery.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Size create() { - return new Size(); - } - } - } - - /** - * Clear command. - */ - @SerializeWith(id=448) - public static class Clear extends MapCommand { - - /** - * Returns a builder for this command. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - - } - - /** - * Get command builder. - */ - public static class Builder extends MapCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Clear create() { - return new Clear(); - } - } - } - - /** - * Map state machine. - */ - public static class StateMachine extends net.kuujo.copycat.raft.server.StateMachine { - private final Map> map = new HashMap<>(); - private final Set sessions = new HashSet<>(); - private long time; - - /** - * Updates the wall clock time. - */ - private void updateTime(Commit commit) { - time = Math.max(time, commit.timestamp()); - } - - @Override - public void register(Session session) { - sessions.add(session.id()); - } - - @Override - public void expire(Session session) { - sessions.remove(session.id()); - } - - @Override - public void close(Session session) { - sessions.remove(session.id()); - } - - /** - * Returns a boolean value indicating whether the given commit is active. - */ - private boolean isActive(Commit commit) { - if (commit == null) { - return false; - } else if (commit.operation().mode() == Mode.EPHEMERAL && !sessions.contains(commit.session().id())) { - return false; - } else if (commit.operation().ttl() != 0 && commit.operation().ttl() < time - commit.timestamp()) { - return false; - } - return true; - } - - /** - * Handles a contains key commit. - */ - @Apply(ContainsKey.class) - protected boolean containsKey(Commit commit) { - updateTime(commit); - Commit command = map.get(commit.operation().key()); - if (!isActive(command)) { - map.remove(commit.operation().key()); - return false; - } - return true; - } - - /** - * Handles a get commit. - */ - @Apply(Get.class) - protected Object get(Commit commit) { - updateTime(commit); - Commit command = map.get(commit.operation().key()); - if (command != null) { - if (!isActive(command)) { - map.remove(commit.operation().key()); - } else { - return command.operation().value(); - } - } - return null; - } - - /** - * Handles a get or default commit. - */ - @Apply(GetOrDefault.class) - protected Object getOrDefault(Commit commit) { - updateTime(commit); - Commit command = map.get(commit.operation().key()); - if (command == null) { - return commit.operation().defaultValue(); - } else if (!isActive(command)) { - map.remove(commit.operation().key()); - } else { - return command.operation().value(); - } - return commit.operation().defaultValue(); - } - - /** - * Handles a put commit. - */ - @Apply(Put.class) - protected Object put(Commit commit) { - updateTime(commit); - Commit command = map.put(commit.operation().key(), commit); - return isActive(command) ? command.operation().value : null; - } - - /** - * Handles a put if absent commit. - */ - @Apply(PutIfAbsent.class) - protected Object putIfAbsent(Commit commit) { - updateTime(commit); - Commit command = map.putIfAbsent(commit.operation().key(), commit); - return isActive(command) ? command.operation().value : null; - } - - /** - * Filters a put and put if absent commit. - */ - @Filter({Put.class, PutIfAbsent.class}) - protected boolean filterPut(Commit commit) { - Commit command = map.get(commit.operation().key()); - return isActive(command) && command.index() == commit.index(); - } - - /** - * Handles a remove commit. - */ - @Apply(Remove.class) - protected Object remove(Commit commit) { - updateTime(commit); - if (commit.operation().value() != null) { - Commit command = map.get(commit.operation().key()); - if (!isActive(command)) { - map.remove(commit.operation().key()); - } else { - Object value = command.operation().value(); - if ((value == null && commit.operation().value() == null) || (value != null && commit.operation().value() != null && value.equals(commit.operation().value()))) { - map.remove(commit.operation().key()); - return true; - } - return false; - } - return false; - } else { - Commit command = map.remove(commit.operation().key()); - return isActive(command) ? command.operation().value() : null; - } - } - - /** - * Filters a remove commit. - */ - @Filter(value={Remove.class, Clear.class}, compaction=Compaction.Type.MAJOR) - protected boolean filterRemove(Commit commit, Compaction compaction) { - return commit.index() > compaction.index(); - } - - /** - * Handles a size commit. - */ - @Apply(Size.class) - protected int size(Commit commit) { - updateTime(commit); - return map.size(); - } - - /** - * Handles an is empty commit. - */ - @Apply(IsEmpty.class) - protected boolean isEmpty(Commit commit) { - updateTime(commit); - return map.isEmpty(); - } - - /** - * Handles a clear commit. - */ - @Apply(Clear.class) - protected void clear(Commit commit) { - updateTime(commit); - map.clear(); - } + return submit(MapCommands.Clear.builder().build()); } } diff --git a/collections/src/main/java/net/kuujo/copycat/collections/DistributedSet.java b/collections/src/main/java/net/kuujo/copycat/collections/DistributedSet.java index 39b2175427..83fe6452b8 100644 --- a/collections/src/main/java/net/kuujo/copycat/collections/DistributedSet.java +++ b/collections/src/main/java/net/kuujo/copycat/collections/DistributedSet.java @@ -15,25 +15,14 @@ */ package net.kuujo.copycat.collections; -import net.kuujo.alleycat.Alleycat; -import net.kuujo.alleycat.AlleycatSerializable; -import net.kuujo.alleycat.SerializeWith; -import net.kuujo.alleycat.io.BufferInput; -import net.kuujo.alleycat.io.BufferOutput; -import net.kuujo.copycat.BuilderPool; import net.kuujo.copycat.Mode; import net.kuujo.copycat.Resource; import net.kuujo.copycat.Stateful; -import net.kuujo.copycat.log.Compaction; -import net.kuujo.copycat.raft.*; -import net.kuujo.copycat.raft.server.Apply; -import net.kuujo.copycat.raft.server.Commit; -import net.kuujo.copycat.raft.server.Filter; +import net.kuujo.copycat.collections.state.SetCommands; +import net.kuujo.copycat.collections.state.SetState; +import net.kuujo.copycat.raft.ConsistencyLevel; +import net.kuujo.copycat.raft.Raft; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -43,711 +32,112 @@ * @param The set value type. * @author Jordan Halterman */ -@Stateful(DistributedSet.StateMachine.class) -public class DistributedSet extends Resource { +@Stateful(SetState.class) +public class DistributedSet extends Resource implements AsyncSet { public DistributedSet(Raft protocol) { super(protocol); } - /** - * Adds a value to the set. - * - * @param value The value to add. - * @return A completable future to be completed with the result once complete. - */ + @Override public CompletableFuture add(T value) { - return submit(Add.builder() + return submit(SetCommands.Add.builder() .withValue(value.hashCode()) .build()); } - /** - * Adds a value to the set with a TTL. - * - * @param value The value to add. - * @param ttl The time to live in milliseconds. - * @return A completable future to be completed with the result once complete. - */ + @Override + @SuppressWarnings("unchecked") + public CompletableFuture add(T value, Mode mode) { + return submit(SetCommands.Add.builder() + .withValue(value.hashCode()) + .withMode(mode) + .build()); + } + + @Override @SuppressWarnings("unchecked") public CompletableFuture add(T value, long ttl) { - return submit(Add.builder() + return submit(SetCommands.Add.builder() .withValue(value.hashCode()) .withTtl(ttl) .build()); } - /** - * Adds a value to the set with a TTL. - * - * @param value The value to add. - * @param ttl The time to live. - * @param unit The time to live unit. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture add(T value, long ttl, TimeUnit unit) { - return submit(Add.builder() + return submit(SetCommands.Add.builder() .withValue(value.hashCode()) .withTtl(ttl, unit) .build()); } - /** - * Adds a value to the set with a TTL. - * - * @param value The value to add. - * @param ttl The time to live in milliseconds. - * @param mode The persistence mode. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture add(T value, long ttl, Mode mode) { - return submit(Add.builder() + return submit(SetCommands.Add.builder() .withValue(value.hashCode()) .withTtl(ttl) .withMode(mode) .build()); } - /** - * Adds a value to the set with a TTL. - * - * @param value The value to add. - * @param ttl The time to live. - * @param unit The time to live unit. - * @param mode The persistence mode. - * @return A completable future to be completed with the result once complete. - */ + @Override @SuppressWarnings("unchecked") public CompletableFuture add(T value, long ttl, TimeUnit unit, Mode mode) { - return submit(Add.builder() + return submit(SetCommands.Add.builder() .withValue(value.hashCode()) .withTtl(ttl, unit) .withMode(mode) .build()); } - /** - * Removes a value from the set. - * - * @param value The value to remove. - * @return A completable future to be completed with the result once complete. - */ + @Override public CompletableFuture remove(T value) { - return submit(Remove.builder() + return submit(SetCommands.Remove.builder() .withValue(value.hashCode()) .build()); } - /** - * Checks whether the set contains a value. - * - * @param value The value to check. - * @return A completable future to be completed with the result once complete. - */ + @Override public CompletableFuture contains(Object value) { - return submit(Contains.builder() + return submit(SetCommands.Contains.builder() .withValue(value.hashCode()) .build()); } - /** - * Checks whether the set contains a value. - * - * @param value The value to check. - * @param consistency The query consistency level. - * @return A completable future to be completed with the result once complete. - */ + @Override public CompletableFuture contains(Object value, ConsistencyLevel consistency) { - return submit(Contains.builder() + return submit(SetCommands.Contains.builder() .withValue(value.hashCode()) .withConsistency(consistency) .build()); } - /** - * Gets the set size. - * - * @return A completable future to be completed with the set size. - */ + @Override public CompletableFuture size() { - return submit(Size.builder().build()); + return submit(SetCommands.Size.builder().build()); } - /** - * Gets the set size. - * - * @param consistency The query consistency level. - * @return A completable future to be completed with the set size. - */ + @Override public CompletableFuture size(ConsistencyLevel consistency) { - return submit(Size.builder().withConsistency(consistency).build()); + return submit(SetCommands.Size.builder().withConsistency(consistency).build()); } - /** - * Checks whether the set is empty. - * - * @return A completable future to be completed with a boolean value indicating whether the set is empty. - */ + @Override public CompletableFuture isEmpty() { - return submit(IsEmpty.builder().build()); + return submit(SetCommands.IsEmpty.builder().build()); } - /** - * Checks whether the set is empty. - * - * @param consistency The query consistency level. - * @return A completable future to be completed with a boolean value indicating whether the set is empty. - */ + @Override public CompletableFuture isEmpty(ConsistencyLevel consistency) { - return submit(IsEmpty.builder().withConsistency(consistency).build()); + return submit(SetCommands.IsEmpty.builder().withConsistency(consistency).build()); } - /** - * Removes all values from the set. - * - * @return A completable future to be completed once the operation is complete. - */ + @Override public CompletableFuture clear() { - return submit(Clear.builder().build()); - } - - /** - * Abstract set command. - */ - public static abstract class SetCommand implements Command, AlleycatSerializable { - - /** - * Base set command builder. - */ - public static abstract class Builder, U extends SetCommand, V> extends Command.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - } - } - - /** - * Abstract set query. - */ - public static abstract class SetQuery implements Query, AlleycatSerializable { - protected ConsistencyLevel consistency = ConsistencyLevel.LINEARIZABLE_LEASE; - - @Override - public ConsistencyLevel consistency() { - return consistency; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - buffer.writeByte(consistency.ordinal()); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - consistency = ConsistencyLevel.values()[buffer.readByte()]; - } - - /** - * Base set query builder. - */ - public static abstract class Builder, U extends SetQuery, V> extends Query.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - - /** - * Sets the query consistency level. - * - * @param consistency The query consistency level. - * @return The query builder. - */ - @SuppressWarnings("unchecked") - public T withConsistency(ConsistencyLevel consistency) { - query.consistency = consistency; - return (T) this; - } - } - } - - /** - * Abstract value command. - */ - public static abstract class ValueCommand extends SetCommand { - protected int value; - - /** - * Returns the value. - */ - public int value() { - return value; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - alleycat.writeObject(value, buffer); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - value = alleycat.readObject(buffer); - } - - /** - * Base key command builder. - */ - public static abstract class Builder, U extends ValueCommand, V> extends SetCommand.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - - /** - * Sets the command value. - * - * @param value The command value - * @return The command builder. - */ - @SuppressWarnings("unchecked") - public T withValue(int value) { - command.value = value; - return (T) this; - } - } - } - - /** - * Abstract value query. - */ - public static abstract class ValueQuery extends SetQuery { - protected int value; - - /** - * Returns the value. - */ - public int value() { - return value; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - super.writeObject(buffer, alleycat); - alleycat.writeObject(value, buffer); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - super.readObject(buffer, alleycat); - value = alleycat.readObject(buffer); - } - - /** - * Base value query builder. - */ - public static abstract class Builder, U extends ValueQuery, V> extends SetQuery.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - - /** - * Sets the query value. - * - * @param value The query value - * @return The query builder. - */ - @SuppressWarnings("unchecked") - public T withValue(int value) { - query.value = value; - return (T) this; - } - } - } - - /** - * Contains value command. - */ - @SerializeWith(id=450) - public static class Contains extends ValueQuery { - - /** - * Returns a builder for this command. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Contains key builder. - */ - public static class Builder extends ValueQuery.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Contains create() { - return new Contains(); - } - } - } - - /** - * TTL command. - */ - public static abstract class TtlCommand extends ValueCommand { - protected long ttl; - protected Mode mode = Mode.PERSISTENT; - - /** - * Returns the time to live in milliseconds. - * - * @return The time to live in milliseconds. - */ - public long ttl() { - return ttl; - } - - /** - * Returns the persistence mode. - * - * @return The persistence mode. - */ - public Mode mode() { - return mode; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - super.writeObject(buffer, alleycat); - buffer.writeByte(mode.ordinal()).writeLong(ttl); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - super.readObject(buffer, alleycat); - mode = Mode.values()[buffer.readByte()]; - ttl = buffer.readLong(); - } - - /** - * TTL command builder. - */ - public static abstract class Builder, U extends TtlCommand, V> extends ValueCommand.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - - /** - * Sets the time to live. - * - * @param ttl The time to live in milliseconds.. - * @return The command builder. - */ - public Builder withTtl(long ttl) { - command.ttl = ttl; - return this; - } - - /** - * Sets the time to live. - * - * @param ttl The time to live. - * @param unit The time to live unit. - * @return The command builder. - */ - public Builder withTtl(long ttl, TimeUnit unit) { - command.ttl = unit.toMillis(ttl); - return this; - } - - /** - * Sets the persistence mode. - * - * @param mode The persistence mode. - * @return The command builder. - */ - public Builder withMode(Mode mode) { - command.mode = mode; - return this; - } - } - } - - /** - * Add command. - */ - @SerializeWith(id=451) - public static class Add extends TtlCommand { - - /** - * Returns a builder for this command. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Add command builder. - */ - public static class Builder extends TtlCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Add create() { - return new Add(); - } - } - } - - /** - * Remove command. - */ - @SerializeWith(id=452) - public static class Remove extends ValueCommand { - - /** - * Returns a builder for this command. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Remove command builder. - */ - public static class Builder extends ValueCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Remove create() { - return new Remove(); - } - } - } - - /** - * Size query. - */ - @SerializeWith(id=453) - public static class Size extends SetQuery { - - /** - * Returns a builder for this query. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Size query builder. - */ - public static class Builder extends SetQuery.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Size create() { - return new Size(); - } - } - } - - /** - * Is empty query. - */ - @SerializeWith(id=454) - public static class IsEmpty extends SetQuery { - - /** - * Returns a builder for this query. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Is empty query builder. - */ - public static class Builder extends SetQuery.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected IsEmpty create() { - return new IsEmpty(); - } - } - } - - /** - * Clear command. - */ - @SerializeWith(id=455) - public static class Clear extends SetCommand { - - /** - * Returns a builder for this command. - */ - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - - } - - /** - * Get command builder. - */ - public static class Builder extends SetCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Clear create() { - return new Clear(); - } - } - } - - /** - * Map state machine. - */ - public static class StateMachine extends net.kuujo.copycat.raft.server.StateMachine { - private final Map> map = new HashMap<>(); - private final Set sessions = new HashSet<>(); - private long time; - - /** - * Updates the wall clock time. - */ - private void updateTime(Commit commit) { - time = Math.max(time, commit.timestamp()); - } - - @Override - public void register(Session session) { - sessions.add(session.id()); - } - - @Override - public void expire(Session session) { - sessions.remove(session.id()); - } - - @Override - public void close(Session session) { - sessions.remove(session.id()); - } - - /** - * Returns a boolean value indicating whether the given commit is active. - */ - private boolean isActive(Commit commit) { - if (commit == null) { - return false; - } else if (commit.operation().mode() == Mode.EPHEMERAL && !sessions.contains(commit.session().id())) { - return false; - } else if (commit.operation().ttl() != 0 && commit.operation().ttl() < time - commit.timestamp()) { - return false; - } - return true; - } - - /** - * Handles a contains commit. - */ - @Apply(Contains.class) - protected boolean contains(Commit commit) { - updateTime(commit); - Commit command = map.get(commit.operation().value()); - if (!isActive(command)) { - map.remove(commit.operation().value()); - return false; - } - return true; - } - - /** - * Handles an add commit. - */ - @Apply(Add.class) - protected boolean put(Commit commit) { - updateTime(commit); - Commit command = map.get(commit.operation().value()); - if (!isActive(command)) { - map.put(commit.operation().value(), commit); - return true; - } - return false; - } - - /** - * Filters an add commit. - */ - @Filter({Add.class}) - protected boolean filterPut(Commit commit) { - Commit command = map.get(commit.operation().value()); - return command != null && command.index() == commit.index() && isActive(command); - } - - /** - * Handles a remove commit. - */ - @Apply(Remove.class) - protected boolean remove(Commit commit) { - updateTime(commit); - Commit command = map.remove(commit.operation().value()); - return isActive(command); - } - - /** - * Filters a remove commit. - */ - @Filter(value={Remove.class, Clear.class}, compaction=Compaction.Type.MAJOR) - protected boolean filterRemove(Commit commit, Compaction compaction) { - return commit.index() > compaction.index(); - } - - /** - * Handles a size commit. - */ - @Apply(Size.class) - protected int size(Commit commit) { - updateTime(commit); - return map.size(); - } - - /** - * Handles an is empty commit. - */ - @Apply(IsEmpty.class) - protected boolean isEmpty(Commit commit) { - updateTime(commit); - return map.isEmpty(); - } - - /** - * Handles a clear commit. - */ - @Apply(Clear.class) - protected void clear(Commit commit) { - updateTime(commit); - map.clear(); - } + return submit(SetCommands.Clear.builder().build()); } } diff --git a/collections/src/main/java/net/kuujo/copycat/collections/DistributedTopic.java b/collections/src/main/java/net/kuujo/copycat/collections/DistributedTopic.java index 4f1843a01b..bb6f4373d6 100644 --- a/collections/src/main/java/net/kuujo/copycat/collections/DistributedTopic.java +++ b/collections/src/main/java/net/kuujo/copycat/collections/DistributedTopic.java @@ -15,22 +15,15 @@ */ package net.kuujo.copycat.collections; -import net.kuujo.alleycat.Alleycat; -import net.kuujo.alleycat.AlleycatSerializable; -import net.kuujo.alleycat.io.BufferInput; -import net.kuujo.alleycat.io.BufferOutput; -import net.kuujo.copycat.*; -import net.kuujo.copycat.raft.Command; -import net.kuujo.copycat.raft.Operation; +import net.kuujo.copycat.Listener; +import net.kuujo.copycat.ListenerContext; +import net.kuujo.copycat.Resource; +import net.kuujo.copycat.Stateful; +import net.kuujo.copycat.collections.state.TopicCommands; +import net.kuujo.copycat.collections.state.TopicState; import net.kuujo.copycat.raft.Raft; -import net.kuujo.copycat.raft.Session; -import net.kuujo.copycat.raft.server.Apply; -import net.kuujo.copycat.raft.server.Commit; -import net.kuujo.copycat.raft.server.Filter; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; @@ -39,8 +32,8 @@ * * @author Jordan Halterman */ -@Stateful(DistributedTopic.StateMachine.class) -public class DistributedTopic extends Resource { +@Stateful(TopicState.class) +public class DistributedTopic extends Resource implements AsyncTopic { private final List> listeners = new CopyOnWriteArrayList<>(); @SuppressWarnings("unchecked") @@ -53,24 +46,14 @@ public DistributedTopic(Raft protocol) { }); } - /** - * Publishes a message to the topic. - * - * @param message The message to publish. - * @return A completable future to be completed once the message has been published. - */ + @Override public CompletableFuture publish(T message) { - return submit(PublishCommand.builder() + return submit(TopicCommands.Publish.builder() .withMessage(message) .build()); } - /** - * Sets a message listener on the topic. - * - * @param listener The message listener. - * @return The listener context. - */ + @Override public ListenerContext onMessage(Listener listener) { TopicListenerContext context = new TopicListenerContext(listener); listeners.add(context); @@ -98,163 +81,4 @@ public void close() { } } - /** - * Abstract topic command. - */ - public static abstract class TopicCommand implements Command, AlleycatSerializable { - - /** - * Base map command builder. - */ - public static abstract class Builder, U extends TopicCommand, V> extends Command.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - } - } - - /** - * Publish command. - */ - public static class PublishCommand extends TopicCommand { - - /** - * Returns a new publish command builder. - * - * @param The message type. - * @return The publish command builder. - */ - @SuppressWarnings("unchecked") - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - private T message; - - /** - * Returns the publish message. - * - * @return The publish message. - */ - public T message() { - return message; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat serializer) { - serializer.writeObject(message, buffer); - } - - @Override - public void readObject(BufferInput buffer, Alleycat serializer) { - message = serializer.readObject(buffer); - } - - /** - * Publish command builder. - */ - public static class Builder extends TopicCommand.Builder, PublishCommand, Void> { - - public Builder(BuilderPool, PublishCommand> pool) { - super(pool); - } - - /** - * Sets the publish command message. - * - * @param message The message. - * @return The publish command builder. - */ - public Builder withMessage(T message) { - command.message = message; - return this; - } - - @Override - protected PublishCommand create() { - return new PublishCommand<>(); - } - } - } - - /** - * Subscribe command. - */ - public static class SubscribeCommand extends TopicCommand { - - /** - * Returns a new publish command builder. - * - * @return The publish command builder. - */ - @SuppressWarnings("unchecked") - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - - } - - /** - * Publish command builder. - */ - public static class Builder extends TopicCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected SubscribeCommand create() { - return new SubscribeCommand(); - } - } - } - - /** - * Topic state machine. - */ - public static class StateMachine extends net.kuujo.copycat.raft.server.StateMachine { - private final Set sessions = new HashSet<>(); - - @Override - public void register(Session session) { - sessions.add(session); - } - - @Override - public void expire(Session session) { - sessions.remove(session); - } - - @Override - public void close(Session session) { - sessions.remove(session); - } - - /** - * Handles a publish commit. - */ - @Apply(PublishCommand.class) - protected void applyPublish(Commit commit) { - for (Session session : sessions) { - session.publish(commit.operation().message()); - } - } - - /** - * Filters a publish commit. - */ - @Filter(PublishCommand.class) - protected boolean filterPublish(Commit commit) { - return false; - } - } - } diff --git a/collections/src/main/java/net/kuujo/copycat/collections/state/MapCommands.java b/collections/src/main/java/net/kuujo/copycat/collections/state/MapCommands.java new file mode 100644 index 0000000000..a2efe34092 --- /dev/null +++ b/collections/src/main/java/net/kuujo/copycat/collections/state/MapCommands.java @@ -0,0 +1,619 @@ +/* + * 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 net.kuujo.copycat.collections.state; + +import net.kuujo.alleycat.Alleycat; +import net.kuujo.alleycat.AlleycatSerializable; +import net.kuujo.alleycat.SerializeWith; +import net.kuujo.alleycat.io.BufferInput; +import net.kuujo.alleycat.io.BufferOutput; +import net.kuujo.copycat.BuilderPool; +import net.kuujo.copycat.Mode; +import net.kuujo.copycat.raft.Command; +import net.kuujo.copycat.raft.ConsistencyLevel; +import net.kuujo.copycat.raft.Operation; +import net.kuujo.copycat.raft.Query; + +import java.util.concurrent.TimeUnit; + +/** + * Map commands. + * + * @author Jordan Halterman + */ +public class MapCommands { + + private MapCommands() { + } + + /** + * Abstract map command. + */ + public static abstract class MapCommand implements Command, AlleycatSerializable { + + /** + * Base map command builder. + */ + public static abstract class Builder, U extends MapCommand, V> extends Command.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + } + } + + /** + * Abstract map query. + */ + public static abstract class MapQuery implements Query, AlleycatSerializable { + protected ConsistencyLevel consistency = ConsistencyLevel.LINEARIZABLE_LEASE; + + @Override + public ConsistencyLevel consistency() { + return consistency; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + buffer.writeByte(consistency.ordinal()); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + consistency = ConsistencyLevel.values()[buffer.readByte()]; + } + + /** + * Base map query builder. + */ + public static abstract class Builder, U extends MapQuery, V> extends Query.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + + /** + * Sets the query consistency level. + * + * @param consistency The query consistency level. + * @return The query builder. + */ + @SuppressWarnings("unchecked") + public T withConsistency(ConsistencyLevel consistency) { + if (consistency == null) + throw new NullPointerException("consistency cannot be null"); + query.consistency = consistency; + return (T) this; + } + } + } + + /** + * Abstract key-based command. + */ + public static abstract class KeyCommand extends MapCommand { + protected Object key; + + /** + * Returns the key. + */ + public Object key() { + return key; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + alleycat.writeObject(key, buffer); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + key = alleycat.readObject(buffer); + } + + /** + * Base key command builder. + */ + public static abstract class Builder, U extends KeyCommand, V> extends MapCommand.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + + /** + * Sets the command key. + * + * @param key The command key + * @return The command builder. + */ + @SuppressWarnings("unchecked") + public T withKey(Object key) { + command.key = key; + return (T) this; + } + } + } + + /** + * Abstract key-based query. + */ + public static abstract class KeyQuery extends MapQuery { + protected Object key; + + /** + * Returns the key. + */ + public Object key() { + return key; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + super.writeObject(buffer, alleycat); + alleycat.writeObject(key, buffer); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + super.readObject(buffer, alleycat); + key = alleycat.readObject(buffer); + } + + /** + * Base key query builder. + */ + public static abstract class Builder, U extends KeyQuery, V> extends MapQuery.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + + /** + * Sets the query key. + * + * @param key The query key + * @return The query builder. + */ + @SuppressWarnings("unchecked") + public T withKey(Object key) { + query.key = key; + return (T) this; + } + } + } + + /** + * Contains key command. + */ + @SerializeWith(id=440) + public static class ContainsKey extends KeyQuery { + + /** + * Returns a builder for this command. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Contains key builder. + */ + public static class Builder extends KeyQuery.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected ContainsKey create() { + return new ContainsKey(); + } + } + } + + /** + * Key/value command. + */ + public static abstract class KeyValueCommand extends KeyCommand { + protected Object value; + + /** + * Returns the command value. + */ + public Object value() { + return value; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + super.writeObject(buffer, alleycat); + alleycat.writeObject(value, buffer); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + super.readObject(buffer, alleycat); + value = alleycat.readObject(buffer); + } + + /** + * Key/value command builder. + */ + public static abstract class Builder, U extends KeyValueCommand, V> extends KeyCommand.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + + /** + * Sets the command value. + * + * @param value The command value. + * @return The command builder. + */ + @SuppressWarnings("unchecked") + public T withValue(Object value) { + command.value = value; + return (T) this; + } + } + } + + /** + * TTL command. + */ + public static abstract class TtlCommand extends KeyValueCommand { + protected Mode mode = Mode.PERSISTENT; + protected long ttl; + + /** + * Returns the persistence mode. + * + * @return The persistence mode. + */ + public Mode mode() { + return mode; + } + + /** + * Returns the time to live in milliseconds. + * + * @return The time to live in milliseconds. + */ + public long ttl() { + return ttl; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + super.writeObject(buffer, alleycat); + buffer.writeByte(mode.ordinal()).writeLong(ttl); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + super.readObject(buffer, alleycat); + mode = Mode.values()[buffer.readByte()]; + ttl = buffer.readLong(); + } + + /** + * TTL command builder. + */ + public static abstract class Builder, U extends TtlCommand, V> extends KeyValueCommand.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + + /** + * Sets the persistence mode. + * + * @param mode The persistence mode. + * @return The command builder. + */ + public Builder withMode(Mode mode) { + command.mode = mode; + return this; + } + + /** + * Sets the time to live. + * + * @param ttl The time to live in milliseconds.. + * @return The command builder. + */ + public Builder withTtl(long ttl) { + command.ttl = ttl; + return this; + } + + /** + * Sets the time to live. + * + * @param ttl The time to live. + * @param unit The time to live unit. + * @return The command builder. + */ + public Builder withTtl(long ttl, TimeUnit unit) { + command.ttl = unit.toMillis(ttl); + return this; + } + } + } + + /** + * Put command. + */ + @SerializeWith(id=441) + public static class Put extends TtlCommand { + + /** + * Returns a builder for this command. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Put command builder. + */ + public static class Builder extends TtlCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Put create() { + return new Put(); + } + } + } + + /** + * Put if absent command. + */ + @SerializeWith(id=442) + public static class PutIfAbsent extends TtlCommand { + + /** + * Returns a builder for this command. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Put command builder. + */ + public static class Builder extends TtlCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected PutIfAbsent create() { + return new PutIfAbsent(); + } + } + } + + /** + * Get query. + */ + @SerializeWith(id=443) + public static class Get extends KeyQuery { + + /** + * Returns a builder for this query. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Get query builder. + */ + public static class Builder extends KeyQuery.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Get create() { + return new Get(); + } + } + } + + /** + * Get or default query. + */ + @SerializeWith(id=444) + public static class GetOrDefault extends KeyQuery { + + /** + * Returns a builder for this query. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + private Object defaultValue; + + /** + * Returns the default value. + * + * @return The default value. + */ + public Object defaultValue() { + return defaultValue; + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + super.readObject(buffer, alleycat); + defaultValue = alleycat.readObject(buffer); + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + super.writeObject(buffer, alleycat); + alleycat.writeObject(defaultValue, buffer); + } + + /** + * Get command builder. + */ + public static class Builder extends KeyQuery.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected GetOrDefault create() { + return new GetOrDefault(); + } + + /** + * Sets the default value. + * + * @param defaultValue The default value. + * @return The query builder. + */ + public Builder withDefaultValue(Object defaultValue) { + query.defaultValue = defaultValue; + return this; + } + } + } + + /** + * Remove command. + */ + @SerializeWith(id=445) + public static class Remove extends KeyValueCommand { + + /** + * Returns a builder for this command. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Get command builder. + */ + public static class Builder extends KeyValueCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Remove create() { + return new Remove(); + } + } + } + + /** + * Is empty query. + */ + @SerializeWith(id=446) + public static class IsEmpty extends MapQuery { + + /** + * Returns a builder for this command. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Is empty command builder. + */ + public static class Builder extends MapQuery.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected IsEmpty create() { + return new IsEmpty(); + } + } + } + + /** + * Size query. + */ + @SerializeWith(id=447) + public static class Size extends MapQuery { + + /** + * Returns a builder for this command. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Is empty command builder. + */ + public static class Builder extends MapQuery.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Size create() { + return new Size(); + } + } + } + + /** + * Clear command. + */ + @SerializeWith(id=448) + public static class Clear extends MapCommand { + + /** + * Returns a builder for this command. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + + } + + /** + * Get command builder. + */ + public static class Builder extends MapCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Clear create() { + return new Clear(); + } + } + } + +} diff --git a/collections/src/main/java/net/kuujo/copycat/collections/state/MapState.java b/collections/src/main/java/net/kuujo/copycat/collections/state/MapState.java new file mode 100644 index 0000000000..870c912313 --- /dev/null +++ b/collections/src/main/java/net/kuujo/copycat/collections/state/MapState.java @@ -0,0 +1,214 @@ +/* + * 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 net.kuujo.copycat.collections.state; + +import net.kuujo.copycat.Mode; +import net.kuujo.copycat.log.Compaction; +import net.kuujo.copycat.raft.Session; +import net.kuujo.copycat.raft.server.Apply; +import net.kuujo.copycat.raft.server.Commit; +import net.kuujo.copycat.raft.server.Filter; +import net.kuujo.copycat.raft.server.StateMachine; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Map state machine. + * + * @author Jordan Halterman + */ +public class MapState extends StateMachine { + private final Map> map = new HashMap<>(); + private final Set sessions = new HashSet<>(); + private long time; + + /** + * Updates the wall clock time. + */ + private void updateTime(Commit commit) { + time = Math.max(time, commit.timestamp()); + } + + @Override + public void register(Session session) { + sessions.add(session.id()); + } + + @Override + public void expire(Session session) { + sessions.remove(session.id()); + } + + @Override + public void close(Session session) { + sessions.remove(session.id()); + } + + /** + * Returns a boolean value indicating whether the given commit is active. + */ + private boolean isActive(Commit commit) { + if (commit == null) { + return false; + } else if (commit.operation().mode() == Mode.EPHEMERAL && !sessions.contains(commit.session().id())) { + return false; + } else if (commit.operation().ttl() != 0 && commit.operation().ttl() < time - commit.timestamp()) { + return false; + } + return true; + } + + /** + * Handles a contains key commit. + */ + @Apply(MapCommands.ContainsKey.class) + protected boolean containsKey(Commit commit) { + updateTime(commit); + Commit command = map.get(commit.operation().key()); + if (!isActive(command)) { + map.remove(commit.operation().key()); + return false; + } + return true; + } + + /** + * Handles a get commit. + */ + @Apply(MapCommands.Get.class) + protected Object get(Commit commit) { + updateTime(commit); + Commit command = map.get(commit.operation().key()); + if (command != null) { + if (!isActive(command)) { + map.remove(commit.operation().key()); + } else { + return command.operation().value(); + } + } + return null; + } + + /** + * Handles a get or default commit. + */ + @Apply(MapCommands.GetOrDefault.class) + protected Object getOrDefault(Commit commit) { + updateTime(commit); + Commit command = map.get(commit.operation().key()); + if (command == null) { + return commit.operation().defaultValue(); + } else if (!isActive(command)) { + map.remove(commit.operation().key()); + } else { + return command.operation().value(); + } + return commit.operation().defaultValue(); + } + + /** + * Handles a put commit. + */ + @Apply(MapCommands.Put.class) + protected Object put(Commit commit) { + updateTime(commit); + Commit command = map.put(commit.operation().key(), commit); + return isActive(command) ? command.operation().value : null; + } + + /** + * Handles a put if absent commit. + */ + @Apply(MapCommands.PutIfAbsent.class) + protected Object putIfAbsent(Commit commit) { + updateTime(commit); + Commit command = map.putIfAbsent(commit.operation().key(), commit); + return isActive(command) ? command.operation().value : null; + } + + /** + * Filters a put and put if absent commit. + */ + @Filter({MapCommands.Put.class, MapCommands.PutIfAbsent.class}) + protected boolean filterPut(Commit commit) { + Commit command = map.get(commit.operation().key()); + return isActive(command) && command.index() == commit.index(); + } + + /** + * Handles a remove commit. + */ + @Apply(MapCommands.Remove.class) + protected Object remove(Commit commit) { + updateTime(commit); + if (commit.operation().value() != null) { + Commit command = map.get(commit.operation().key()); + if (!isActive(command)) { + map.remove(commit.operation().key()); + } else { + Object value = command.operation().value(); + if ((value == null && commit.operation().value() == null) || (value != null && commit.operation().value() != null && value.equals(commit.operation().value()))) { + map.remove(commit.operation().key()); + return true; + } + return false; + } + return false; + } else { + Commit command = map.remove(commit.operation().key()); + return isActive(command) ? command.operation().value() : null; + } + } + + /** + * Filters a remove commit. + */ + @Filter(value={MapCommands.Remove.class, MapCommands.Clear.class}, compaction= Compaction.Type.MAJOR) + protected boolean filterRemove(Commit commit, Compaction compaction) { + return commit.index() > compaction.index(); + } + + /** + * Handles a size commit. + */ + @Apply(MapCommands.Size.class) + protected int size(Commit commit) { + updateTime(commit); + return map.size(); + } + + /** + * Handles an is empty commit. + */ + @Apply(MapCommands.IsEmpty.class) + protected boolean isEmpty(Commit commit) { + updateTime(commit); + return map.isEmpty(); + } + + /** + * Handles a clear commit. + */ + @Apply(MapCommands.Clear.class) + protected void clear(Commit commit) { + updateTime(commit); + map.clear(); + } + +} diff --git a/collections/src/main/java/net/kuujo/copycat/collections/state/SetCommands.java b/collections/src/main/java/net/kuujo/copycat/collections/state/SetCommands.java new file mode 100644 index 0000000000..2f1135169b --- /dev/null +++ b/collections/src/main/java/net/kuujo/copycat/collections/state/SetCommands.java @@ -0,0 +1,452 @@ +/* + * 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 net.kuujo.copycat.collections.state; + +import net.kuujo.alleycat.Alleycat; +import net.kuujo.alleycat.AlleycatSerializable; +import net.kuujo.alleycat.SerializeWith; +import net.kuujo.alleycat.io.BufferInput; +import net.kuujo.alleycat.io.BufferOutput; +import net.kuujo.copycat.BuilderPool; +import net.kuujo.copycat.Mode; +import net.kuujo.copycat.raft.Command; +import net.kuujo.copycat.raft.ConsistencyLevel; +import net.kuujo.copycat.raft.Operation; +import net.kuujo.copycat.raft.Query; + +import java.util.concurrent.TimeUnit; + +/** + * Distributed set commands. + * + * @author Jordan Halterman + */ +public class SetCommands { + + private SetCommands() { + } + + /** + * Abstract set command. + */ + private static abstract class SetCommand implements Command, AlleycatSerializable { + + /** + * Base set command builder. + */ + public static abstract class Builder, U extends SetCommand, V> extends Command.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + } + } + + /** + * Abstract set query. + */ + private static abstract class SetQuery implements Query, AlleycatSerializable { + protected ConsistencyLevel consistency = ConsistencyLevel.LINEARIZABLE_LEASE; + + @Override + public ConsistencyLevel consistency() { + return consistency; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + buffer.writeByte(consistency.ordinal()); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + consistency = ConsistencyLevel.values()[buffer.readByte()]; + } + + /** + * Base set query builder. + */ + public static abstract class Builder, U extends SetQuery, V> extends Query.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + + /** + * Sets the query consistency level. + * + * @param consistency The query consistency level. + * @return The query builder. + */ + @SuppressWarnings("unchecked") + public T withConsistency(ConsistencyLevel consistency) { + query.consistency = consistency; + return (T) this; + } + } + } + + /** + * Abstract value command. + */ + private static abstract class ValueCommand extends SetCommand { + protected int value; + + /** + * Returns the value. + */ + public int value() { + return value; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + alleycat.writeObject(value, buffer); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + value = alleycat.readObject(buffer); + } + + /** + * Base key command builder. + */ + public static abstract class Builder, U extends ValueCommand, V> extends SetCommand.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + + /** + * Sets the command value. + * + * @param value The command value + * @return The command builder. + */ + @SuppressWarnings("unchecked") + public T withValue(int value) { + command.value = value; + return (T) this; + } + } + } + + /** + * Abstract value query. + */ + private static abstract class ValueQuery extends SetQuery { + protected int value; + + /** + * Returns the value. + */ + public int value() { + return value; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + super.writeObject(buffer, alleycat); + alleycat.writeObject(value, buffer); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + super.readObject(buffer, alleycat); + value = alleycat.readObject(buffer); + } + + /** + * Base value query builder. + */ + public static abstract class Builder, U extends ValueQuery, V> extends SetQuery.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + + /** + * Sets the query value. + * + * @param value The query value + * @return The query builder. + */ + @SuppressWarnings("unchecked") + public T withValue(int value) { + query.value = value; + return (T) this; + } + } + } + + /** + * Contains value command. + */ + @SerializeWith(id=450) + public static class Contains extends ValueQuery { + + /** + * Returns a builder for this command. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Contains key builder. + */ + public static class Builder extends ValueQuery.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Contains create() { + return new Contains(); + } + } + } + + /** + * TTL command. + */ + public static abstract class TtlCommand extends ValueCommand { + protected long ttl; + protected Mode mode = Mode.PERSISTENT; + + /** + * Returns the time to live in milliseconds. + * + * @return The time to live in milliseconds. + */ + public long ttl() { + return ttl; + } + + /** + * Returns the persistence mode. + * + * @return The persistence mode. + */ + public Mode mode() { + return mode; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + super.writeObject(buffer, alleycat); + buffer.writeByte(mode.ordinal()).writeLong(ttl); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + super.readObject(buffer, alleycat); + mode = Mode.values()[buffer.readByte()]; + ttl = buffer.readLong(); + } + + /** + * TTL command builder. + */ + public static abstract class Builder, U extends TtlCommand, V> extends ValueCommand.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + + /** + * Sets the time to live. + * + * @param ttl The time to live in milliseconds.. + * @return The command builder. + */ + public Builder withTtl(long ttl) { + command.ttl = ttl; + return this; + } + + /** + * Sets the time to live. + * + * @param ttl The time to live. + * @param unit The time to live unit. + * @return The command builder. + */ + public Builder withTtl(long ttl, TimeUnit unit) { + command.ttl = unit.toMillis(ttl); + return this; + } + + /** + * Sets the persistence mode. + * + * @param mode The persistence mode. + * @return The command builder. + */ + public Builder withMode(Mode mode) { + command.mode = mode; + return this; + } + } + } + + /** + * Add command. + */ + @SerializeWith(id=451) + public static class Add extends TtlCommand { + + /** + * Returns a builder for this command. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Add command builder. + */ + public static class Builder extends TtlCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Add create() { + return new Add(); + } + } + } + + /** + * Remove command. + */ + @SerializeWith(id=452) + public static class Remove extends ValueCommand { + + /** + * Returns a builder for this command. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Remove command builder. + */ + public static class Builder extends ValueCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Remove create() { + return new Remove(); + } + } + } + + /** + * Size query. + */ + @SerializeWith(id=453) + public static class Size extends SetQuery { + + /** + * Returns a builder for this query. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Size query builder. + */ + public static class Builder extends SetQuery.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Size create() { + return new Size(); + } + } + } + + /** + * Is empty query. + */ + @SerializeWith(id=454) + public static class IsEmpty extends SetQuery { + + /** + * Returns a builder for this query. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Is empty query builder. + */ + public static class Builder extends SetQuery.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected IsEmpty create() { + return new IsEmpty(); + } + } + } + + /** + * Clear command. + */ + @SerializeWith(id=455) + public static class Clear extends SetCommand { + + /** + * Returns a builder for this command. + */ + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + + } + + /** + * Get command builder. + */ + public static class Builder extends SetCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Clear create() { + return new Clear(); + } + } + } + +} diff --git a/collections/src/main/java/net/kuujo/copycat/collections/state/SetState.java b/collections/src/main/java/net/kuujo/copycat/collections/state/SetState.java new file mode 100644 index 0000000000..e6b35c1a28 --- /dev/null +++ b/collections/src/main/java/net/kuujo/copycat/collections/state/SetState.java @@ -0,0 +1,159 @@ +/* + * 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 net.kuujo.copycat.collections.state; + +import net.kuujo.copycat.Mode; +import net.kuujo.copycat.log.Compaction; +import net.kuujo.copycat.raft.Session; +import net.kuujo.copycat.raft.server.Apply; +import net.kuujo.copycat.raft.server.Commit; +import net.kuujo.copycat.raft.server.Filter; +import net.kuujo.copycat.raft.server.StateMachine; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Distributed set state machine. + * + * @author Jordan Halterman + */ +public class SetState extends StateMachine { + private final Map> map = new HashMap<>(); + private final Set sessions = new HashSet<>(); + private long time; + + /** + * Updates the wall clock time. + */ + private void updateTime(Commit commit) { + time = Math.max(time, commit.timestamp()); + } + + @Override + public void register(Session session) { + sessions.add(session.id()); + } + + @Override + public void expire(Session session) { + sessions.remove(session.id()); + } + + @Override + public void close(Session session) { + sessions.remove(session.id()); + } + + /** + * Returns a boolean value indicating whether the given commit is active. + */ + private boolean isActive(Commit commit) { + if (commit == null) { + return false; + } else if (commit.operation().mode() == Mode.EPHEMERAL && !sessions.contains(commit.session().id())) { + return false; + } else if (commit.operation().ttl() != 0 && commit.operation().ttl() < time - commit.timestamp()) { + return false; + } + return true; + } + + /** + * Handles a contains commit. + */ + @Apply(SetCommands.Contains.class) + protected boolean contains(Commit commit) { + updateTime(commit); + Commit command = map.get(commit.operation().value()); + if (!isActive(command)) { + map.remove(commit.operation().value()); + return false; + } + return true; + } + + /** + * Handles an add commit. + */ + @Apply(SetCommands.Add.class) + protected boolean put(Commit commit) { + updateTime(commit); + Commit command = map.get(commit.operation().value()); + if (!isActive(command)) { + map.put(commit.operation().value(), commit); + return true; + } + return false; + } + + /** + * Filters an add commit. + */ + @Filter({SetCommands.Add.class}) + protected boolean filterPut(Commit commit) { + Commit command = map.get(commit.operation().value()); + return command != null && command.index() == commit.index() && isActive(command); + } + + /** + * Handles a remove commit. + */ + @Apply(SetCommands.Remove.class) + protected boolean remove(Commit commit) { + updateTime(commit); + Commit command = map.remove(commit.operation().value()); + return isActive(command); + } + + /** + * Filters a remove commit. + */ + @Filter(value={SetCommands.Remove.class, SetCommands.Clear.class}, compaction=Compaction.Type.MAJOR) + protected boolean filterRemove(Commit commit, Compaction compaction) { + return commit.index() > compaction.index(); + } + + /** + * Handles a size commit. + */ + @Apply(SetCommands.Size.class) + protected int size(Commit commit) { + updateTime(commit); + return map.size(); + } + + /** + * Handles an is empty commit. + */ + @Apply(SetCommands.IsEmpty.class) + protected boolean isEmpty(Commit commit) { + updateTime(commit); + return map.isEmpty(); + } + + /** + * Handles a clear commit. + */ + @Apply(SetCommands.Clear.class) + protected void clear(Commit commit) { + updateTime(commit); + map.clear(); + } + +} diff --git a/collections/src/main/java/net/kuujo/copycat/collections/state/TopicCommands.java b/collections/src/main/java/net/kuujo/copycat/collections/state/TopicCommands.java new file mode 100644 index 0000000000..ef1332e361 --- /dev/null +++ b/collections/src/main/java/net/kuujo/copycat/collections/state/TopicCommands.java @@ -0,0 +1,155 @@ +/* + * 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 net.kuujo.copycat.collections.state; + +import net.kuujo.alleycat.Alleycat; +import net.kuujo.alleycat.AlleycatSerializable; +import net.kuujo.alleycat.io.BufferInput; +import net.kuujo.alleycat.io.BufferOutput; +import net.kuujo.copycat.BuilderPool; +import net.kuujo.copycat.raft.Command; +import net.kuujo.copycat.raft.Operation; + +/** + * Topic commands. + * + * @author Jordan Halterman + */ +public class TopicCommands { + + private TopicCommands() { + } + + /** + * Abstract topic command. + */ + public static abstract class TopicCommand implements Command, AlleycatSerializable { + + /** + * Base map command builder. + */ + public static abstract class Builder, U extends TopicCommand, V> extends Command.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + } + } + + /** + * Publish command. + */ + public static class Publish extends TopicCommand { + + /** + * Returns a new publish command builder. + * + * @param The message type. + * @return The publish command builder. + */ + @SuppressWarnings("unchecked") + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + private T message; + + /** + * Returns the publish message. + * + * @return The publish message. + */ + public T message() { + return message; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat serializer) { + serializer.writeObject(message, buffer); + } + + @Override + public void readObject(BufferInput buffer, Alleycat serializer) { + message = serializer.readObject(buffer); + } + + /** + * Publish command builder. + */ + public static class Builder extends TopicCommand.Builder, Publish, Void> { + + public Builder(BuilderPool, Publish> pool) { + super(pool); + } + + /** + * Sets the publish command message. + * + * @param message The message. + * @return The publish command builder. + */ + public Builder withMessage(T message) { + command.message = message; + return this; + } + + @Override + protected Publish create() { + return new Publish<>(); + } + } + } + + /** + * Subscribe command. + */ + public static class Subscribe extends TopicCommand { + + /** + * Returns a new publish command builder. + * + * @return The publish command builder. + */ + @SuppressWarnings("unchecked") + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + + } + + /** + * Publish command builder. + */ + public static class Builder extends TopicCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Subscribe create() { + return new Subscribe(); + } + } + } + +} diff --git a/collections/src/main/java/net/kuujo/copycat/collections/state/TopicState.java b/collections/src/main/java/net/kuujo/copycat/collections/state/TopicState.java new file mode 100644 index 0000000000..3023482795 --- /dev/null +++ b/collections/src/main/java/net/kuujo/copycat/collections/state/TopicState.java @@ -0,0 +1,68 @@ +/* + * 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 net.kuujo.copycat.collections.state; + +import net.kuujo.copycat.raft.Session; +import net.kuujo.copycat.raft.server.Apply; +import net.kuujo.copycat.raft.server.Commit; +import net.kuujo.copycat.raft.server.Filter; +import net.kuujo.copycat.raft.server.StateMachine; + +import java.util.HashSet; +import java.util.Set; + +/** + * Topic state machine. + * + * @author Jordan Halterman + */ +public class TopicState extends StateMachine { + private final Set sessions = new HashSet<>(); + + @Override + public void register(Session session) { + sessions.add(session); + } + + @Override + public void expire(Session session) { + sessions.remove(session); + } + + @Override + public void close(Session session) { + sessions.remove(session); + } + + /** + * Handles a publish commit. + */ + @Apply(TopicCommands.Publish.class) + protected void applyPublish(Commit commit) { + for (Session session : sessions) { + session.publish(commit.operation().message()); + } + } + + /** + * Filters a publish commit. + */ + @Filter(TopicCommands.Publish.class) + protected boolean filterPublish(Commit commit) { + return false; + } + +} diff --git a/collections/src/main/resources/META-INF/services/net.kuujo.alleycat.AlleycatSerializable b/collections/src/main/resources/META-INF/services/net.kuujo.alleycat.AlleycatSerializable index 794e226c3c..9beb062fd0 100644 --- a/collections/src/main/resources/META-INF/services/net.kuujo.alleycat.AlleycatSerializable +++ b/collections/src/main/resources/META-INF/services/net.kuujo.alleycat.AlleycatSerializable @@ -13,19 +13,22 @@ # See the License for the specific language governing permissions and # limitations under the License. # -net.kuujo.copycat.collections.DistributedMap$Clear -net.kuujo.copycat.collections.DistributedMap$ContainsKey -net.kuujo.copycat.collections.DistributedMap$Get -net.kuujo.copycat.collections.DistributedMap$GetOrDefault -net.kuujo.copycat.collections.DistributedMap$IsEmpty -net.kuujo.copycat.collections.DistributedMap$Put -net.kuujo.copycat.collections.DistributedMap$PutIfAbsent -net.kuujo.copycat.collections.DistributedMap$Remove -net.kuujo.copycat.collections.DistributedMap$Size +net.kuujo.copycat.collections.state.MapCommands$Clear +net.kuujo.copycat.collections.state.MapCommands$ContainsKey +net.kuujo.copycat.collections.state.MapCommands$Get +net.kuujo.copycat.collections.state.MapCommands$GetOrDefault +net.kuujo.copycat.collections.state.MapCommands$IsEmpty +net.kuujo.copycat.collections.state.MapCommands$Put +net.kuujo.copycat.collections.state.MapCommands$PutIfAbsent +net.kuujo.copycat.collections.state.MapCommands$Remove +net.kuujo.copycat.collections.state.MapCommands$Size -net.kuujo.copycat.collections.DistributedSet$Add -net.kuujo.copycat.collections.DistributedSet$Clear -net.kuujo.copycat.collections.DistributedSet$Contains -net.kuujo.copycat.collections.DistributedSet$IsEmpty -net.kuujo.copycat.collections.DistributedSet$Remove -net.kuujo.copycat.collections.DistributedSet$Size +net.kuujo.copycat.collections.state.SetCommands$Add +net.kuujo.copycat.collections.state.SetCommands$Clear +net.kuujo.copycat.collections.state.SetCommands$Contains +net.kuujo.copycat.collections.state.SetCommands$IsEmpty +net.kuujo.copycat.collections.state.SetCommands$Remove +net.kuujo.copycat.collections.state.SetCommands$Size + +net.kuujo.copycat.collections.state.TopicCommands$Publish +net.kuujo.copycat.collections.state.TopicCommands$Subscribe diff --git a/collections/src/test/java/net/kuujo/copycat/collections/DistributedMapTest.java b/collections/src/test/java/net/kuujo/copycat/collections/DistributedMapTest.java index cedae2728a..29a35b5fc7 100644 --- a/collections/src/test/java/net/kuujo/copycat/collections/DistributedMapTest.java +++ b/collections/src/test/java/net/kuujo/copycat/collections/DistributedMapTest.java @@ -17,9 +17,17 @@ import net.jodah.concurrentunit.ConcurrentTestCase; import net.kuujo.copycat.Copycat; +import net.kuujo.copycat.CopycatServer; import net.kuujo.copycat.Node; +import net.kuujo.copycat.log.Log; +import net.kuujo.copycat.log.StorageLevel; +import net.kuujo.copycat.raft.Member; +import net.kuujo.copycat.raft.Members; +import net.kuujo.copycat.transport.LocalServerRegistry; +import net.kuujo.copycat.transport.LocalTransport; import org.testng.annotations.Test; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -154,27 +162,30 @@ public void testMapTtl() throws Throwable { * Creates a Copycat instance. */ private List createCopycats(int nodes) throws Throwable { - /* - TestMemberRegistry registry = new TestMemberRegistry(); + LocalServerRegistry registry = new LocalServerRegistry(); List copycats = new ArrayList<>(); expectResumes(nodes); + Members.Builder builder = Members.builder(); for (int i = 1; i <= nodes; i++) { - TestCluster.Builder builder = TestCluster.builder() - .withMemberId(i) - .withRegistry(registry); + builder.addMember(Member.builder() + .withId(i) + .withHost("localhost") + .withPort(5000 + i) + .build()); + } - for (int j = 1; j <= nodes; j++) { - builder.addMember(TestMember.builder() - .withId(j) - .withAddress(String.format("test-%d", j)) - .build()); - } + Members members = builder.build(); + for (int i = 1; i <= nodes; i++) { Copycat copycat = CopycatServer.builder() - .withCluster(builder.build()) + .withMemberId(i) + .withMembers(members) + .withTransport(LocalTransport.builder() + .withRegistry(registry) + .build()) .withLog(Log.builder() .withStorageLevel(StorageLevel.MEMORY) .build()) @@ -183,11 +194,11 @@ private List createCopycats(int nodes) throws Throwable { copycat.open().thenRun(this::resume); copycats.add(copycat); - }*/ + } await(); - return Collections.EMPTY_LIST; + return copycats; } } diff --git a/collections/src/test/java/net/kuujo/copycat/collections/DistributedSetTest.java b/collections/src/test/java/net/kuujo/copycat/collections/DistributedSetTest.java index 928e4f432a..8b7664cc8b 100644 --- a/collections/src/test/java/net/kuujo/copycat/collections/DistributedSetTest.java +++ b/collections/src/test/java/net/kuujo/copycat/collections/DistributedSetTest.java @@ -17,9 +17,17 @@ import net.jodah.concurrentunit.ConcurrentTestCase; import net.kuujo.copycat.Copycat; +import net.kuujo.copycat.CopycatServer; import net.kuujo.copycat.Node; +import net.kuujo.copycat.log.Log; +import net.kuujo.copycat.log.StorageLevel; +import net.kuujo.copycat.raft.Member; +import net.kuujo.copycat.raft.Members; +import net.kuujo.copycat.transport.LocalServerRegistry; +import net.kuujo.copycat.transport.LocalTransport; import org.testng.annotations.Test; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -60,32 +68,34 @@ public void testSetAddRemove() throws Throwable { assertFalse(set2.contains("Hello world!").get()); } - /** * Creates a Copycat instance. */ private List createCopycats(int nodes) throws Throwable { - /* - TestMemberRegistry registry = new TestMemberRegistry(); + LocalServerRegistry registry = new LocalServerRegistry(); List copycats = new ArrayList<>(); expectResumes(nodes); + Members.Builder builder = Members.builder(); for (int i = 1; i <= nodes; i++) { - TestCluster.Builder builder = TestCluster.builder() - .withMemberId(i) - .withRegistry(registry); + builder.addMember(Member.builder() + .withId(i) + .withHost("localhost") + .withPort(5000 + i) + .build()); + } - for (int j = 1; j <= nodes; j++) { - builder.addMember(TestMember.builder() - .withId(j) - .withAddress(String.format("test-%d", j)) - .build()); - } + Members members = builder.build(); + for (int i = 1; i <= nodes; i++) { Copycat copycat = CopycatServer.builder() - .withCluster(builder.build()) + .withMemberId(i) + .withMembers(members) + .withTransport(LocalTransport.builder() + .withRegistry(registry) + .build()) .withLog(Log.builder() .withStorageLevel(StorageLevel.MEMORY) .build()) @@ -94,11 +104,11 @@ private List createCopycats(int nodes) throws Throwable { copycat.open().thenRun(this::resume); copycats.add(copycat); - }*/ + } await(); - return Collections.EMPTY_LIST; + return copycats; } } diff --git a/coordination/src/main/java/net/kuujo/copycat/coordination/AsyncLeaderElection.java b/coordination/src/main/java/net/kuujo/copycat/coordination/AsyncLeaderElection.java new file mode 100644 index 0000000000..1a1ab0656c --- /dev/null +++ b/coordination/src/main/java/net/kuujo/copycat/coordination/AsyncLeaderElection.java @@ -0,0 +1,38 @@ +/* + * 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 net.kuujo.copycat.coordination; + +import net.kuujo.copycat.Listener; +import net.kuujo.copycat.ListenerContext; + +import java.util.concurrent.CompletableFuture; + +/** + * Asynchronous leader election. + * + * @author Jordan Halterman + */ +public interface AsyncLeaderElection { + + /** + * Registers a listener to be called when this client is elected. + * + * @param listener The listener to register. + * @return A completable future to be completed with the listener context. + */ + CompletableFuture> onElection(Listener listener); + +} diff --git a/coordination/src/main/java/net/kuujo/copycat/coordination/AsyncLock.java b/coordination/src/main/java/net/kuujo/copycat/coordination/AsyncLock.java new file mode 100644 index 0000000000..345b393809 --- /dev/null +++ b/coordination/src/main/java/net/kuujo/copycat/coordination/AsyncLock.java @@ -0,0 +1,66 @@ +/* + * 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 net.kuujo.copycat.coordination; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * Asynchronous lock. + * + * @author Jordan Halterman + */ +public interface AsyncLock { + + /** + * Acquires the lock. + * + * @return A completable future to be completed once the lock has been acquired. + */ + CompletableFuture lock(); + + /** + * Acquires the lock if it's free. + * + * @return A completable future to be completed with a boolean indicating whether the lock was acquired. + */ + CompletableFuture tryLock(); + + /** + * Acquires the lock if it's free within the given timeout. + * + * @param time The time within which to acquire the lock in milliseconds. + * @return A completable future to be completed with a boolean indicating whether the lock was acquired. + */ + CompletableFuture tryLock(long time); + + /** + * Acquires the lock if it's free within the given timeout. + * + * @param time The time within which to acquire the lock. + * @param unit The time unit. + * @return A completable future to be completed with a boolean indicating whether the lock was acquired. + */ + CompletableFuture tryLock(long time, TimeUnit unit); + + /** + * Releases the lock. + * + * @return A completable future to be completed once the lock has been released. + */ + CompletableFuture unlock(); + +} diff --git a/coordination/src/main/java/net/kuujo/copycat/coordination/DistributedElection.java b/coordination/src/main/java/net/kuujo/copycat/coordination/DistributedElection.java deleted file mode 100644 index 7e6be570fc..0000000000 --- a/coordination/src/main/java/net/kuujo/copycat/coordination/DistributedElection.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * 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 net.kuujo.copycat.coordination; - -import net.kuujo.alleycat.Alleycat; -import net.kuujo.alleycat.AlleycatSerializable; -import net.kuujo.alleycat.SerializeWith; -import net.kuujo.alleycat.io.BufferInput; -import net.kuujo.alleycat.io.BufferOutput; -import net.kuujo.copycat.*; -import net.kuujo.copycat.log.Compaction; -import net.kuujo.copycat.raft.Command; -import net.kuujo.copycat.raft.Operation; -import net.kuujo.copycat.raft.Raft; -import net.kuujo.copycat.raft.Session; -import net.kuujo.copycat.raft.server.Apply; -import net.kuujo.copycat.raft.server.Commit; -import net.kuujo.copycat.raft.server.Filter; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Asynchronous leader election resource. - * - * @author Jordan Halterman - */ -@Stateful(DistributedElection.StateMachine.class) -public class DistributedElection extends Resource { - private final Set> listeners = Collections.newSetFromMap(new ConcurrentHashMap<>()); - - public DistributedElection(Raft protocol) { - super(protocol); - protocol.session().onReceive(v -> { - for (Listener listener : listeners) { - listener.accept(null); - } - }); - } - - /** - * Registers a listener to be called when this client is elected. - * - * @param listener The listener to register. - * @return A completable future to be completed with the listener context. - */ - public CompletableFuture> onElection(Listener listener) { - if (!listeners.isEmpty()) { - listeners.add(listener); - return CompletableFuture.completedFuture(new ElectionListenerContext(listener)); - } - - listeners.add(listener); - return submit(Listen.builder().build()) - .thenApply(v -> new ElectionListenerContext(listener)); - } - - /** - * Change listener context. - */ - private class ElectionListenerContext implements ListenerContext { - private final Listener listener; - - private ElectionListenerContext(Listener listener) { - this.listener = listener; - } - - @Override - public void accept(Void event) { - listener.accept(event); - } - - @Override - public void close() { - synchronized (DistributedElection.this) { - listeners.remove(listener); - if (listeners.isEmpty()) { - submit(Unlisten.builder().build()); - } - } - } - } - - /** - * Abstract election command. - */ - public static abstract class ElectionCommand implements Command, AlleycatSerializable { - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - } - - /** - * Base reference command builder. - */ - public static abstract class Builder, U extends ElectionCommand, V> extends Command.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - } - } - - /** - * Listen command. - */ - @SerializeWith(id=510) - public static class Listen extends ElectionCommand { - - /** - * Returns a new listen command builder. - * - * @return A new listen command builder. - */ - @SuppressWarnings("unchecked") - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Listen command builder. - */ - public static class Builder extends ElectionCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Listen create() { - return new Listen(); - } - } - } - - /** - * Unlisten command. - */ - @SerializeWith(id=510) - public static class Unlisten extends ElectionCommand { - - /** - * Returns a new unlisten command builder. - * - * @return A new unlisten command builder. - */ - @SuppressWarnings("unchecked") - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Unlisten command builder. - */ - public static class Builder extends ElectionCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Unlisten create() { - return new Unlisten(); - } - } - } - - /** - * Async leader election state machine. - */ - public static class StateMachine extends net.kuujo.copycat.raft.server.StateMachine { - private long version; - private Session leader; - private final List> listeners = new ArrayList<>(); - - @Override - public void close(Session session) { - if (leader != null && leader.equals(session)) { - leader = null; - if (!listeners.isEmpty()) { - Commit leader = listeners.remove(0); - this.leader = leader.session(); - this.version = leader.index(); - this.leader.publish(true); - } - } - } - - /** - * Applies listen commits. - */ - @Apply(Listen.class) - protected void applyListen(Commit commit) { - if (leader == null) { - leader = commit.session(); - version = commit.index(); - leader.publish(true); - } else { - listeners.add(commit); - } - } - - /** - * Applies listen commits. - */ - @Apply(Unlisten.class) - protected void applyUnlisten(Commit commit) { - if (leader != null && leader.equals(commit.session())) { - leader = null; - if (!listeners.isEmpty()) { - Commit leader = listeners.remove(0); - this.leader = leader.session(); - this.version = leader.index(); - this.leader.publish(true); - } - } - } - - /** - * Filters listen commits. - */ - @Filter(Listen.class) - protected boolean filterListen(Commit commit, Compaction compaction) { - return commit.index() >= version; - } - - /** - * Filters unlisten commits. - */ - @Filter(Unlisten.class) - protected boolean filterUnlisten(Commit commit, Compaction compaction) { - return commit.index() >= version; - } - } - -} diff --git a/coordination/src/main/java/net/kuujo/copycat/coordination/DistributedLeaderElection.java b/coordination/src/main/java/net/kuujo/copycat/coordination/DistributedLeaderElection.java new file mode 100644 index 0000000000..49e3fc5b1e --- /dev/null +++ b/coordination/src/main/java/net/kuujo/copycat/coordination/DistributedLeaderElection.java @@ -0,0 +1,87 @@ +/* + * 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 net.kuujo.copycat.coordination; + +import net.kuujo.copycat.Listener; +import net.kuujo.copycat.ListenerContext; +import net.kuujo.copycat.Resource; +import net.kuujo.copycat.Stateful; +import net.kuujo.copycat.coordination.state.LeaderElectionCommands; +import net.kuujo.copycat.coordination.state.LeaderElectionState; +import net.kuujo.copycat.raft.Raft; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Asynchronous leader election resource. + * + * @author Jordan Halterman + */ +@Stateful(LeaderElectionState.class) +public class DistributedLeaderElection extends Resource implements AsyncLeaderElection { + private final Set> listeners = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public DistributedLeaderElection(Raft protocol) { + super(protocol); + protocol.session().onReceive(v -> { + for (Listener listener : listeners) { + listener.accept(null); + } + }); + } + + @Override + public CompletableFuture> onElection(Listener listener) { + if (!listeners.isEmpty()) { + listeners.add(listener); + return CompletableFuture.completedFuture(new ElectionListenerContext(listener)); + } + + listeners.add(listener); + return submit(LeaderElectionCommands.Listen.builder().build()) + .thenApply(v -> new ElectionListenerContext(listener)); + } + + /** + * Change listener context. + */ + private class ElectionListenerContext implements ListenerContext { + private final Listener listener; + + private ElectionListenerContext(Listener listener) { + this.listener = listener; + } + + @Override + public void accept(Void event) { + listener.accept(event); + } + + @Override + public void close() { + synchronized (DistributedLeaderElection.this) { + listeners.remove(listener); + if (listeners.isEmpty()) { + submit(LeaderElectionCommands.Unlisten.builder().build()); + } + } + } + } + +} diff --git a/coordination/src/main/java/net/kuujo/copycat/coordination/DistributedLock.java b/coordination/src/main/java/net/kuujo/copycat/coordination/DistributedLock.java index 14f2b7385b..cd9ab9b4e5 100644 --- a/coordination/src/main/java/net/kuujo/copycat/coordination/DistributedLock.java +++ b/coordination/src/main/java/net/kuujo/copycat/coordination/DistributedLock.java @@ -15,23 +15,12 @@ */ package net.kuujo.copycat.coordination; -import net.kuujo.alleycat.Alleycat; -import net.kuujo.alleycat.AlleycatSerializable; -import net.kuujo.alleycat.SerializeWith; -import net.kuujo.alleycat.io.BufferInput; -import net.kuujo.alleycat.io.BufferOutput; -import net.kuujo.copycat.BuilderPool; import net.kuujo.copycat.Resource; import net.kuujo.copycat.Stateful; -import net.kuujo.copycat.log.Compaction; -import net.kuujo.copycat.raft.Command; -import net.kuujo.copycat.raft.Operation; +import net.kuujo.copycat.coordination.state.LockCommands; +import net.kuujo.copycat.coordination.state.LockState; import net.kuujo.copycat.raft.Raft; -import net.kuujo.copycat.raft.server.Apply; -import net.kuujo.copycat.raft.server.Commit; -import net.kuujo.copycat.raft.server.Filter; -import java.util.ArrayDeque; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; @@ -43,8 +32,8 @@ * * @author Jordan Halterman */ -@Stateful(DistributedLock.StateMachine.class) -public class DistributedLock extends Resource { +@Stateful(LockState.class) +public class DistributedLock extends Resource implements AsyncLock { private final Queue> queue = new ConcurrentLinkedQueue<>(); public DistributedLock(Raft protocol) { @@ -62,16 +51,12 @@ private void receive(boolean locked) { } } - /** - * Acquires the lock. - * - * @return A completable future to be completed once the lock has been acquired. - */ + @Override public CompletableFuture lock() { CompletableFuture future = new CompletableFuture<>(); Consumer consumer = locked -> future.complete(null); queue.add(consumer); - submit(Lock.builder().withTimeout(-1).build()).whenComplete((result, error) -> { + submit(LockCommands.Lock.builder().withTimeout(-1).build()).whenComplete((result, error) -> { if (error != null) { queue.remove(consumer); } @@ -79,16 +64,12 @@ public CompletableFuture lock() { return future; } - /** - * Acquires the lock if it's free. - * - * @return A completable future to be completed with a boolean indicating whether the lock was acquired. - */ + @Override public CompletableFuture tryLock() { CompletableFuture future = new CompletableFuture<>(); Consumer consumer = future::complete; queue.add(consumer); - submit(Lock.builder().build()).whenComplete((result, error) -> { + submit(LockCommands.Lock.builder().build()).whenComplete((result, error) -> { if (error != null) { queue.remove(consumer); } @@ -96,17 +77,12 @@ public CompletableFuture tryLock() { return future; } - /** - * Acquires the lock if it's free within the given timeout. - * - * @param time The time within which to acquire the lock in milliseconds. - * @return A completable future to be completed with a boolean indicating whether the lock was acquired. - */ + @Override public CompletableFuture tryLock(long time) { CompletableFuture future = new CompletableFuture<>(); Consumer consumer = future::complete; queue.add(consumer); - submit(Lock.builder().withTimeout(time).build()).whenComplete((result, error) -> { + submit(LockCommands.Lock.builder().withTimeout(time).build()).whenComplete((result, error) -> { if (error != null) { queue.remove(consumer); } @@ -114,235 +90,14 @@ public CompletableFuture tryLock(long time) { return future; } - /** - * Acquires the lock if it's free within the given timeout. - * - * @param time The time within which to acquire the lock. - * @param unit The time unit. - * @return A completable future to be completed with a boolean indicating whether the lock was acquired. - */ + @Override public CompletableFuture tryLock(long time, TimeUnit unit) { - return submit(Lock.builder().withTimeout(time, unit).build()); + return submit(LockCommands.Lock.builder().withTimeout(time, unit).build()); } - /** - * Releases the lock. - * - * @return A completable future to be completed once the lock has been released. - */ + @Override public CompletableFuture unlock() { - return submit(Unlock.builder().build()); - } - - /** - * Abstract lock command. - */ - public static abstract class LockCommand implements Command, AlleycatSerializable { - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - } - - /** - * Base reference command builder. - */ - public static abstract class Builder, U extends LockCommand, V> extends Command.Builder { - protected Builder(BuilderPool pool) { - super(pool); - } - } - } - - /** - * Lock command. - */ - @SerializeWith(id=512) - public static class Lock extends LockCommand { - - /** - * Returns a new lock command builder. - * - * @return A new lock command builder. - */ - @SuppressWarnings("unchecked") - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - private long timeout; - - /** - * Returns the try lock timeout. - * - * @return The try lock timeout in milliseconds. - */ - public long timeout() { - return timeout; - } - - @Override - public void writeObject(BufferOutput buffer, Alleycat alleycat) { - buffer.writeLong(timeout); - } - - @Override - public void readObject(BufferInput buffer, Alleycat alleycat) { - timeout = buffer.readLong(); - } - - /** - * Try lock builder. - */ - public static class Builder extends LockCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected void reset(Lock command) { - super.reset(command); - command.timeout = 0; - } - - /** - * Sets the lock timeout. - * - * @param timeout The lock timeout in milliseconds. - * @return The command builder. - */ - public Builder withTimeout(long timeout) { - if (timeout < -1) - throw new IllegalArgumentException("timeout cannot be less than -1"); - command.timeout = timeout; - return this; - } - - /** - * Sets the lock timeout. - * - * @param timeout The lock timeout. - * @param unit The lock timeout time unit. - * @return The command builder. - */ - public Builder withTimeout(long timeout, TimeUnit unit) { - return withTimeout(unit.toMillis(timeout)); - } - - @Override - protected Lock create() { - return new Lock(); - } - } - } - - /** - * Unlock command. - */ - @SerializeWith(id=513) - public static class Unlock extends LockCommand { - - /** - * Returns a new unlock command builder. - * - * @return A new unlock command builder. - */ - @SuppressWarnings("unchecked") - public static Builder builder() { - return Operation.builder(Builder.class, Builder::new); - } - - /** - * Unlock command builder. - */ - public static class Builder extends LockCommand.Builder { - public Builder(BuilderPool pool) { - super(pool); - } - - @Override - protected Unlock create() { - return new Unlock(); - } - } - } - - /** - * Asynchronous lock state machine. - */ - public static class StateMachine extends net.kuujo.copycat.raft.server.StateMachine { - private long version; - private long time; - private Commit lock; - private final Queue> queue = new ArrayDeque<>(); - - /** - * Updates the current time. - */ - private void updateTime(Commit commit) { - this.time = commit.timestamp(); - } - - /** - * Applies a lock commit. - */ - @Apply(Lock.class) - protected void applyLock(Commit commit) { - updateTime(commit); - if (lock == null) { - lock = commit; - commit.session().publish(true); - version = commit.index(); - } else if (commit.operation().timeout() == 0) { - commit.session().publish(false); - version = commit.index(); - } else { - queue.add(commit); - } - } - - /** - * Applies an unlock commit. - */ - @Apply(Unlock.class) - protected void applyUnlock(Commit commit) { - updateTime(commit); - - if (lock == null) - throw new IllegalStateException("not locked"); - if (!lock.session().equals(commit.session())) - throw new IllegalStateException("not the lock holder"); - - lock = queue.poll(); - while (lock != null && (lock.operation().timeout() != -1 && lock.timestamp() + lock.operation().timeout() < time)) { - version = lock.index(); - lock = queue.poll(); - } - - if (lock != null) { - lock.session().publish(true); - version = lock.index(); - } - } - - /** - * Filters a lock commit. - */ - @Filter(Lock.class) - protected boolean filterLock(Commit commit, Compaction compaction) { - return commit.index() >= version; - } - - /** - * Filters an unlock commit. - */ - @Filter(Lock.class) - protected boolean filterUnlock(Commit commit, Compaction compaction) { - return commit.index() >= version; - } - + return submit(LockCommands.Unlock.builder().build()); } } diff --git a/coordination/src/main/java/net/kuujo/copycat/coordination/state/LeaderElectionCommands.java b/coordination/src/main/java/net/kuujo/copycat/coordination/state/LeaderElectionCommands.java new file mode 100644 index 0000000000..0679db10ce --- /dev/null +++ b/coordination/src/main/java/net/kuujo/copycat/coordination/state/LeaderElectionCommands.java @@ -0,0 +1,121 @@ +/* + * 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 net.kuujo.copycat.coordination.state; + +import net.kuujo.alleycat.Alleycat; +import net.kuujo.alleycat.AlleycatSerializable; +import net.kuujo.alleycat.SerializeWith; +import net.kuujo.alleycat.io.BufferInput; +import net.kuujo.alleycat.io.BufferOutput; +import net.kuujo.copycat.BuilderPool; +import net.kuujo.copycat.raft.Command; +import net.kuujo.copycat.raft.Operation; + +/** + * Leader election commands. + * + * @author Jordan Halterman + */ +public class LeaderElectionCommands { + + private LeaderElectionCommands() { + } + + /** + * Abstract election command. + */ + public static abstract class ElectionCommand implements Command, AlleycatSerializable { + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + } + + /** + * Base reference command builder. + */ + public static abstract class Builder, U extends ElectionCommand, V> extends Command.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + } + } + + /** + * Listen command. + */ + @SerializeWith(id=510) + public static class Listen extends ElectionCommand { + + /** + * Returns a new listen command builder. + * + * @return A new listen command builder. + */ + @SuppressWarnings("unchecked") + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Listen command builder. + */ + public static class Builder extends ElectionCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Listen create() { + return new Listen(); + } + } + } + + /** + * Unlisten command. + */ + @SerializeWith(id=511) + public static class Unlisten extends ElectionCommand { + + /** + * Returns a new unlisten command builder. + * + * @return A new unlisten command builder. + */ + @SuppressWarnings("unchecked") + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Unlisten command builder. + */ + public static class Builder extends ElectionCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Unlisten create() { + return new Unlisten(); + } + } + } + +} diff --git a/coordination/src/main/java/net/kuujo/copycat/coordination/state/LeaderElectionState.java b/coordination/src/main/java/net/kuujo/copycat/coordination/state/LeaderElectionState.java new file mode 100644 index 0000000000..1b87821139 --- /dev/null +++ b/coordination/src/main/java/net/kuujo/copycat/coordination/state/LeaderElectionState.java @@ -0,0 +1,104 @@ +/* + * 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 net.kuujo.copycat.coordination.state; + +import net.kuujo.copycat.log.Compaction; +import net.kuujo.copycat.raft.Operation; +import net.kuujo.copycat.raft.Session; +import net.kuujo.copycat.raft.server.Apply; +import net.kuujo.copycat.raft.server.Commit; +import net.kuujo.copycat.raft.server.Filter; +import net.kuujo.copycat.raft.server.StateMachine; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * Leader election state machine. + * + * @author Jordan Halterman + */ +public class LeaderElectionState extends StateMachine { + private long version; + private Session leader; + private final List> listeners = new ArrayList<>(); + + @Override + public void close(Session session) { + if (leader != null && leader.equals(session)) { + leader = null; + if (!listeners.isEmpty()) { + Commit leader = listeners.remove(0); + this.leader = leader.session(); + this.version = leader.index(); + this.leader.publish(true); + } + } + } + + @Override + public CompletableFuture apply(Commit commit) { + return super.apply(commit); + } + + /** + * Applies listen commits. + */ + @Apply(LeaderElectionCommands.Listen.class) + protected void applyListen(Commit commit) { + if (leader == null) { + leader = commit.session(); + version = commit.index(); + leader.publish(true); + } else { + listeners.add(commit); + } + } + + /** + * Applies listen commits. + */ + @Apply(LeaderElectionCommands.Unlisten.class) + protected void applyUnlisten(Commit commit) { + if (leader != null && leader.equals(commit.session())) { + leader = null; + if (!listeners.isEmpty()) { + Commit leader = listeners.remove(0); + this.leader = leader.session(); + this.version = leader.index(); + this.leader.publish(true); + } + } + } + + /** + * Filters listen commits. + */ + @Filter(LeaderElectionCommands.Listen.class) + protected boolean filterListen(Commit commit, Compaction compaction) { + return commit.index() >= version; + } + + /** + * Filters unlisten commits. + */ + @Filter(LeaderElectionCommands.Unlisten.class) + protected boolean filterUnlisten(Commit commit, Compaction compaction) { + return commit.index() >= version; + } + +} diff --git a/coordination/src/main/java/net/kuujo/copycat/coordination/state/LockCommands.java b/coordination/src/main/java/net/kuujo/copycat/coordination/state/LockCommands.java new file mode 100644 index 0000000000..85b211aa6e --- /dev/null +++ b/coordination/src/main/java/net/kuujo/copycat/coordination/state/LockCommands.java @@ -0,0 +1,174 @@ +/* + * 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 net.kuujo.copycat.coordination.state; + +import net.kuujo.alleycat.Alleycat; +import net.kuujo.alleycat.AlleycatSerializable; +import net.kuujo.alleycat.SerializeWith; +import net.kuujo.alleycat.io.BufferInput; +import net.kuujo.alleycat.io.BufferOutput; +import net.kuujo.copycat.BuilderPool; +import net.kuujo.copycat.raft.Command; +import net.kuujo.copycat.raft.Operation; + +import java.util.concurrent.TimeUnit; + +/** + * Lock commands. + * + * @author Jordan Halterman + */ +public class LockCommands { + + private LockCommands() { + } + + /** + * Abstract lock command. + */ + public static abstract class LockCommand implements Command, AlleycatSerializable { + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + } + + /** + * Base reference command builder. + */ + public static abstract class Builder, U extends LockCommand, V> extends Command.Builder { + protected Builder(BuilderPool pool) { + super(pool); + } + } + } + + /** + * Lock command. + */ + @SerializeWith(id=512) + public static class Lock extends LockCommand { + + /** + * Returns a new lock command builder. + * + * @return A new lock command builder. + */ + @SuppressWarnings("unchecked") + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + private long timeout; + + /** + * Returns the try lock timeout. + * + * @return The try lock timeout in milliseconds. + */ + public long timeout() { + return timeout; + } + + @Override + public void writeObject(BufferOutput buffer, Alleycat alleycat) { + buffer.writeLong(timeout); + } + + @Override + public void readObject(BufferInput buffer, Alleycat alleycat) { + timeout = buffer.readLong(); + } + + /** + * Try lock builder. + */ + public static class Builder extends LockCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected void reset(Lock command) { + super.reset(command); + command.timeout = 0; + } + + /** + * Sets the lock timeout. + * + * @param timeout The lock timeout in milliseconds. + * @return The command builder. + */ + public Builder withTimeout(long timeout) { + if (timeout < -1) + throw new IllegalArgumentException("timeout cannot be less than -1"); + command.timeout = timeout; + return this; + } + + /** + * Sets the lock timeout. + * + * @param timeout The lock timeout. + * @param unit The lock timeout time unit. + * @return The command builder. + */ + public Builder withTimeout(long timeout, TimeUnit unit) { + return withTimeout(unit.toMillis(timeout)); + } + + @Override + protected Lock create() { + return new Lock(); + } + } + } + + /** + * Unlock command. + */ + @SerializeWith(id=513) + public static class Unlock extends LockCommand { + + /** + * Returns a new unlock command builder. + * + * @return A new unlock command builder. + */ + @SuppressWarnings("unchecked") + public static Builder builder() { + return Operation.builder(Builder.class, Builder::new); + } + + /** + * Unlock command builder. + */ + public static class Builder extends LockCommand.Builder { + public Builder(BuilderPool pool) { + super(pool); + } + + @Override + protected Unlock create() { + return new Unlock(); + } + } + } + +} diff --git a/coordination/src/main/java/net/kuujo/copycat/coordination/state/LockState.java b/coordination/src/main/java/net/kuujo/copycat/coordination/state/LockState.java new file mode 100644 index 0000000000..5f49f1c63c --- /dev/null +++ b/coordination/src/main/java/net/kuujo/copycat/coordination/state/LockState.java @@ -0,0 +1,103 @@ +/* + * 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 net.kuujo.copycat.coordination.state; + +import net.kuujo.copycat.log.Compaction; +import net.kuujo.copycat.raft.server.Apply; +import net.kuujo.copycat.raft.server.Commit; +import net.kuujo.copycat.raft.server.Filter; +import net.kuujo.copycat.raft.server.StateMachine; + +import java.util.ArrayDeque; +import java.util.Queue; + +/** + * Lock state machine. + * + * @author Jordan Halterman + */ +public class LockState extends StateMachine { + private long version; + private long time; + private Commit lock; + private final Queue> queue = new ArrayDeque<>(); + + /** + * Updates the current time. + */ + private void updateTime(Commit commit) { + this.time = commit.timestamp(); + } + + /** + * Applies a lock commit. + */ + @Apply(LockCommands.Lock.class) + protected void applyLock(Commit commit) { + updateTime(commit); + if (lock == null) { + lock = commit; + commit.session().publish(true); + version = commit.index(); + } else if (commit.operation().timeout() == 0) { + commit.session().publish(false); + version = commit.index(); + } else { + queue.add(commit); + } + } + + /** + * Applies an unlock commit. + */ + @Apply(LockCommands.Unlock.class) + protected void applyUnlock(Commit commit) { + updateTime(commit); + + if (lock == null) + throw new IllegalStateException("not locked"); + if (!lock.session().equals(commit.session())) + throw new IllegalStateException("not the lock holder"); + + lock = queue.poll(); + while (lock != null && (lock.operation().timeout() != -1 && lock.timestamp() + lock.operation().timeout() < time)) { + version = lock.index(); + lock = queue.poll(); + } + + if (lock != null) { + lock.session().publish(true); + version = lock.index(); + } + } + + /** + * Filters a lock commit. + */ + @Filter(LockCommands.Lock.class) + protected boolean filterLock(Commit commit, Compaction compaction) { + return commit.index() >= version; + } + + /** + * Filters an unlock commit. + */ + @Filter(LockCommands.Lock.class) + protected boolean filterUnlock(Commit commit, Compaction compaction) { + return commit.index() >= version; + } + +} diff --git a/coordination/src/main/resources/META-INF/services/net.kuujo.alleycat.AlleycatSerializable b/coordination/src/main/resources/META-INF/services/net.kuujo.alleycat.AlleycatSerializable index ddb87601ce..b983601de0 100644 --- a/coordination/src/main/resources/META-INF/services/net.kuujo.alleycat.AlleycatSerializable +++ b/coordination/src/main/resources/META-INF/services/net.kuujo.alleycat.AlleycatSerializable @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # -net.kuujo.copycat.coordination.DistributedElection$Listen -net.kuujo.copycat.coordination.DistributedElection$Unlisten +net.kuujo.copycat.coordination.state.LeaderElectionCommands$Listen +net.kuujo.copycat.coordination.state.LeaderElectionCommands$Unlisten -net.kuujo.copycat.coordination.DistributedLock$Lock -net.kuujo.copycat.coordination.DistributedLock$Unlock +net.kuujo.copycat.coordination.state.LockCommands$Lock +net.kuujo.copycat.coordination.state.LockCommands$Unlock diff --git a/coordination/src/test/java/net/kuujo/copycat/coordination/DistributedElectionTest.java b/coordination/src/test/java/net/kuujo/copycat/coordination/DistributedLeaderElectionTest.java similarity index 94% rename from coordination/src/test/java/net/kuujo/copycat/coordination/DistributedElectionTest.java rename to coordination/src/test/java/net/kuujo/copycat/coordination/DistributedLeaderElectionTest.java index e954bdeeab..4821bff151 100644 --- a/coordination/src/test/java/net/kuujo/copycat/coordination/DistributedElectionTest.java +++ b/coordination/src/test/java/net/kuujo/copycat/coordination/DistributedLeaderElectionTest.java @@ -37,7 +37,7 @@ */ @Test @SuppressWarnings("unchecked") -public class DistributedElectionTest extends ConcurrentTestCase { +public class DistributedLeaderElectionTest extends ConcurrentTestCase { /** * Tests winning leadership. @@ -49,7 +49,7 @@ public void testElection() throws Throwable { Copycat copycat = servers.get(0); Node node = copycat.create("/test").get(); - DistributedElection election = node.create(DistributedElection.class).get(); + DistributedLeaderElection election = node.create(DistributedLeaderElection.class).get(); expectResumes(2); election.onElection(v -> resume()).thenRun(this::resume); diff --git a/core/src/main/java/net/kuujo/copycat/Copycat.java b/core/src/main/java/net/kuujo/copycat/Copycat.java index 85c2197818..56b853250e 100644 --- a/core/src/main/java/net/kuujo/copycat/Copycat.java +++ b/core/src/main/java/net/kuujo/copycat/Copycat.java @@ -15,7 +15,10 @@ */ package net.kuujo.copycat; -import net.kuujo.copycat.manager.*; +import net.kuujo.copycat.manager.CreatePath; +import net.kuujo.copycat.manager.CreateResource; +import net.kuujo.copycat.manager.DeletePath; +import net.kuujo.copycat.manager.PathExists; import net.kuujo.copycat.raft.ManagedRaft; import net.kuujo.copycat.raft.Raft; import net.kuujo.copycat.raft.server.StateMachine; @@ -124,15 +127,7 @@ public CompletableFuture delete(String path) { @Override public CompletableFuture open() { - return raft.open().thenApply(v -> { - raft.session().onReceive(message -> { - ResourceProtocol resource = resources.get(message.resource()); - if (resource != null) { - resource.session().publish(message); - } - }); - return this; - }); + return raft.open().thenApply(v -> this); } @Override diff --git a/pom.xml b/pom.xml index e7204e2418..ab4c3200c2 100644 --- a/pom.xml +++ b/pom.xml @@ -221,7 +221,7 @@ ${maven.javadoc.plugin.version} public - net.kuujo.copycat.server.state.*:net.kuujo.copycat.client.state.* + *.state.* target/docs -Xdoclint:none true