You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by bd...@apache.org on 2022/10/13 00:21:09 UTC
[cassandra-accord] branch trunk updated: Accord transaction state storage
This is an automated email from the ASF dual-hosted git repository.
bdeggleston pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-accord.git
The following commit(s) were added to refs/heads/trunk by this push:
new 5dd0045 Accord transaction state storage
5dd0045 is described below
commit 5dd0045da7cc353481be6a5caf2e8c8e933b3edc
Author: Blake Eggleston <be...@apple.com>
AuthorDate: Wed Oct 12 17:21:04 2022 -0700
Accord transaction state storage
Patch by Blake Eggleston; Reviewed by David Capwell and Benedict Elliott Smith for CASSANDRA-17104
---
accord-core/build.gradle | 6 +-
.../src/main/java/accord/api/ProgressLog.java | 19 +-
accord-core/src/main/java/accord/api/Read.java | 5 +-
accord-core/src/main/java/accord/api/Write.java | 4 +-
.../java/accord/coordinate/CheckOnCommitted.java | 13 +-
.../java/accord/coordinate/CheckOnUncommitted.java | 5 +-
.../src/main/java/accord/coordinate/Execute.java | 31 +-
.../main/java/accord/coordinate/Invalidate.java | 9 +-
.../src/main/java/accord/coordinate/Recover.java | 4 +-
.../src/main/java/accord/impl/InMemoryCommand.java | 314 +++++++++++
.../InMemoryCommandStore.java} | 301 ++++-------
.../java/accord/impl/InMemoryCommandStores.java | 110 ++++
.../java/accord/impl/InMemoryCommandsForKey.java | 137 +++++
.../main/java/accord/impl/SimpleProgressLog.java | 142 ++---
.../src/main/java/accord/local/Command.java | 573 +++++++++++----------
.../src/main/java/accord/local/CommandStore.java | 331 ++----------
.../src/main/java/accord/local/CommandStores.java | 345 ++++++-------
.../src/main/java/accord/local/CommandsForKey.java | 83 ++-
.../src/main/java/accord/local/Listener.java | 15 +
.../src/main/java/accord/local/Listeners.java | 2 +-
accord-core/src/main/java/accord/local/Node.java | 71 +--
.../src/main/java/accord/local/PartialCommand.java | 47 ++
.../src/main/java/accord/local/PreLoadContext.java | 68 +++
.../src/main/java/accord/messages/Accept.java | 82 ++-
.../src/main/java/accord/messages/Apply.java | 57 +-
.../main/java/accord/messages/BeginInvalidate.java | 37 +-
.../main/java/accord/messages/BeginRecovery.java | 65 ++-
.../src/main/java/accord/messages/CheckStatus.java | 33 +-
.../src/main/java/accord/messages/Commit.java | 87 +++-
.../java/accord/messages/InformOfPersistence.java | 32 +-
.../src/main/java/accord/messages/InformOfTxn.java | 32 +-
.../src/main/java/accord/messages/PreAccept.java | 66 ++-
.../src/main/java/accord/messages/ReadData.java | 128 ++++-
.../src/main/java/accord/messages/Reply.java | 1 -
.../src/main/java/accord/messages/TxnRequest.java | 3 +-
.../main/java/accord/messages/WaitOnCommit.java | 74 ++-
.../src/main/java/accord/primitives/Deps.java | 55 +-
.../src/main/java/accord/primitives/KeyRanges.java | 5 +-
.../src/main/java/accord/primitives/Keys.java | 38 +-
.../src/main/java/accord/primitives/Timestamp.java | 1 +
.../src/main/java/accord/topology/Topologies.java | 8 +-
.../src/main/java/accord/topology/Topology.java | 17 -
accord-core/src/main/java/accord/txn/Txn.java | 129 +++--
accord-core/src/main/java/accord/txn/Writes.java | 37 +-
.../accord/utils/DeterministicIdentitySet.java | 28 +-
.../src/main/java/accord/utils/ReducingFuture.java | 54 ++
.../src/main/java/accord/utils/SortedArrays.java | 2 +-
.../java/accord/utils/Utils.java} | 48 +-
.../cassandra/concurrent/ExecutionFailure.java | 180 -------
.../apache/cassandra/concurrent/ExecutorPlus.java | 185 -------
.../cassandra/concurrent/ImmediateExecutor.java | 44 --
.../java/org/apache/cassandra/utils/Clock.java | 124 -----
.../java/org/apache/cassandra/utils/Intercept.java | 31 --
.../java/org/apache/cassandra/utils/Shared.java | 45 --
.../cassandra/utils/concurrent/AbstractFuture.java | 508 ------------------
.../cassandra/utils/concurrent/AsyncFuture.java | 162 ------
.../cassandra/utils/concurrent/AsyncPromise.java | 268 ----------
.../cassandra/utils/concurrent/Awaitable.java | 404 ---------------
.../cassandra/utils/concurrent/Condition.java | 104 ----
.../apache/cassandra/utils/concurrent/Future.java | 191 -------
.../utils/concurrent/ImmediateFuture.java | 52 --
.../apache/cassandra/utils/concurrent/Inline.java | 23 -
.../cassandra/utils/concurrent/IntrusiveStack.java | 156 ------
.../cassandra/utils/concurrent/ListenerList.java | 376 --------------
.../apache/cassandra/utils/concurrent/Promise.java | 128 -----
.../concurrent/UncheckedInterruptedException.java | 38 --
.../cassandra/utils/concurrent/WaitQueue.java | 437 ----------------
.../src/test/java/accord/NetworkFilter.java | 2 +-
accord-core/src/test/java/accord/Utils.java | 4 +-
.../src/test/java/accord/burn/BurnTest.java | 8 +-
.../src/test/java/accord/burn/TopologyUpdate.java | 11 +-
.../java/accord/coordinate/CoordinateTest.java | 57 +-
.../accord/coordinate/PreAcceptTrackerTest.java | 4 +-
.../test/java/accord/coordinate/RecoverTest.java | 5 +-
.../java/accord/coordinate/TopologyChangeTest.java | 13 +-
.../coordinate/tracking/QuorumTrackerTest.java | 4 +-
.../coordinate/tracking/ReadTrackerTest.java | 7 +-
.../src/test/java/accord/impl/IntHashKey.java | 8 +-
accord-core/src/test/java/accord/impl/IntKey.java | 4 +-
.../src/test/java/accord/impl/TopologyFactory.java | 5 +-
.../src/test/java/accord/impl/TopologyUtils.java | 7 +-
.../src/test/java/accord/impl/basic/Cluster.java | 6 +-
.../src/test/java/accord/impl/list/ListRead.java | 8 +-
.../src/test/java/accord/impl/list/ListWrite.java | 9 +-
.../src/test/java/accord/impl/mock/EpochSync.java | 8 +-
.../test/java/accord/impl/mock/MockCluster.java | 6 +-
.../src/test/java/accord/impl/mock/MockStore.java | 9 +-
.../src/test/java/accord/local/CommandTest.java | 42 +-
.../test/java/accord/messages/PreAcceptTest.java | 17 +-
accord-core/src/test/java/accord/txn/DepsTest.java | 7 +-
.../test/java/accord/utils/SortedArraysTest.java | 9 +-
accord-maelstrom/build.gradle | 1 +
.../src/main/java/accord/maelstrom/Body.java | 8 +-
.../src/main/java/accord/maelstrom/Cluster.java | 6 +-
.../src/main/java/accord/maelstrom/Datum.java | 6 +-
.../src/main/java/accord/maelstrom/Json.java | 36 +-
.../main/java/accord/maelstrom/MaelstromKey.java | 2 +-
.../main/java/accord/maelstrom/MaelstromRead.java | 7 +-
.../java/accord/maelstrom/MaelstromRequest.java | 8 +-
.../java/accord/maelstrom/MaelstromResult.java | 2 +-
.../main/java/accord/maelstrom/MaelstromWrite.java | 6 +-
.../src/main/java/accord/maelstrom/Main.java | 7 +-
.../src/main/java/accord/maelstrom/Packet.java | 2 +-
.../java/accord/maelstrom/TopologyFactory.java | 4 +-
.../src/main/java/accord/maelstrom/Value.java | 6 +-
.../src/test/java/accord/maelstrom/Runner.java | 10 +-
.../src/main/groovy/accord.java-conventions.gradle | 2 +-
107 files changed, 2659 insertions(+), 4924 deletions(-)
diff --git a/accord-core/build.gradle b/accord-core/build.gradle
index 1bd1a28..f8c4688 100644
--- a/accord-core/build.gradle
+++ b/accord-core/build.gradle
@@ -30,10 +30,8 @@ java {
}
dependencies {
- // Future is part of our API and depends on netty/guava; for this reason need to expose netty/guava
- // as part as our API (compile + runtime dependency for users)
- api 'io.netty:netty-all:4.1.58.Final'
- api 'com.google.guava:guava:30.1.1-jre'
+ implementation group: 'org.apache.cassandra', name: 'cassandra-all', version: '4.1-alpha1'
+ implementation group: "net.ju-n.compile-command-annotations", name: "compile-command-annotations", version: "1.2.0"
// Dependencies we depend on that are not part of our API.
// These act as runtimeOnly dependencies to users
diff --git a/accord-core/src/main/java/accord/api/ProgressLog.java b/accord-core/src/main/java/accord/api/ProgressLog.java
index 69c2091..a7c76f9 100644
--- a/accord-core/src/main/java/accord/api/ProgressLog.java
+++ b/accord-core/src/main/java/accord/api/ProgressLog.java
@@ -24,10 +24,11 @@ import javax.annotation.Nullable;
import accord.coordinate.CheckOnUncommitted;
import accord.coordinate.InformHomeOfTxn;
+import accord.local.Command;
import accord.local.CommandStore;
import accord.local.Node.Id;
+import accord.local.PartialCommand;
import accord.primitives.Keys;
-import accord.primitives.TxnId;
/**
* This interface is responsible for managing incomplete transactions *and retrying them*.
@@ -70,7 +71,7 @@ public interface ProgressLog
* A non-home shard should begin monitoring this transaction only to ensure it reaches the Accept phase, or is
* witnessed by a majority of the home shard.
*/
- void preaccept(TxnId txnId, boolean isProgressShard, boolean isHomeShard);
+ void preaccept(Command command, boolean isProgressShard, boolean isHomeShard);
/**
* Has been accepted
@@ -78,7 +79,7 @@ public interface ProgressLog
* A home shard should monitor this transaction for global progress.
* A non-home shard can safely ignore this transaction, as it has been witnessed by a majority of the home shard.
*/
- void accept(TxnId txnId, boolean isProgressShard, boolean isHomeShard);
+ void accept(Command command, boolean isProgressShard, boolean isHomeShard);
/**
* Has committed
@@ -86,7 +87,7 @@ public interface ProgressLog
* A home shard should monitor this transaction for global progress.
* A non-home shard can safely ignore this transaction, as it has been witnessed by a majority of the home shard.
*/
- void commit(TxnId txnId, boolean isProgressShard, boolean isHomeShard);
+ void commit(Command command, boolean isProgressShard, boolean isHomeShard);
/**
* The transaction is waiting to make progress, as all local dependencies have applied.
@@ -94,7 +95,7 @@ public interface ProgressLog
* A home shard should monitor this transaction for global progress.
* A non-home shard can safely ignore this transaction, as it has been witnessed by a majority of the home shard.
*/
- void readyToExecute(TxnId txnId, boolean isProgressShard, boolean isHomeShard);
+ void readyToExecute(Command command, boolean isProgressShard, boolean isHomeShard);
/**
* The transaction's outcome has been durably recorded (but not necessarily applied) locally.
@@ -105,12 +106,12 @@ public interface ProgressLog
*
* May also permit aborting a pending waitingOn-triggered event.
*/
- void execute(TxnId txnId, boolean isProgressShard, boolean isHomeShard);
+ void execute(Command command, boolean isProgressShard, boolean isHomeShard);
/**
* The transaction has been durably invalidated
*/
- void invalidate(TxnId txnId, boolean isProgressShard, boolean isHomeShard);
+ void invalidate(Command command, boolean isProgressShard, boolean isHomeShard);
/**
* The transaction's outcome has been durably recorded (but not necessarily applied) at a quorum of all shards.
@@ -120,7 +121,7 @@ public interface ProgressLog
*
* Otherwise, this transaction no longer needs to be monitored by either home or non-home shards.
*/
- void executedOnAllShards(TxnId txnId, Set<Id> persistedOn);
+ void executedOnAllShards(Command command, Set<Id> persistedOn);
/**
* The parameter is a command that some other command's execution is most proximally blocked by.
@@ -138,5 +139,5 @@ public interface ProgressLog
*
* In all other scenarios, the implementation is free to choose its course of action.
*/
- void waiting(TxnId blockedBy, @Nullable Keys someKeys);
+ void waiting(PartialCommand command, @Nullable Keys someKeys);
}
diff --git a/accord-core/src/main/java/accord/api/Read.java b/accord-core/src/main/java/accord/api/Read.java
index ef2f268..58a608b 100644
--- a/accord-core/src/main/java/accord/api/Read.java
+++ b/accord-core/src/main/java/accord/api/Read.java
@@ -20,6 +20,9 @@ package accord.api;
import accord.primitives.Keys;
import accord.primitives.Timestamp;
+import accord.local.CommandStore;
+import org.apache.cassandra.utils.concurrent.Future;
+
/**
* A read to be performed on potentially multiple shards, the inputs of which may be fed to a {@link Query}
@@ -29,5 +32,5 @@ import accord.primitives.Timestamp;
public interface Read
{
Keys keys();
- Data read(Key key, Timestamp executeAt, DataStore store);
+ Future<Data> read(Key key, boolean forWriteTxn, CommandStore commandStore, Timestamp executeAt, DataStore store);
}
diff --git a/accord-core/src/main/java/accord/api/Write.java b/accord-core/src/main/java/accord/api/Write.java
index 5ef43ee..35cb871 100644
--- a/accord-core/src/main/java/accord/api/Write.java
+++ b/accord-core/src/main/java/accord/api/Write.java
@@ -19,6 +19,8 @@
package accord.api;
import accord.primitives.Timestamp;
+import accord.local.CommandStore;
+import org.apache.cassandra.utils.concurrent.Future;
/**
* A collection of data to write to one or more stores
@@ -27,5 +29,5 @@ import accord.primitives.Timestamp;
*/
public interface Write
{
- void apply(Key key, Timestamp executeAt, DataStore store);
+ Future<Void> apply(Key key, CommandStore commandStore, Timestamp executeAt, DataStore store);
}
diff --git a/accord-core/src/main/java/accord/coordinate/CheckOnCommitted.java b/accord-core/src/main/java/accord/coordinate/CheckOnCommitted.java
index 1f53ddd..c931765 100644
--- a/accord-core/src/main/java/accord/coordinate/CheckOnCommitted.java
+++ b/accord-core/src/main/java/accord/coordinate/CheckOnCommitted.java
@@ -21,10 +21,14 @@ package accord.coordinate;
import accord.api.Key;
import accord.local.Command;
import accord.local.Node;
+import accord.local.PreLoadContext;
import accord.messages.CheckStatus.CheckStatusOkFull;
import accord.messages.CheckStatus.IncludeInfo;
import accord.topology.Shard;
import accord.primitives.TxnId;
+import com.google.common.collect.Iterables;
+
+import java.util.Collections;
import static accord.local.Status.Executed;
@@ -71,24 +75,25 @@ public class CheckOnCommitted extends CheckShardStatus<CheckStatusOkFull>
return;
}
- Key progressKey = node.trySelectProgressKey(txnId, max.txn.keys, max.homeKey);
+ Key progressKey = node.trySelectProgressKey(txnId, max.txn.keys(), max.homeKey);
+ PreLoadContext loadContext = PreLoadContext.contextFor(Iterables.concat(Collections.singleton(txnId), max.deps.txnIds()), max.txn.keys());
switch (max.status)
{
default: throw new IllegalStateException();
case Executed:
case Applied:
- node.forEachLocalSince(max.txn.keys, max.executeAt.epoch, commandStore -> {
+ node.forEachLocalSince(loadContext, max.txn.keys(), max.executeAt.epoch, commandStore -> {
Command command = commandStore.command(txnId);
command.apply(max.txn, max.homeKey, progressKey, max.executeAt, max.deps, max.writes, max.result);
});
- node.forEachLocal(max.txn.keys, txnId.epoch, max.executeAt.epoch - 1, commandStore -> {
+ node.forEachLocal(loadContext, max.txn.keys(), txnId.epoch, max.executeAt.epoch - 1, commandStore -> {
Command command = commandStore.command(txnId);
command.commit(max.txn, max.homeKey, progressKey, max.executeAt, max.deps);
});
break;
case Committed:
case ReadyToExecute:
- node.forEachLocalSince(max.txn.keys, txnId.epoch, commandStore -> {
+ node.forEachLocalSince(loadContext, max.txn.keys(), txnId.epoch, commandStore -> {
Command command = commandStore.command(txnId);
command.commit(max.txn, max.homeKey, progressKey, max.executeAt, max.deps);
});
diff --git a/accord-core/src/main/java/accord/coordinate/CheckOnUncommitted.java b/accord-core/src/main/java/accord/coordinate/CheckOnUncommitted.java
index d2d0343..c87f5b1 100644
--- a/accord-core/src/main/java/accord/coordinate/CheckOnUncommitted.java
+++ b/accord-core/src/main/java/accord/coordinate/CheckOnUncommitted.java
@@ -21,6 +21,7 @@ package accord.coordinate;
import accord.api.Key;
import accord.local.Command;
import accord.local.Node;
+import accord.local.PreLoadContext;
import accord.messages.CheckStatus.CheckStatusOkFull;
import accord.topology.Shard;
import accord.primitives.Keys;
@@ -68,7 +69,7 @@ public class CheckOnUncommitted extends CheckOnCommitted
{
default: throw new IllegalStateException();
case Invalidated:
- node.forEachLocalSince(someKeys, txnId.epoch, commandStore -> {
+ node.forEachLocalSince(PreLoadContext.contextFor(txnId), someKeys, txnId.epoch, commandStore -> {
Command command = commandStore.ifPresent(txnId);
if (command != null)
command.commitInvalidate();
@@ -78,7 +79,7 @@ public class CheckOnUncommitted extends CheckOnCommitted
break;
case PreAccepted:
case Accepted:
- node.forEachLocalSince(full.txn.keys, txnId.epoch, commandStore -> {
+ node.forEachLocalSince(PreLoadContext.contextFor(txnId), full.txn.keys(), txnId.epoch, commandStore -> {
Command command = commandStore.ifPresent(txnId);
if (command != null)
command.homeKey(full.homeKey);
diff --git a/accord-core/src/main/java/accord/coordinate/Execute.java b/accord-core/src/main/java/accord/coordinate/Execute.java
index ddfb1b5..6780436 100644
--- a/accord-core/src/main/java/accord/coordinate/Execute.java
+++ b/accord-core/src/main/java/accord/coordinate/Execute.java
@@ -37,6 +37,7 @@ import accord.local.Node.Id;
import accord.messages.Commit;
import accord.messages.ReadData;
import accord.messages.ReadData.ReadOk;
+import com.google.common.base.Preconditions;
class Execute implements Callback<ReadReply>
{
@@ -61,7 +62,7 @@ class Execute implements Callback<ReadReply>
this.executeAt = executeAt;
this.deps = deps;
this.topologies = node.topology().forEpoch(txn, executeAt.epoch);
- Topologies readTopologies = node.topology().forEpoch(txn.read.keys(), executeAt.epoch);
+ Topologies readTopologies = node.topology().forEpoch(txn.read().keys(), executeAt.epoch);
this.readTracker = new ReadTracker(readTopologies);
this.callback = callback;
}
@@ -70,6 +71,12 @@ class Execute implements Callback<ReadReply>
{
Set<Id> readSet = readTracker.computeMinimalReadSetAndMarkInflight();
Commit.commitAndRead(node, topologies, txnId, txn, homeKey, executeAt, deps, readSet, this);
+ // skip straight to persistence if there's no read
+ if (txn.read().keys().isEmpty())
+ {
+ Preconditions.checkState(readTracker.hasCompletedRead());
+ persist();
+ }
}
public static void execute(Node node, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, BiConsumer<Result, Throwable> callback)
@@ -78,15 +85,20 @@ class Execute implements Callback<ReadReply>
execute.start();
}
+ private void persist()
+ {
+ isDone = true;
+ Result result = txn.result(data);
+ callback.accept(result, null);
+ Persist.persist(node, topologies, txnId, homeKey, txn, executeAt, deps, txn.execute(executeAt, data), result);
+ }
+
@Override
public void onSuccess(Id from, ReadReply reply)
{
if (isDone)
return;
- if (!reply.isFinal())
- return;
-
if (!reply.isOK())
{
isDone = true;
@@ -100,12 +112,7 @@ class Execute implements Callback<ReadReply>
readTracker.recordReadSuccess(from);
if (readTracker.hasCompletedRead())
- {
- isDone = true;
- Result result = txn.result(data);
- callback.accept(result, null);
- Persist.persist(node, topologies, txnId, homeKey, txn, executeAt, deps, txn.execute(executeAt, data), result);
- }
+ persist();
}
@Override
@@ -113,7 +120,7 @@ class Execute implements Callback<ReadReply>
{
Set<Id> readFrom = readTracker.computeMinimalReadSetAndMarkInflight();
if (readFrom != null)
- node.send(readFrom, to -> new ReadData(to, readTracker.topologies(), txnId, txn, homeKey, executeAt), this);
+ node.send(readFrom, to -> new ReadData(to, readTracker.topologies(), txnId, txn, deps, homeKey, executeAt), this);
}
@Override
@@ -133,7 +140,7 @@ class Execute implements Callback<ReadReply>
Set<Id> readFrom = readTracker.computeMinimalReadSetAndMarkInflight();
if (readFrom != null)
{
- node.send(readFrom, to -> new ReadData(to, readTracker.topologies(), txnId, txn, homeKey, executeAt), this);
+ node.send(readFrom, to -> new ReadData(to, readTracker.topologies(), txnId, txn, deps, homeKey, executeAt), this);
}
else if (readTracker.hasFailed())
{
diff --git a/accord-core/src/main/java/accord/coordinate/Invalidate.java b/accord-core/src/main/java/accord/coordinate/Invalidate.java
index 3d44d4d..1f4e668 100644
--- a/accord-core/src/main/java/accord/coordinate/Invalidate.java
+++ b/accord-core/src/main/java/accord/coordinate/Invalidate.java
@@ -30,6 +30,7 @@ import accord.coordinate.Invalidate.Outcome;
import accord.coordinate.tracking.AbstractQuorumTracker.QuorumShardTracker;
import accord.local.Node;
import accord.local.Node.Id;
+import accord.local.PreLoadContext;
import accord.messages.BeginInvalidate;
import accord.messages.BeginInvalidate.InvalidateNack;
import accord.messages.BeginInvalidate.InvalidateOk;
@@ -90,9 +91,9 @@ public class Invalidate extends AsyncFuture<Outcome> implements Callback<Recover
InvalidateNack nack = (InvalidateNack) response;
if (nack.homeKey != null && nack.txn != null)
{
- Key progressKey = node.trySelectProgressKey(txnId.epoch, nack.txn.keys, nack.homeKey);
+ Key progressKey = node.trySelectProgressKey(txnId.epoch, nack.txn.keys(), nack.homeKey);
// TODO: consider limiting epoch upper bound we process this status for
- node.ifLocalSince(someKey, txnId, instance -> {
+ node.ifLocalSince(PreLoadContext.contextFor(txnId, nack.txn.keys()), someKey, txnId, instance -> {
instance.command(txnId).preaccept(nack.txn, nack.homeKey, progressKey);
return null;
});
@@ -100,7 +101,7 @@ public class Invalidate extends AsyncFuture<Outcome> implements Callback<Recover
else if (nack.homeKey != null)
{
// TODO: consider limiting epoch upper bound we process this status for
- node.ifLocalSince(someKey, txnId, instance -> {
+ node.ifLocalSince(PreLoadContext.contextFor(txnId), someKey, txnId, instance -> {
instance.command(txnId).homeKey(nack.homeKey);
return null;
});
@@ -172,7 +173,7 @@ public class Invalidate extends AsyncFuture<Outcome> implements Callback<Recover
try
{
Commit.commitInvalidate(node, txnId, someKeys, txnId);
- node.forEachLocalSince(someKeys, txnId, instance -> {
+ node.forEachLocalSince(PreLoadContext.contextFor(txnId, someKeys), someKeys, txnId, instance -> {
instance.command(txnId).commitInvalidate();
});
}
diff --git a/accord-core/src/main/java/accord/coordinate/Recover.java b/accord-core/src/main/java/accord/coordinate/Recover.java
index 2ca4572..397cad3 100644
--- a/accord-core/src/main/java/accord/coordinate/Recover.java
+++ b/accord-core/src/main/java/accord/coordinate/Recover.java
@@ -254,7 +254,7 @@ public class Recover extends AsyncFuture<Result> implements Callback<RecoverRepl
else
{
tryFailure(new Invalidated());
- Commit.commitInvalidate(node, txnId, txn.keys, recoverOks.stream().map(ok -> ok.executeAt).reduce(txnId, Timestamp::max));
+ Commit.commitInvalidate(node, txnId, txn.keys(), recoverOks.stream().map(ok -> ok.executeAt).reduce(txnId, Timestamp::max));
}
});
return;
@@ -270,7 +270,7 @@ public class Recover extends AsyncFuture<Result> implements Callback<RecoverRepl
case Invalidated:
Timestamp invalidateUntil = recoverOks.stream().map(ok -> ok.executeAt).reduce(txnId, Timestamp::max);
node.withEpoch(invalidateUntil.epoch, () -> {
- Commit.commitInvalidate(node, txnId, txn.keys, invalidateUntil);
+ Commit.commitInvalidate(node, txnId, txn.keys(), invalidateUntil);
});
tryFailure(new Invalidated());
return;
diff --git a/accord-core/src/main/java/accord/impl/InMemoryCommand.java b/accord-core/src/main/java/accord/impl/InMemoryCommand.java
new file mode 100644
index 0000000..7a5beb4
--- /dev/null
+++ b/accord-core/src/main/java/accord/impl/InMemoryCommand.java
@@ -0,0 +1,314 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 accord.impl;
+
+import accord.api.Key;
+import accord.api.Result;
+import accord.local.*;
+import accord.primitives.Ballot;
+import accord.primitives.Deps;
+import accord.primitives.Timestamp;
+import accord.primitives.TxnId;
+import accord.txn.*;
+
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.TreeMap;
+
+import static accord.local.Status.NotWitnessed;
+
+public class InMemoryCommand extends Command
+{
+ public final CommandStore commandStore;
+ private final TxnId txnId;
+
+ private Key homeKey, progressKey;
+ private Txn txn;
+ private Ballot promised = Ballot.ZERO, accepted = Ballot.ZERO;
+ private Timestamp executeAt;
+ private Deps deps = Deps.NONE;
+ private Writes writes;
+ private Result result;
+
+ private Status status = NotWitnessed;
+
+ private boolean isGloballyPersistent; // only set on home shard
+
+ private NavigableMap<TxnId, PartialCommand> waitingOnCommit;
+ private NavigableMap<TxnId, PartialCommand> waitingOnApply;
+
+ private final Listeners listeners = new Listeners();
+
+ public InMemoryCommand(CommandStore commandStore, TxnId txnId)
+ {
+ this.commandStore = commandStore;
+ this.txnId = txnId;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ InMemoryCommand command = (InMemoryCommand) o;
+ return commandStore == command.commandStore
+ && txnId.equals(command.txnId)
+ && Objects.equals(homeKey, command.homeKey)
+ && Objects.equals(progressKey, command.progressKey)
+ && Objects.equals(txn, command.txn)
+ && promised.equals(command.promised)
+ && accepted.equals(command.accepted)
+ && Objects.equals(executeAt, command.executeAt)
+ && deps.equals(command.deps)
+ && Objects.equals(writes, command.writes)
+ && Objects.equals(result, command.result)
+ && status == command.status
+ && isGloballyPersistent == command.isGloballyPersistent
+ && Objects.equals(waitingOnCommit, command.waitingOnCommit)
+ && Objects.equals(waitingOnApply, command.waitingOnApply)
+ && Objects.equals(listeners, command.listeners);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(commandStore, txnId, txn, promised, accepted, executeAt, deps, writes, result, status, waitingOnCommit, waitingOnApply, listeners);
+ }
+
+ @Override
+ public TxnId txnId()
+ {
+ return txnId;
+ }
+
+ @Override
+ public CommandStore commandStore()
+ {
+ return commandStore;
+ }
+
+ @Override
+ public Key homeKey()
+ {
+ return homeKey;
+ }
+
+ @Override
+ protected void setHomeKey(Key key)
+ {
+ this.homeKey = key;
+ }
+
+ @Override
+ public Key progressKey()
+ {
+ return progressKey;
+ }
+
+ @Override
+ protected void setProgressKey(Key key)
+ {
+ this.progressKey = key;
+ }
+
+ @Override
+ public Txn txn()
+ {
+ return txn;
+ }
+
+ @Override
+ protected void setTxn(Txn txn)
+ {
+ this.txn = txn;
+ }
+
+ @Override
+ public Ballot promised()
+ {
+ return promised;
+ }
+
+ @Override
+ public void promised(Ballot ballot)
+ {
+ this.promised = ballot;
+ }
+
+ @Override
+ public Ballot accepted()
+ {
+ return accepted;
+ }
+
+ @Override
+ public void accepted(Ballot ballot)
+ {
+ this.accepted = ballot;
+ }
+
+ @Override
+ public Timestamp executeAt()
+ {
+ return executeAt;
+ }
+
+ @Override
+ public void executeAt(Timestamp timestamp)
+ {
+ this.executeAt = timestamp;
+ }
+
+ @Override
+ public Deps savedDeps()
+ {
+ return deps;
+ }
+
+ @Override
+ public void savedDeps(Deps deps)
+ {
+ this.deps = deps;
+ }
+
+ @Override
+ public Writes writes()
+ {
+ return writes;
+ }
+
+ @Override
+ public void writes(Writes writes)
+ {
+ this.writes = writes;
+ }
+
+ @Override
+ public Result result()
+ {
+ return result;
+ }
+
+ @Override
+ public void result(Result result)
+ {
+ this.result = result;
+ }
+
+ @Override
+ public Status status()
+ {
+ return status;
+ }
+
+ @Override
+ public void status(Status status)
+ {
+ this.status = status;
+ }
+
+ @Override
+ public boolean isGloballyPersistent()
+ {
+ return isGloballyPersistent;
+ }
+
+ @Override
+ public void isGloballyPersistent(boolean v)
+ {
+ isGloballyPersistent = v;
+ }
+
+ @Override
+ public Command addListener(Listener listener)
+ {
+ listeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public void removeListener(Listener listener)
+ {
+ listeners.remove(listener);
+ }
+
+ @Override
+ public void notifyListeners()
+ {
+ listeners.forEach(this);
+ }
+
+ @Override
+ public void addWaitingOnCommit(Command command)
+ {
+ if (waitingOnCommit == null)
+ waitingOnCommit = new TreeMap<>();
+
+ waitingOnCommit.put(command.txnId(), command);
+ }
+
+ @Override
+ public boolean isWaitingOnCommit()
+ {
+ return waitingOnCommit != null && !waitingOnCommit.isEmpty();
+ }
+
+ @Override
+ public void removeWaitingOnCommit(PartialCommand command)
+ {
+ if (waitingOnCommit == null)
+ return;
+ waitingOnCommit.remove(command.txnId());
+ }
+
+ @Override
+ public PartialCommand firstWaitingOnCommit()
+ {
+ return isWaitingOnCommit() ? waitingOnCommit.firstEntry().getValue() : null;
+ }
+
+ @Override
+ public void addWaitingOnApplyIfAbsent(PartialCommand command)
+ {
+ if (waitingOnApply == null)
+ waitingOnApply = new TreeMap<>();
+
+ waitingOnApply.putIfAbsent(command.txnId(), command);
+ }
+
+ @Override
+ public boolean isWaitingOnApply()
+ {
+ return waitingOnApply != null && !waitingOnApply.isEmpty();
+ }
+
+ @Override
+ public void removeWaitingOnApply(PartialCommand command)
+ {
+ if (waitingOnApply == null)
+ return;
+ waitingOnApply.remove(command.txnId());
+ }
+
+ @Override
+ public PartialCommand firstWaitingOnApply()
+ {
+ return isWaitingOnApply() ? waitingOnApply.firstEntry().getValue() : null;
+ }
+}
diff --git a/accord-core/src/main/java/accord/local/CommandStore.java b/accord-core/src/main/java/accord/impl/InMemoryCommandStore.java
similarity index 55%
copy from accord-core/src/main/java/accord/local/CommandStore.java
copy to accord-core/src/main/java/accord/impl/InMemoryCommandStore.java
index 06b934a..54cdd0a 100644
--- a/accord-core/src/main/java/accord/local/CommandStore.java
+++ b/accord-core/src/main/java/accord/impl/InMemoryCommandStore.java
@@ -16,96 +16,61 @@
* limitations under the License.
*/
-package accord.local;
+package accord.impl;
import accord.api.Agent;
+import accord.api.DataStore;
import accord.api.Key;
-import accord.local.CommandStores.ShardedRanges;
import accord.api.ProgressLog;
+import accord.local.Command;
+import accord.local.CommandStore;
+import accord.local.CommandsForKey;
+import accord.local.Node;
import accord.primitives.KeyRange;
-import accord.api.DataStore;
import accord.primitives.KeyRanges;
-import accord.primitives.Keys;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
+import accord.local.*;
import org.apache.cassandra.utils.concurrent.AsyncPromise;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.concurrent.Promise;
-import com.google.common.base.Preconditions;
-
-import java.util.*;
-import java.util.concurrent.*;
+import java.util.Collection;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.LongSupplier;
+import java.util.stream.Collectors;
-import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
-
-/**
- * Single threaded internal shard of accord transaction metadata
- */
-public abstract class CommandStore
+public abstract class InMemoryCommandStore extends CommandStore
{
- public interface Factory
- {
- CommandStore create(int generation,
- int shardIndex,
- int numShards,
- Node node,
- Agent agent,
- DataStore store,
- ProgressLog.Factory progressLogFactory,
- RangesForEpoch rangesForEpoch);
- }
+ private final NavigableMap<TxnId, Command> commands = new TreeMap<>();
+ private final NavigableMap<Key, CommandsForKey> commandsForKey = new TreeMap<>();
- public interface RangesForEpoch
+ public static InMemoryCommandStore inMemory(CommandStore commandStore)
{
- KeyRanges at(long epoch);
- KeyRanges since(long epoch);
- boolean intersects(long epoch, Keys keys);
+ return (InMemoryCommandStore) commandStore;
}
- private final int generation;
- private final int shardIndex;
- private final int numShards;
- private final Node node;
- private final Agent agent;
- private final DataStore store;
- private final ProgressLog progressLog;
- private final RangesForEpoch rangesForEpoch;
-
- private final NavigableMap<TxnId, Command> commands = new TreeMap<>();
- private final NavigableMap<Key, CommandsForKey> commandsForKey = new TreeMap<>();
-
- public CommandStore(int generation,
- int shardIndex,
- int numShards,
- Node node,
- Agent agent,
- DataStore store,
- ProgressLog.Factory progressLogFactory,
- RangesForEpoch rangesForEpoch)
+ public InMemoryCommandStore(int generation, int index, int numShards, Function<Timestamp, Timestamp> uniqueNow, LongSupplier currentEpoch, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory, RangesForEpoch rangesForEpoch)
{
- Preconditions.checkArgument(shardIndex < numShards);
- this.generation = generation;
- this.shardIndex = shardIndex;
- this.numShards = numShards;
- this.node = node;
- this.agent = agent;
- this.store = store;
- this.progressLog = progressLogFactory.create(this);
- this.rangesForEpoch = rangesForEpoch;
+ super(generation, index, numShards, uniqueNow, currentEpoch, agent, store, progressLogFactory, rangesForEpoch);
}
+ @Override
public Command ifPresent(TxnId txnId)
{
return commands.get(txnId);
}
+ @Override
public Command command(TxnId txnId)
{
- return commands.computeIfAbsent(txnId, id -> new Command(this, id));
+ return commands.computeIfAbsent(txnId, id -> new InMemoryCommand(this, id));
}
public boolean hasCommand(TxnId txnId)
@@ -113,14 +78,10 @@ public abstract class CommandStore
return commands.containsKey(txnId);
}
+ @Override
public CommandsForKey commandsForKey(Key key)
{
- return commandsForKey.computeIfAbsent(key, ignore -> new CommandsForKey());
- }
-
- public CommandsForKey maybeCommandsForKey(Key key)
- {
- return commandsForKey.get(key);
+ return commandsForKey.computeIfAbsent(key, InMemoryCommandsForKey::new);
}
public boolean hasCommandsForKey(Key key)
@@ -128,50 +89,10 @@ public abstract class CommandStore
return commandsForKey.containsKey(key);
}
- public DataStore store()
- {
- return store;
- }
-
- public Timestamp uniqueNow(Timestamp atLeast)
- {
- return node.uniqueNow(atLeast);
- }
-
- public Agent agent()
- {
- return agent;
- }
-
- public ProgressLog progressLog()
- {
- return progressLog;
- }
-
- public Node node()
- {
- return node;
- }
-
- public RangesForEpoch ranges()
- {
- return rangesForEpoch;
- }
-
- public long latestEpoch()
- {
- // TODO: why not inject the epoch to each command store?
- return node.epoch();
- }
-
- protected Timestamp maxConflict(Keys keys)
+ @Override
+ public CommandsForKey maybeCommandsForKey(Key key)
{
- return keys.stream()
- .map(this::maybeCommandsForKey)
- .filter(Objects::nonNull)
- .map(CommandsForKey::max)
- .max(Comparator.naturalOrder())
- .orElse(Timestamp.NONE);
+ return commandsForKey.get(key);
}
public void forEpochCommands(KeyRanges ranges, long epoch, Consumer<Command> consumer)
@@ -186,7 +107,7 @@ public abstract class CommandStore
range.endInclusive()).values();
for (CommandsForKey commands : rangeCommands)
{
- commands.forWitnessed(minTimestamp, maxTimestamp, consumer);
+ commands.forWitnessed(minTimestamp, maxTimestamp, cmd -> consumer.accept((Command) cmd));
}
}
}
@@ -204,36 +125,27 @@ public abstract class CommandStore
for (CommandsForKey commands : rangeCommands)
{
- Collection<Command> committed = commands.committedByExecuteAt.subMap(minTimestamp,
- true,
- maxTimestamp,
- true).values();
+ Collection<Command> committed = commands.committedByExecuteAt()
+ .between(minTimestamp, maxTimestamp).map(cmd -> (Command) cmd).collect(Collectors.toList());
committed.forEach(consumer);
}
}
}
- public boolean hashIntersects(Key key)
- {
- return ShardedRanges.keyIndex(key, numShards) == shardIndex;
- }
-
- public boolean intersects(Keys keys, KeyRanges ranges)
+ protected void processInternal(Consumer<? super CommandStore> consumer, Promise<Void> promise)
{
- return keys.any(ranges, this::hashIntersects);
+ processInternal(cs -> {
+ consumer.accept(cs);
+ return null;
+ }, promise);
}
- public static void onEach(Collection<CommandStore> stores, Consumer<? super CommandStore> consumer)
- {
- for (CommandStore store : stores)
- store.process(consumer);
- }
-
- <R> void processInternal(Function<? super CommandStore, R> function, Promise<R> promise)
+ protected <T> void processInternal(Function<? super CommandStore, T> function, Promise<T> promise)
{
try
{
- promise.setSuccess(function.apply(this));
+ T result = function.apply(this);
+ promise.setSuccess(result);
}
catch (Throwable e)
{
@@ -241,61 +153,47 @@ public abstract class CommandStore
}
}
- void processInternal(Consumer<? super CommandStore> consumer, Promise<Void> promise)
+ public static class Synchronized extends InMemoryCommandStore
{
- try
- {
- consumer.accept(this);
- promise.setSuccess(null);
- }
- catch (Throwable e)
+ public Synchronized(int generation,
+ int index,
+ int numShards,
+ Function<Timestamp, Timestamp> uniqueNow,
+ LongSupplier currentEpoch,
+ Agent agent,
+ DataStore store,
+ ProgressLog.Factory progressLogFactory,
+ RangesForEpoch rangesForEpoch)
{
- promise.tryFailure(e);
+ super(generation, index, numShards, uniqueNow, currentEpoch, agent, store, progressLogFactory, rangesForEpoch);
}
- }
-
- public abstract Future<Void> process(Consumer<? super CommandStore> consumer);
- public abstract <T> Future<T> process(Function<? super CommandStore, T> function);
-
- public void processBlocking(Consumer<? super CommandStore> consumer)
- {
- try
- {
- process(consumer).get();
- }
- catch (InterruptedException e)
- {
- throw new UncheckedInterruptedException(e);
- }
- catch (ExecutionException e)
+ @Override
+ public synchronized Future<Void> processSetup(Consumer<? super CommandStore> function)
{
- throw new RuntimeException(e.getCause());
+ AsyncPromise<Void> promise = new AsyncPromise<>();
+ processInternal(function, promise);
+ return promise;
}
- }
- public abstract void shutdown();
-
- public static class Synchronized extends CommandStore
- {
- public Synchronized(int generation, int index, int numShards, Node node,
- Agent agent, DataStore store,
- ProgressLog.Factory progressLogFactory,
- RangesForEpoch rangesForEpoch)
+ @Override
+ public synchronized <T> Future<T> processSetup(Function<? super CommandStore, T> function)
{
- super(generation, index, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
+ AsyncPromise<T> promise = new AsyncPromise<>();
+ processInternal(function, promise);
+ return promise;
}
@Override
- public synchronized Future<Void> process(Consumer<? super CommandStore> consumer)
+ public synchronized Future<Void> process(PreLoadContext context, Consumer<? super CommandStore> consumer)
{
- AsyncPromise<Void> promise = new AsyncPromise<>();
+ Promise<Void> promise = new AsyncPromise<>();
processInternal(consumer, promise);
return promise;
}
@Override
- public <T> Future<T> process(Function<? super CommandStore, T> function)
+ public synchronized <T> Future<T> process(PreLoadContext context, Function<? super CommandStore, T> function)
{
AsyncPromise<T> promise = new AsyncPromise<>();
processInternal(function, promise);
@@ -303,10 +201,10 @@ public abstract class CommandStore
}
@Override
- public void shutdown() {}
+ public synchronized void shutdown() {}
}
- public static class SingleThread extends CommandStore
+ public static class SingleThread extends InMemoryCommandStore
{
private final ExecutorService executor;
@@ -342,20 +240,43 @@ public abstract class CommandStore
}
}
- public SingleThread(int generation, int index, int numShards, Node node,
- Agent agent, DataStore store, ProgressLog.Factory progressLogFactory,
+ public SingleThread(int generation,
+ int index,
+ int numShards,
+ Node.Id nodeId,
+ Function<Timestamp, Timestamp> uniqueNow,
+ LongSupplier currentEpoch,
+ Agent agent,
+ DataStore store,
+ ProgressLog.Factory progressLogFactory,
RangesForEpoch rangesForEpoch)
{
- super(generation, index, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
+ super(generation, index, numShards, uniqueNow, currentEpoch, agent, store, progressLogFactory, rangesForEpoch);
executor = Executors.newSingleThreadExecutor(r -> {
Thread thread = new Thread(r);
- thread.setName(CommandStore.class.getSimpleName() + '[' + node.id() + ':' + index + ']');
+ thread.setName(CommandStore.class.getSimpleName() + '[' + nodeId + ':' + index + ']');
return thread;
});
}
@Override
- public Future<Void> process(Consumer<? super CommandStore> consumer)
+ public Future<Void> processSetup(Consumer<? super CommandStore> function)
+ {
+ ConsumerWrapper future = new ConsumerWrapper(function);
+ executor.execute(future);
+ return future;
+ }
+
+ @Override
+ public <T> Future<T> processSetup(Function<? super CommandStore, T> function)
+ {
+ FunctionWrapper<T> future = new FunctionWrapper<>(function);
+ executor.execute(future);
+ return future;
+ }
+
+ @Override
+ public Future<Void> process(PreLoadContext context, Consumer<? super CommandStore> consumer)
{
ConsumerWrapper future = new ConsumerWrapper(consumer);
executor.execute(future);
@@ -363,7 +284,7 @@ public abstract class CommandStore
}
@Override
- public <T> Future<T> process(Function<? super CommandStore, T> function)
+ public <T> Future<T> process(PreLoadContext context, Function<? super CommandStore, T> function)
{
FunctionWrapper<T> future = new FunctionWrapper<>(function);
executor.execute(future);
@@ -377,15 +298,22 @@ public abstract class CommandStore
}
}
- static class Debug extends SingleThread
+ public static class SingleThreadDebug extends SingleThread
{
private final AtomicReference<Thread> expectedThread = new AtomicReference<>();
- public Debug(int generation, int index, int numShards, Node node,
- Agent agent, DataStore store, ProgressLog.Factory progressLogFactory,
- RangesForEpoch rangesForEpoch)
+ public SingleThreadDebug(int generation,
+ int index,
+ int numShards,
+ Node.Id nodeId,
+ Function<Timestamp, Timestamp> uniqueNow,
+ LongSupplier currentEpoch,
+ Agent agent,
+ DataStore store,
+ ProgressLog.Factory progressLogFactory,
+ RangesForEpoch rangesForEpoch)
{
- super(generation, index, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
+ super(generation, index, numShards, nodeId, uniqueNow, currentEpoch, agent, store, progressLogFactory, rangesForEpoch);
}
private void assertThread()
@@ -399,7 +327,8 @@ public abstract class CommandStore
break;
expectedThread.compareAndSet(null, Thread.currentThread());
}
- Preconditions.checkState(expected == current);
+ if (expected != current)
+ throw new IllegalStateException(String.format("Command store called from the wrong thread. Expected %s, got %s", expected, current));
}
@Override
@@ -431,18 +360,10 @@ public abstract class CommandStore
}
@Override
- <R> void processInternal(Function<? super CommandStore, R> function, Promise<R> future)
+ protected void processInternal(Consumer<? super CommandStore> consumer, Promise<Void> promise)
{
assertThread();
- super.processInternal(function, future);
- }
-
- @Override
- void processInternal(Consumer<? super CommandStore> consumer, Promise<Void> future)
- {
- assertThread();
- super.processInternal(consumer, future);
+ super.processInternal(consumer, promise);
}
}
-
}
diff --git a/accord-core/src/main/java/accord/impl/InMemoryCommandStores.java b/accord-core/src/main/java/accord/impl/InMemoryCommandStores.java
new file mode 100644
index 0000000..f597ce0
--- /dev/null
+++ b/accord-core/src/main/java/accord/impl/InMemoryCommandStores.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 accord.impl;
+
+import accord.api.Agent;
+import accord.api.DataStore;
+import accord.api.ProgressLog;
+import accord.local.CommandStore;
+import accord.local.CommandStores;
+import accord.local.Node;
+import accord.primitives.Keys;
+
+import java.util.function.Consumer;
+
+import static java.lang.Boolean.FALSE;
+
+public abstract class InMemoryCommandStores extends CommandStores
+{
+ public InMemoryCommandStores(int num, Node node, Agent agent, DataStore store,
+ ProgressLog.Factory progressLogFactory)
+ {
+ super(num, node, agent, store, progressLogFactory);
+ }
+
+ public static InMemoryCommandStores inMemory(Node node)
+ {
+ return (InMemoryCommandStores) node.commandStores();
+ }
+
+ public void forEachLocal(Consumer<? super CommandStore> forEach)
+ {
+ foldl((ranges, o, minEpoch, maxEpoch) -> ranges.all(),
+ null, Long.MIN_VALUE, Long.MAX_VALUE,
+ (store, f, r, t) -> { f.accept(store); return null; }, forEach, null, ignore -> FALSE);
+ }
+
+ public void forEachLocal(Keys keys, long minEpoch, long maxEpoch, Consumer<? super CommandStore> forEach)
+ {
+ foldl(ShardedRanges::shards, keys, minEpoch, maxEpoch, (store, f, r, t) -> { f.accept(store); return null; }, forEach, null, ignore -> FALSE);
+ }
+
+ public void forEachLocal(Keys keys, long epoch, Consumer<? super CommandStore> forEach)
+ {
+ forEachLocal(keys, epoch, epoch, forEach);
+ }
+
+ public void forEachLocalSince(Keys keys, long epoch, Consumer<? super CommandStore> forEach)
+ {
+ forEachLocal(keys, epoch, Long.MAX_VALUE, forEach);
+ }
+
+ public static class Synchronized extends InMemoryCommandStores
+ {
+ public Synchronized(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory)
+ {
+ super(num, node, agent, store, progressLogFactory);
+ }
+
+ @Override
+ protected CommandStore createCommandStore(int generation, int index, int numShards, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory, CommandStore.RangesForEpoch rangesForEpoch)
+ {
+ return new InMemoryCommandStore.Synchronized(generation, index, numShards, node::uniqueNow, node.topology()::epoch, agent, store, progressLogFactory, rangesForEpoch);
+ }
+ }
+
+ public static class SingleThread extends InMemoryCommandStores
+ {
+ public SingleThread(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory)
+ {
+ super(num, node, agent, store, progressLogFactory);
+ }
+
+ @Override
+ protected CommandStore createCommandStore(int generation, int index, int numShards, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory, CommandStore.RangesForEpoch rangesForEpoch)
+ {
+ return new InMemoryCommandStore.SingleThread(generation, index, numShards, node.id(), node::uniqueNow, node.topology()::epoch, agent, store, progressLogFactory, rangesForEpoch);
+ }
+ }
+
+ public static class Debug extends InMemoryCommandStores.SingleThread
+ {
+ public Debug(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory)
+ {
+ super(num, node, agent, store, progressLogFactory);
+ }
+
+ @Override
+ protected CommandStore createCommandStore(int generation, int index, int numShards, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory, CommandStore.RangesForEpoch rangesForEpoch)
+ {
+ return new InMemoryCommandStore.SingleThreadDebug(generation, index, numShards, node.id(), node::uniqueNow, node.topology()::epoch, agent, store, progressLogFactory, rangesForEpoch);
+ }
+ }
+
+}
diff --git a/accord-core/src/main/java/accord/impl/InMemoryCommandsForKey.java b/accord-core/src/main/java/accord/impl/InMemoryCommandsForKey.java
new file mode 100644
index 0000000..b4902ef
--- /dev/null
+++ b/accord-core/src/main/java/accord/impl/InMemoryCommandsForKey.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 accord.impl;
+
+import accord.api.Key;
+import accord.local.Command;
+import accord.local.CommandsForKey;
+import accord.local.PartialCommand;
+import accord.primitives.Timestamp;
+
+import java.util.NavigableMap;
+import java.util.TreeMap;
+import java.util.stream.Stream;
+
+public class InMemoryCommandsForKey extends CommandsForKey
+{
+ static class InMemoryCommandTimeseries implements CommandTimeseries
+ {
+ private final NavigableMap<Timestamp, Command> commands = new TreeMap<>();
+
+ @Override
+ public PartialCommand.WithDeps get(Timestamp timestamp)
+ {
+ return commands.get(timestamp);
+ }
+
+ @Override
+ public void add(Timestamp timestamp, Command command)
+ {
+ if (commands.containsKey(timestamp) && !commands.get(timestamp).equals(command))
+ throw new IllegalStateException(String.format("Attempting to overwrite command at timestamp %s %s with %s.",
+ timestamp, commands.get(timestamp), command));
+
+ commands.put(timestamp, command);
+ }
+
+ @Override
+ public void remove(Timestamp timestamp)
+ {
+ commands.remove(timestamp);
+ }
+
+ @Override
+ public boolean isEmpty()
+ {
+ return commands.isEmpty();
+ }
+
+ @Override
+ public Stream<PartialCommand.WithDeps> before(Timestamp timestamp)
+ {
+ return commands.headMap(timestamp, false).values().stream().map(cmd -> cmd);
+ }
+
+ @Override
+ public Stream<PartialCommand.WithDeps> after(Timestamp timestamp)
+ {
+ return commands.tailMap(timestamp, false).values().stream().map(cmd -> cmd);
+ }
+
+ @Override
+ public Stream<PartialCommand.WithDeps> between(Timestamp min, Timestamp max)
+ {
+ return commands.subMap(min, true, max, true).values().stream().map(cmd -> cmd);
+ }
+
+ @Override
+ public Stream<PartialCommand.WithDeps> all()
+ {
+ return commands.values().stream().map(cmd -> cmd);
+ }
+ }
+
+ private final Key key;
+ private final InMemoryCommandTimeseries uncommitted = new InMemoryCommandTimeseries();
+ private final InMemoryCommandTimeseries committedById = new InMemoryCommandTimeseries();
+ private final InMemoryCommandTimeseries committedByExecuteAt = new InMemoryCommandTimeseries();
+
+ private Timestamp max = Timestamp.NONE;
+
+ public InMemoryCommandsForKey(Key key)
+ {
+ this.key = key;
+ }
+
+ @Override
+ public Key key()
+ {
+ return key;
+ }
+
+ @Override
+ public Timestamp max()
+ {
+ return max;
+ }
+
+ @Override
+ public void updateMax(Timestamp timestamp)
+ {
+ max = Timestamp.max(max, timestamp);
+ }
+
+ @Override
+ public CommandTimeseries uncommitted()
+ {
+ return uncommitted;
+ }
+
+ @Override
+ public CommandTimeseries committedById()
+ {
+ return committedById;
+ }
+
+ @Override
+ public CommandTimeseries committedByExecuteAt()
+ {
+ return committedByExecuteAt;
+ }
+}
diff --git a/accord-core/src/main/java/accord/impl/SimpleProgressLog.java b/accord-core/src/main/java/accord/impl/SimpleProgressLog.java
index fd49324..9b9f013 100644
--- a/accord-core/src/main/java/accord/impl/SimpleProgressLog.java
+++ b/accord-core/src/main/java/accord/impl/SimpleProgressLog.java
@@ -18,6 +18,7 @@
package accord.impl;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -28,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
+import accord.local.*;
import com.google.common.base.Preconditions;
import accord.api.Key;
@@ -36,11 +38,7 @@ import accord.api.Result;
import accord.coordinate.CheckOnCommitted;
import accord.coordinate.Invalidate;
import accord.impl.SimpleProgressLog.HomeState.LocalStatus;
-import accord.local.Command;
-import accord.local.CommandStore;
-import accord.local.Node;
import accord.local.Node.Id;
-import accord.local.Status;
import accord.messages.Apply;
import accord.messages.Callback;
import accord.messages.CheckStatus.CheckStatusOk;
@@ -150,7 +148,7 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
Object debugInvestigating;
- void ensureAtLeast(LocalStatus newStatus, Progress newProgress, Node node, Command command)
+ void ensureAtLeast(Command command, LocalStatus newStatus, Progress newProgress, Node node)
{
if (newStatus == Committed && global.isAtLeast(Durable) && !command.executes())
{
@@ -388,15 +386,15 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
Status blockedOn = Status.NotWitnessed;
Progress progress = NoneExpected;
Keys someKeys;
- Command blocking;
+ PartialCommand blocking;
Object debugInvestigating;
- void recordBlocking(Command blocking, Keys someKeys)
+ void recordBlocking(PartialCommand blocking, Keys someKeys)
{
this.blocking = blocking;
- if (blocking.txn() != null && !blocking.txn().keys.containsAll(someKeys))
- throw new IllegalStateException(String.format("The transaction does not involve some of the keys on which another transaction has taken a dependency upon it (%s vs %s)", blocking.txn().keys, someKeys));
+ if (blocking.txn() != null && !blocking.txn().keys().containsAll(someKeys))
+ throw new IllegalStateException(String.format("The transaction does not involve some of the keys on which another transaction has taken a dependency upon it (%s vs %s)", blocking.txn().keys(), someKeys));
switch (blocking.status())
{
@@ -501,7 +499,7 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
break;
case Committed:
case ReadyToExecute:
- Preconditions.checkState(command.hasBeen(Status.Committed) || !command.commandStore.ranges().intersects(txnId.epoch, someKeys));
+ Preconditions.checkState(command.hasBeen(Status.Committed) || !command.commandStore().ranges().intersects(txnId.epoch, someKeys));
if (blockedOn == Status.Committed)
progress = NoneExpected;
break;
@@ -528,19 +526,19 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
static class State
{
final TxnId txnId;
- final Command command;
+ final CommandStore commandStore;
HomeState homeState;
NonHomeState nonHomeState;
BlockingState blockingState;
- State(TxnId txnId, Command command)
+ State(TxnId txnId, CommandStore commandStore)
{
this.txnId = txnId;
- this.command = command;
+ this.commandStore = commandStore;
}
- void recordBlocking(Command blockedByCommand, Keys someKeys)
+ void recordBlocking(PartialCommand blockedByCommand, Keys someKeys)
{
Preconditions.checkArgument(blockedByCommand.txnId().equals(txnId));
if (blockingState == null)
@@ -561,12 +559,12 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
return homeState;
}
- void ensureAtLeast(LocalStatus newStatus, Progress newProgress, Node node)
+ void ensureAtLeast(Command command, LocalStatus newStatus, Progress newProgress, Node node)
{
- home().ensureAtLeast(newStatus, newProgress, node, command);
+ home().ensureAtLeast(command, newStatus, newProgress, node);
}
- void updateNonHome(Node node)
+ void updateNonHome(Node node, Command command)
{
switch (nonHomeState)
{
@@ -593,14 +591,18 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
void update(Node node)
{
- if (blockingState != null)
- blockingState.update(node, txnId, command);
+ PreLoadContext context = PreLoadContext.contextFor(txnId);
+ commandStore.process(context, cs -> {
+ Command command = cs.command(txnId);
+ if (blockingState != null)
+ blockingState.update(node, txnId, command);
- if (homeState != null)
- homeState.update(node, txnId, command);
+ if (homeState != null)
+ homeState.update(node, txnId, command);
- if (nonHomeState != null)
- updateNonHome(node);
+ if (nonHomeState != null)
+ updateNonHome(node, command);
+ });
}
@Override
@@ -635,7 +637,7 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
State ensure(TxnId txnId)
{
- return stateMap.computeIfAbsent(txnId, id -> new State(id, commandStore.command(id)));
+ return stateMap.computeIfAbsent(txnId, id -> new State(id, commandStore));
}
State ensure(TxnId txnId, State state)
@@ -644,12 +646,12 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
}
@Override
- public void preaccept(TxnId txnId, boolean isProgressShard, boolean isHomeShard)
+ public void preaccept(Command command, boolean isProgressShard, boolean isHomeShard)
{
if (isProgressShard)
{
- State state = ensure(txnId);
- if (isHomeShard) state.ensureAtLeast(Uncommitted, Expected, node);
+ State state = ensure(command.txnId());
+ if (isHomeShard) state.ensureAtLeast(command, Uncommitted, Expected, node);
else state.ensureAtLeast(NonHomeState.Unsafe);
}
}
@@ -670,79 +672,79 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
return state;
}
- private void ensureSafeOrAtLeast(TxnId txnId, boolean isProgressShard, boolean isHomeShard, LocalStatus newStatus, Progress newProgress)
+ private void ensureSafeOrAtLeast(Command command, boolean isProgressShard, boolean isHomeShard, LocalStatus newStatus, Progress newProgress)
{
State state = null;
assert newStatus.isAtMost(ReadyToExecute);
if (newStatus.isAtLeast(LocalStatus.Committed))
- state = recordCommit(txnId);
+ state = recordCommit(command.txnId());
if (isProgressShard)
{
- state = ensure(txnId, state);
+ state = ensure(command.txnId(), state);
- if (isHomeShard) state.ensureAtLeast(newStatus, newProgress, node);
- else ensure(txnId).ensureAtLeast(Safe);
+ if (isHomeShard) state.ensureAtLeast(command, newStatus, newProgress, node);
+ else ensure(command.txnId()).ensureAtLeast(Safe);
}
}
@Override
- public void accept(TxnId txnId, boolean isProgressShard, boolean isHomeShard)
+ public void accept(Command command, boolean isProgressShard, boolean isHomeShard)
{
- ensureSafeOrAtLeast(txnId, isProgressShard, isHomeShard, Uncommitted, Expected);
+ ensureSafeOrAtLeast(command, isProgressShard, isHomeShard, Uncommitted, Expected);
}
@Override
- public void commit(TxnId txnId, boolean isProgressShard, boolean isHomeShard)
+ public void commit(Command command, boolean isProgressShard, boolean isHomeShard)
{
- ensureSafeOrAtLeast(txnId, isProgressShard, isHomeShard, LocalStatus.Committed, NoneExpected);
+ ensureSafeOrAtLeast(command, isProgressShard, isHomeShard, LocalStatus.Committed, NoneExpected);
}
@Override
- public void readyToExecute(TxnId txnId, boolean isProgressShard, boolean isHomeShard)
+ public void readyToExecute(Command command, boolean isProgressShard, boolean isHomeShard)
{
- ensureSafeOrAtLeast(txnId, isProgressShard, isHomeShard, LocalStatus.ReadyToExecute, Expected);
+ ensureSafeOrAtLeast(command, isProgressShard, isHomeShard, LocalStatus.ReadyToExecute, Expected);
}
@Override
- public void execute(TxnId txnId, boolean isProgressShard, boolean isHomeShard)
+ public void execute(Command command, boolean isProgressShard, boolean isHomeShard)
{
- State state = recordExecute(txnId);
+ State state = recordExecute(command.txnId());
if (isProgressShard)
{
- state = ensure(txnId, state);
+ state = ensure(command.txnId(), state);
- if (isHomeShard) state.home().executed(node, state.command);
- else ensure(txnId).ensureAtLeast(Safe);
+ if (isHomeShard) state.home().executed(node, command);
+ else ensure(command.txnId()).ensureAtLeast(Safe);
}
}
@Override
- public void invalidate(TxnId txnId, boolean isProgressShard, boolean isHomeShard)
+ public void invalidate(Command command, boolean isProgressShard, boolean isHomeShard)
{
- State state = recordExecute(txnId);
+ State state = recordExecute(command.txnId());
if (isProgressShard)
{
- state = ensure(txnId, state);
+ state = ensure(command.txnId(), state);
- if (isHomeShard) state.ensureAtLeast(LocalStatus.Done, Done, node);
- else ensure(txnId).ensureAtLeast(Safe);
+ if (isHomeShard) state.ensureAtLeast(command, LocalStatus.Done, Done, node);
+ else ensure(command.txnId()).ensureAtLeast(Safe);
}
}
@Override
- public void executedOnAllShards(TxnId txnId, Set<Id> persistedOn)
+ public void executedOnAllShards(Command command, Set<Id> persistedOn)
{
- State state = ensure(txnId);
- state.home().executedOnAllShards(node, state.command, persistedOn);
+ State state = ensure(command.txnId());
+ state.home().executedOnAllShards(node, command, persistedOn);
}
@Override
- public void waiting(TxnId blockedBy, Keys someKeys)
+ public void waiting(PartialCommand blockedByCommand, Keys someKeys)
{
- Command blockedByCommand = commandStore.command(blockedBy);
+ TxnId blockedBy = blockedByCommand.txnId();
if (!blockedByCommand.hasBeen(Executed))
ensure(blockedBy).recordBlocking(blockedByCommand, someKeys);
}
@@ -756,20 +758,20 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
// TODO: we want to be able to poll others about pending dependencies to check forward progress,
// as we don't know all dependencies locally (or perhaps any, at execution time) so we may
// begin expecting forward progress too early
- instance.stateMap.values().forEach(state -> state.update(node));
+ // state map may be updated during iteration, so need to clone the values set
+ new ArrayList<>(instance.stateMap.values()).forEach(state -> state.update(node));
}
}
static class CoordinateApplyAndCheck extends AsyncFuture<Void> implements Callback<ApplyAndCheckOk>
{
final TxnId txnId;
- final Command command;
final HomeState state;
final Set<Id> waitingOnResponses;
static Future<Void> applyAndCheck(Node node, TxnId txnId, Command command, HomeState state)
{
- CoordinateApplyAndCheck coordinate = new CoordinateApplyAndCheck(txnId, command, state);
+ CoordinateApplyAndCheck coordinate = new CoordinateApplyAndCheck(txnId, state);
Topologies topologies = node.topology().preciseEpochs(command.txn(), command.executeAt().epoch);
state.globalNotPersisted.retainAll(topologies.nodes()); // we might have had some nodes from older shards that are now redundant
node.send(state.globalNotPersisted, id -> new ApplyAndCheck(id, topologies,
@@ -781,10 +783,9 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
return coordinate;
}
- CoordinateApplyAndCheck(TxnId txnId, Command command, HomeState state)
+ CoordinateApplyAndCheck(TxnId txnId, HomeState state)
{
this.txnId = txnId;
- this.command = command;
this.state = state;
this.waitingOnResponses = new HashSet<>(state.globalNotPersisted);
}
@@ -812,18 +813,33 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
public static class ApplyAndCheck extends Apply
{
- final Set<Id> notPersisted;
+ public static class SerializerSupport
+ {
+ public static ApplyAndCheck create(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, Writes writes, Result result, Set<Id> notPersisted)
+ {
+ return new ApplyAndCheck(scope, waitForEpoch, txnId, txn, homeKey, executeAt, deps, writes, result, notPersisted);
+ }
+ }
+
+ public final Set<Id> notPersisted;
ApplyAndCheck(Id id, Topologies topologies, TxnId txnId, Txn txn, Key homeKey, Deps deps, Timestamp executeAt, Writes writes, Result result, Set<Id> notPersisted)
{
super(id, topologies, txnId, txn, homeKey, executeAt, deps, writes, result);
this.notPersisted = notPersisted;
}
+ public ApplyAndCheck(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, Writes writes, Result result, Set<Id> notPersisted)
+ {
+ super(scope, waitForEpoch, txnId, txn, homeKey, executeAt, deps, writes, result);
+ this.notPersisted = notPersisted;
+ }
+
@Override
public void process(Node node, Id from, ReplyContext replyContext)
{
- Key progressKey = node.trySelectProgressKey(txnId, txn.keys, homeKey);
- node.reply(from, replyContext, node.mapReduceLocalSince(scope(), executeAt, instance -> {
+ Key progressKey = node.trySelectProgressKey(txnId, txn.keys(), homeKey);
+ PreLoadContext context = PreLoadContext.contextFor(txnId, txn.keys());
+ node.reply(from, replyContext, node.mapReduceLocalSince(context, scope(), executeAt, instance -> {
Command command = instance.command(txnId);
command.apply(txn, homeKey, progressKey, executeAt, deps, writes, result);
if (homeKey.equals(progressKey) && command.handles(txnId.epoch, progressKey))
@@ -869,9 +885,9 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
public static class ApplyAndCheckOk implements Reply
{
- final Set<Id> notPersisted;
+ public final Set<Id> notPersisted;
- ApplyAndCheckOk(Set<Id> notPersisted)
+ public ApplyAndCheckOk(Set<Id> notPersisted)
{
this.notPersisted = notPersisted;
}
diff --git a/accord-core/src/main/java/accord/local/Command.java b/accord-core/src/main/java/accord/local/Command.java
index f9e2d42..65efda4 100644
--- a/accord-core/src/main/java/accord/local/Command.java
+++ b/accord-core/src/main/java/accord/local/Command.java
@@ -18,129 +18,113 @@
package accord.local;
-import java.util.NavigableMap;
-import java.util.TreeMap;
-import java.util.function.Consumer;
-
-import com.google.common.base.Preconditions;
-
-import accord.api.Key;
-import accord.api.Result;
+import accord.api.*;
import accord.local.Node.Id;
-import accord.primitives.KeyRanges;
-import accord.primitives.Ballot;
-import accord.primitives.Deps;
-import accord.primitives.Keys;
-import accord.primitives.Timestamp;
+import accord.primitives.*;
import accord.txn.Txn;
-import accord.primitives.TxnId;
import accord.txn.Writes;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+import org.apache.cassandra.utils.concurrent.Future;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.function.Consumer;
+import java.util.function.Function;
-import static accord.local.Status.Accepted;
-import static accord.local.Status.AcceptedInvalidate;
-import static accord.local.Status.Applied;
-import static accord.local.Status.Committed;
-import static accord.local.Status.Executed;
-import static accord.local.Status.Invalidated;
-import static accord.local.Status.NotWitnessed;
-import static accord.local.Status.PreAccepted;
-import static accord.local.Status.ReadyToExecute;
-
-// TODO: this needs to be backed by persistent storage
-public class Command implements Listener, Consumer<Listener>
+import static accord.local.Status.*;
+import static accord.utils.Utils.listOf;
+
+public abstract class Command implements Listener, Consumer<Listener>, PreLoadContext, PartialCommand.WithDeps
{
- public final CommandStore commandStore;
- private final TxnId txnId;
- private Key homeKey, progressKey;
- private Txn txn; // TODO: only store this on the home shard, or split to each shard independently
- private Ballot promised = Ballot.ZERO, accepted = Ballot.ZERO;
- private Timestamp executeAt; // TODO: compress these states together
- private Deps deps = Deps.NONE;
- private Writes writes;
- private Result result;
+ private static final Logger logger = LoggerFactory.getLogger(Command.class);
- private Status status = NotWitnessed;
- private boolean isGloballyPersistent; // only set on home shard
+ public abstract CommandStore commandStore();
- private NavigableMap<TxnId, Command> waitingOnCommit;
- private NavigableMap<Timestamp, Command> waitingOnApply;
+ public abstract Key homeKey();
- private final Listeners listeners = new Listeners();
+ protected abstract void setHomeKey(Key key);
- public Command(CommandStore commandStore, TxnId id)
- {
- this.commandStore = commandStore;
- this.txnId = id;
- }
+ public abstract Key progressKey();
- public TxnId txnId()
- {
- return txnId;
- }
+ protected abstract void setProgressKey(Key key);
- public Txn txn()
- {
- return txn;
- }
+ protected abstract void setTxn(Txn txn);
- public Ballot promised()
- {
- return promised;
- }
+ public abstract Ballot promised();
- public Ballot accepted()
- {
- return accepted;
- }
+ public abstract void promised(Ballot ballot);
- public Timestamp executeAt()
- {
- return executeAt;
- }
+ public abstract Ballot accepted();
- public Deps savedDeps()
- {
- return deps;
- }
+ public abstract void accepted(Ballot ballot);
- public Writes writes()
- {
- return writes;
- }
+ public abstract void executeAt(Timestamp timestamp);
- public Result result()
- {
- return result;
- }
+ public abstract Deps savedDeps();
- public Status status()
- {
- return status;
- }
+ public abstract void savedDeps(Deps deps);
- public boolean hasBeen(Status status)
+ public abstract Writes writes();
+
+ public abstract void writes(Writes writes);
+
+ public abstract Result result();
+
+ public abstract void result(Result result);
+
+ public abstract void status(Status status);
+
+ public abstract boolean isGloballyPersistent();
+
+ public abstract void isGloballyPersistent(boolean v);
+
+ public abstract Command addListener(Listener listener);
+
+ public abstract void notifyListeners();
+
+ public abstract void addWaitingOnCommit(Command command);
+
+ public abstract boolean isWaitingOnCommit();
+
+ public abstract void removeWaitingOnCommit(PartialCommand command);
+
+ public abstract PartialCommand firstWaitingOnCommit();
+
+ public abstract void addWaitingOnApplyIfAbsent(PartialCommand command);
+
+ public abstract boolean isWaitingOnApply();
+
+ public abstract void removeWaitingOnApply(PartialCommand command);
+
+ public abstract PartialCommand firstWaitingOnApply();
+
+ public boolean isUnableToApply()
{
- return this.status.compareTo(status) >= 0;
+ return isWaitingOnCommit() || isWaitingOnApply();
}
- public boolean is(Status status)
+ @Override
+ public Iterable<TxnId> txnIds()
{
- return this.status == status;
+ return Iterables.concat(Collections.singleton(txnId()), savedDeps().txnIds());
}
- public boolean isGloballyPersistent()
+ @Override
+ public Iterable<Key> keys()
{
- return isGloballyPersistent;
+ return txn().keys();
}
public void setGloballyPersistent(Key homeKey, Timestamp executeAt)
{
homeKey(homeKey);
if (!hasBeen(Committed))
- this.executeAt = executeAt;
- else if (!this.executeAt.equals(executeAt))
- commandStore.agent().onInconsistentTimestamp(this, this.executeAt, executeAt);
- isGloballyPersistent = true;
+ this.executeAt(executeAt);
+ else if (!this.executeAt().equals(executeAt))
+ commandStore().agent().onInconsistentTimestamp(this, this.executeAt(), executeAt);
+ isGloballyPersistent(true);
}
// requires that command != null
@@ -152,21 +136,22 @@ public class Command implements Listener, Consumer<Listener>
homeKey(homeKey);
progressKey(progressKey);
- if (status == NotWitnessed)
- status = PreAccepted;
+ if (status() == NotWitnessed)
+ status(PreAccepted);
- if (executeAt == null)
+ if (executeAt() == null)
{
- Timestamp max = commandStore.maxConflict(txn.keys);
+ Timestamp max = commandStore().maxConflict(txn.keys());
// unlike in the Accord paper, we partition shards within a node, so that to ensure a total order we must either:
// - use a global logical clock to issue new timestamps; or
// - assign each shard _and_ process a unique id, and use both as components of the timestamp
- executeAt = txnId.compareTo(max) > 0 && txnId.epoch >= commandStore.latestEpoch()
- ? txnId : commandStore.uniqueNow(max);
+ Timestamp witnessed = txnId().compareTo(max) > 0 && txnId().epoch >= commandStore().latestEpoch()
+ ? txnId() : commandStore().uniqueNow(max);
+ executeAt(witnessed);
- txn.keys().foldl(commandStore.ranges().since(txnId.epoch), (i, key, param) -> {
- if (commandStore.hashIntersects(key))
- commandStore.commandsForKey(key).register(this);
+ txn.keys().foldl(commandStore().ranges().since(txnId().epoch), (i, key, param) -> {
+ if (commandStore().hashIntersects(key))
+ commandStore().commandsForKey(key).register(this);
return null;
}, null);
}
@@ -174,53 +159,76 @@ public class Command implements Listener, Consumer<Listener>
public boolean preaccept(Txn txn, Key homeKey, Key progressKey)
{
- if (promised.compareTo(Ballot.ZERO) > 0)
+ if (promised().compareTo(Ballot.ZERO) > 0)
+ {
+ logger.trace("{}: skipping preaccept - witnessed higher ballot ({})", txnId(), promised());
return false;
+ }
if (hasBeen(PreAccepted))
+ {
+ logger.trace("{}: skipping preaccept - already preaccepted ({})", txnId(), status());
return true;
+ }
witness(txn, homeKey, progressKey);
- boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
- commandStore.progressLog().preaccept(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+ boolean isProgressShard = progressKey != null && handles(txnId().epoch, progressKey);
+ commandStore().progressLog().preaccept(this, isProgressShard, isProgressShard && progressKey.equals(homeKey));
- listeners.forEach(this);
+ logger.trace("{}: preaccepted with executeAt: {}, deps: {}", txnId(), executeAt(), savedDeps());
+ notifyListeners();
return true;
}
public boolean accept(Ballot ballot, Txn txn, Key homeKey, Key progressKey, Timestamp executeAt, Deps deps)
{
- if (this.promised.compareTo(ballot) > 0)
+ if (promised().compareTo(ballot) > 0)
+ {
+ logger.trace("{}: skipping accept - witnessed higher ballot ({} > {})", txnId(), promised(), ballot);
return false;
+ }
if (hasBeen(Committed))
+ {
+ logger.trace("{}: skipping accept - already committed ({})", txnId(), status());
return false;
+ }
witness(txn, homeKey, progressKey);
- this.deps = deps;
- this.executeAt = executeAt;
- promised = accepted = ballot;
- status = Accepted;
+ savedDeps(deps);
+ executeAt(executeAt);
+ promised(ballot);
+ accepted(ballot);
+ status(Accepted);
- boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
- commandStore.progressLog().accept(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+ boolean isProgressShard = progressKey != null && handles(txnId().epoch, progressKey);
+ commandStore().progressLog().accept(this, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+ logger.trace("{}: preaccepted with executeAt: {}, deps: {}", txnId(), executeAt, deps);
- listeners.forEach(this);
+ notifyListeners();
return true;
}
public boolean acceptInvalidate(Ballot ballot)
{
- if (this.promised.compareTo(ballot) > 0)
+ if (this.promised().compareTo(ballot) > 0)
+ {
+ logger.trace("{}: skipping accept invalidated - witnessed higher ballot ({} > {})", txnId(), promised(), ballot);
return false;
+ }
if (hasBeen(Committed))
+ {
+ logger.trace("{}: skipping accept invalidated - already committed ({})", txnId(), status());
return false;
+ }
- promised = accepted = ballot;
- status = AcceptedInvalidate;
+ promised(ballot);
+ accepted(ballot);
+ status(AcceptedInvalidate);
+ logger.trace("{}: accepted invalidated", txnId());
- listeners.forEach(this);
+ notifyListeners();
return true;
}
@@ -229,25 +237,28 @@ public class Command implements Listener, Consumer<Listener>
{
if (hasBeen(Committed))
{
- if (executeAt.equals(this.executeAt) && status != Invalidated)
+ logger.trace("{}: skipping commit - already committed ({})", txnId(), status());
+ if (executeAt.equals(executeAt()) && status() != Invalidated)
return false;
- commandStore.agent().onInconsistentTimestamp(this, (status == Invalidated ? Timestamp.NONE : this.executeAt), executeAt);
+ commandStore().agent().onInconsistentTimestamp(this, (status() == Invalidated ? Timestamp.NONE : this.executeAt()), executeAt);
}
witness(txn, homeKey, progressKey);
- this.status = Committed;
- this.deps = deps;
- this.executeAt = executeAt;
- this.waitingOnCommit = new TreeMap<>();
- this.waitingOnApply = new TreeMap<>();
-
- KeyRanges ranges = commandStore.ranges().since(executeAt.epoch);
+ savedDeps(deps);
+ executeAt(executeAt);
+ status(Committed);
+ Preconditions.checkState(!isWaitingOnCommit());
+ Preconditions.checkState(!isWaitingOnApply());
+ logger.trace("{}: committed with executeAt: {}, deps: {}", txnId(), executeAt, deps);
+
+ KeyRanges ranges = commandStore().ranges().since(executeAt.epoch);
if (ranges != null)
{
- savedDeps().forEachOn(ranges, commandStore::hashIntersects, txnId -> {
- Command command = commandStore.command(txnId);
- switch (command.status)
+ savedDeps().forEachOn(ranges, commandStore()::hashIntersects, txnId -> {
+ Command command = commandStore().command(txnId);
+ logger.trace("{}: dep {} is {}", txnId(), command.txnId(), command.status());
+ switch (command.status())
{
default:
throw new IllegalStateException();
@@ -257,7 +268,7 @@ public class Command implements Listener, Consumer<Listener>
case AcceptedInvalidate:
// we don't know when these dependencies will execute, and cannot execute until we do
command.addListener(this);
- waitingOnCommit.put(txnId, command);
+ addWaitingOnCommit(command);
break;
case Committed:
// TODO: split into ReadyToRead and ReadyToWrite;
@@ -274,101 +285,112 @@ public class Command implements Listener, Consumer<Listener>
});
}
- if (waitingOnCommit.isEmpty())
- {
- waitingOnCommit = null;
- if (waitingOnApply.isEmpty())
- waitingOnApply = null;
- }
- boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
- commandStore.progressLog().commit(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+ boolean isProgressShard = progressKey != null && handles(txnId().epoch, progressKey);
+ commandStore().progressLog().commit(this, isProgressShard, isProgressShard && progressKey.equals(homeKey));
- maybeExecute(false);
- listeners.forEach(this);
return true;
}
+ public Future<Void> commitAndBeginExecution(Txn txn, Key homeKey, Key progressKey, Timestamp executeAt, Deps deps)
+ {
+ if (!commit(txn, homeKey, progressKey, executeAt, deps))
+ return Writes.SUCCESS;
+
+ return maybeExecute(true);
+ }
+
+ // TODO (now): commitInvalidate may need to update cfks _if_ possible
public boolean commitInvalidate()
{
if (hasBeen(Committed))
{
+ logger.trace("{}: skipping commit invalidated - already committed ({})", txnId(), status());
if (!hasBeen(Invalidated))
- commandStore.agent().onInconsistentTimestamp(this, Timestamp.NONE, executeAt);
+ commandStore().agent().onInconsistentTimestamp(this, Timestamp.NONE, executeAt());
return false;
}
- status = Invalidated;
+ status(Invalidated);
- boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
- commandStore.progressLog().invalidate(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+ boolean isProgressShard = progressKey() != null && handles(txnId().epoch, progressKey());
+ commandStore().progressLog().invalidate(this, isProgressShard, isProgressShard && progressKey().equals(homeKey()));
+ logger.trace("{}: committed invalidated", txnId());
- listeners.forEach(this);
+ notifyListeners();
return true;
}
- public boolean apply(Txn txn, Key homeKey, Key progressKey, Timestamp executeAt, Deps deps, Writes writes, Result result)
+ public Future<Void> apply(Txn txn, Key homeKey, Key progressKey, Timestamp executeAt, Deps deps, Writes writes, Result result)
{
- if (hasBeen(Executed) && executeAt.equals(this.executeAt))
- return false;
+ if (hasBeen(Executed) && executeAt.equals(executeAt()))
+ {
+ logger.trace("{}: skipping apply - already executed ({})", txnId(), status());
+ return Writes.SUCCESS;
+ }
else if (!hasBeen(Committed))
commit(txn, homeKey, progressKey, executeAt, deps);
- else if (!executeAt.equals(this.executeAt))
- commandStore.agent().onInconsistentTimestamp(this, this.executeAt, executeAt);
+ else if (!executeAt.equals(executeAt()))
+ commandStore().agent().onInconsistentTimestamp(this, executeAt(), executeAt);
- this.executeAt = executeAt;
- this.writes = writes;
- this.result = result;
- this.status = Executed;
+ executeAt(executeAt);
+ writes(writes);
+ result(result);
+ status(Executed);
+ logger.trace("{}: apply, status set to Executed with executeAt: {}, deps: {}", txnId(), executeAt, deps);
- boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
- commandStore.progressLog().execute(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+ boolean isProgressShard = progressKey != null && handles(txnId().epoch, progressKey);
+ commandStore().progressLog().execute(this, isProgressShard, isProgressShard && progressKey.equals(homeKey));
- maybeExecute(false);
- this.listeners.forEach(this);
- return true;
+ return maybeExecute(true);
}
public boolean recover(Txn txn, Key homeKey, Key progressKey, Ballot ballot)
{
- if (this.promised.compareTo(ballot) > 0)
+ if (this.promised().compareTo(ballot) > 0)
+ {
+ logger.trace("{}: skipping recover - higher ballot witnessed ({})", txnId(), promised());
return false;
+ }
- Status status = this.status;
+ Status status = status();
witness(txn, homeKey, progressKey);
- boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
+ boolean isProgressShard = progressKey != null && handles(txnId().epoch, progressKey);
if (status == NotWitnessed)
- commandStore.progressLog().preaccept(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
- this.promised = ballot;
+ commandStore().progressLog().preaccept(this, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+ promised(ballot);
+ logger.trace("{}: recovery started with executeAt: {}, deps: {}", txnId(), executeAt(), savedDeps());
return true;
}
public boolean preAcceptInvalidate(Ballot ballot)
{
- if (this.promised.compareTo(ballot) > 0)
+ if (promised().compareTo(ballot) > 0)
+ {
+ logger.trace("{}: skipping preaccept invalidate - higher ballot witnessed ({})", txnId(), promised());
return false;
+ }
- this.promised = ballot;
+ promised(ballot);
+ logger.trace("{}: preaccepted invalidated", txnId());
return true;
}
- public boolean addListener(Listener listener)
- {
- return listeners.add(listener);
- }
-
- public void removeListener(Listener listener)
+ @Override
+ public PreLoadContext listenerPreLoadContext(TxnId caller)
{
- listeners.remove(listener);
+ return PreLoadContext.contextFor(listOf(txnId(), caller), Collections.emptyList());
}
- @Override
- public void onChange(Command command)
+ private void onChangeInternal(PartialCommand command)
{
- switch (command.status)
+ logger.trace("{}: updating as listener in response to change on {} with status {} ({})",
+ txnId(), command.txnId(), command.status(), command);
+ switch (command.status())
{
- default: throw new IllegalStateException();
+ default:
+ throw new IllegalStateException();
case NotWitnessed:
case PreAccepted:
case Accepted:
@@ -380,97 +402,140 @@ public class Command implements Listener, Consumer<Listener>
case Executed:
case Applied:
case Invalidated:
- if (waitingOnApply != null)
+ if (isUnableToApply())
{
updatePredecessor(command);
- if (waitingOnCommit != null)
+ if (isWaitingOnCommit())
{
- if (waitingOnCommit.remove(command.txnId) != null && waitingOnCommit.isEmpty())
- waitingOnCommit = null;
+ removeWaitingOnCommit(command);
}
- if (waitingOnCommit == null && waitingOnApply.isEmpty())
- waitingOnApply = null;
}
else
{
command.removeListener(this);
}
- maybeExecute(true);
+ maybeExecute(false);
break;
}
}
- private void maybeExecute(boolean notifyListeners)
+ @Override
+ public void onChange(Command command)
{
- if (status != Committed && status != Executed)
- return;
+ onChangeInternal(command);
+ }
- if (waitingOnApply != null)
+ protected void postApply()
+ {
+ logger.trace("{} applied, setting status to Applied and notifying listeners", txnId());
+ status(Applied);
+ notifyListeners();
+ }
+
+ private static Function<CommandStore, Void> callPostApply(TxnId txnId)
+ {
+ return commandStore -> {
+ commandStore.command(txnId).postApply();
+ return null;
+ };
+ }
+
+ protected Future<Void> apply()
+ {
+ // important: we can't include a reference to *this* in the lambda, since the C* implementation may evict
+ // the command instance from memory between now and the write completing (and post apply being called)
+ return writes().apply(commandStore()).flatMap(unused ->
+ commandStore().process(this, callPostApply(txnId()))
+ );
+ }
+
+ public Future<Data> read(Keys scope)
+ {
+ return txn().read(this);
+ }
+
+ private Future<Void> maybeExecute(boolean notifyListenersOnNoop)
+ {
+ if (logger.isTraceEnabled())
+ logger.trace("{}: Maybe executing with status {}. Will notify listeners on noop: {}", txnId(), status(), notifyListenersOnNoop);
+
+ if (status() != Committed && status() != Executed)
+ {
+ if (notifyListenersOnNoop) notifyListeners();
+ return Writes.SUCCESS;
+ }
+
+ if (isUnableToApply())
{
BlockedBy blockedBy = blockedBy();
if (blockedBy != null)
{
- commandStore.progressLog().waiting(blockedBy.txnId, blockedBy.someKeys);
- return;
+ logger.trace("{}: not executing, blocked on {}", txnId(), blockedBy.command.txnId());
+ commandStore().progressLog().waiting(blockedBy.command, blockedBy.someKeys);
+ if (notifyListenersOnNoop) notifyListeners();
+ return Writes.SUCCESS;
}
- assert waitingOnApply == null;
+ assert !isWaitingOnApply();
}
- switch (status)
+ switch (status())
{
case Committed:
// TODO: maintain distinct ReadyToRead and ReadyToWrite states
- status = ReadyToExecute;
- boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
- commandStore.progressLog().readyToExecute(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
- if (notifyListeners)
- listeners.forEach(this);
+ status(ReadyToExecute);
+ logger.trace("{}: set to ReadyToExecute", txnId());
+ boolean isProgressShard = progressKey() != null && handles(txnId().epoch, progressKey());
+ commandStore().progressLog().readyToExecute(this, isProgressShard, isProgressShard && progressKey().equals(homeKey()));
+ notifyListeners();
break;
case Executed:
- writes.apply(commandStore);
- status = Applied;
- if (notifyListeners)
- listeners.forEach(this);
+ logger.trace("{}: applying", txnId());
+ if (notifyListenersOnNoop) notifyListeners();
+ return apply();
}
+ return Writes.SUCCESS;
}
/**
* @param dependency is either committed or invalidated
*/
- private void updatePredecessor(Command dependency)
+ private void updatePredecessor(PartialCommand dependency)
{
Preconditions.checkState(dependency.hasBeen(Committed));
if (dependency.hasBeen(Invalidated))
{
+ logger.trace("{}: {} is invalidated. Stop listening and removing from waiting on commit set.", txnId(), dependency.txnId());
dependency.removeListener(this);
- if (waitingOnCommit.remove(dependency.txnId) != null && waitingOnCommit.isEmpty())
- waitingOnCommit = null;
+ removeWaitingOnCommit(dependency);
}
- else if (dependency.executeAt.compareTo(executeAt) > 0)
+ else if (dependency.executeAt().compareTo(executeAt()) > 0)
{
// cannot be a predecessor if we execute later
+ logger.trace("{}: {} executes after us. Stop listening.", txnId(), dependency.txnId());
dependency.removeListener(this);
}
else if (dependency.hasBeen(Applied))
{
- waitingOnApply.remove(dependency.executeAt);
+ logger.trace("{}: {} has been applied. Stop listening and removing from waiting on apply set.", txnId(), dependency.txnId());
+ removeWaitingOnApply(dependency);
dependency.removeListener(this);
}
else
{
- waitingOnApply.putIfAbsent(dependency.executeAt, dependency);
+ logger.trace("{}: adding {} to waiting on apply set.", txnId(), dependency.txnId());
+ addWaitingOnApplyIfAbsent(dependency);
}
}
// TEMPORARY: once we can invalidate commands that have not been witnessed on any shard, we do not need to know the home shard
static class BlockedBy
{
- final TxnId txnId;
+ final PartialCommand command;
final Keys someKeys;
- BlockedBy(TxnId txnId, Keys someKeys)
+ BlockedBy(PartialCommand command, Keys someKeys)
{
- this.txnId = txnId;
+ this.command = command;
this.someKeys = someKeys;
}
}
@@ -478,21 +543,14 @@ public class Command implements Listener, Consumer<Listener>
public BlockedBy blockedBy()
{
Command prev = this;
- Command cur = directlyBlockedBy();
+ PartialCommand cur = directlyBlockedBy();
if (cur == null)
return null;
- Command next;
- while (null != (next = cur.directlyBlockedBy()))
- {
- prev = cur;
- cur = next;
- }
-
Keys someKeys = cur.someKeys();
if (someKeys == null)
- someKeys = prev.deps.someKeys(cur.txnId);
- return new BlockedBy(cur.txnId, someKeys);
+ someKeys = prev.savedDeps().someKeys(cur.txnId());
+ return new BlockedBy(cur, someKeys);
}
/**
@@ -506,21 +564,22 @@ public class Command implements Listener, Consumer<Listener>
*
* TODO: Markdown documentation explaining the home shard and local shard concepts
*/
- public Key homeKey()
- {
- return homeKey;
- }
- public void homeKey(Key homeKey)
+ public final void homeKey(Key homeKey)
{
- if (this.homeKey == null) this.homeKey = homeKey;
- else if (!this.homeKey.equals(homeKey)) throw new IllegalStateException();
+ Key current = homeKey();
+ if (current == null) setHomeKey(homeKey);
+ else if (!current.equals(homeKey)) throw new AssertionError();
}
- public Keys someKeys()
+
+ public Key someKey()
{
- if (txn != null)
- return txn.keys;
+ if (homeKey() != null)
+ return homeKey();
+
+ if (txn().keys() != null)
+ return txn().keys().get(0);
return null;
}
@@ -532,36 +591,34 @@ public class Command implements Listener, Consumer<Listener>
*
* Preferentially, this is homeKey on nodes that replicate it, and otherwise any key that is replicated, as of txnId.epoch
*/
- public Key progressKey()
- {
- return progressKey;
- }
- public void progressKey(Key progressKey)
+ public final void progressKey(Key progressKey)
{
- if (this.progressKey == null) this.progressKey = progressKey;
- else if (!this.progressKey.equals(progressKey)) throw new IllegalStateException();
+ Key current = progressKey();
+ if (current == null) setProgressKey(progressKey);
+ else if (!current.equals(progressKey)) throw new AssertionError();
}
// does this specific Command instance execute (i.e. does it lose ownership post Commit)
public boolean executes()
{
- KeyRanges ranges = commandStore.ranges().at(executeAt.epoch);
- return ranges != null && txn.keys.any(ranges, commandStore::hashIntersects);
+ KeyRanges ranges = commandStore().ranges().at(executeAt().epoch);
+ return ranges != null && txn().keys().any(ranges, commandStore()::hashIntersects);
}
- public void txn(Txn txn)
+ public final void txn(Txn txn)
{
- if (this.txn == null) this.txn = txn;
- else if (!this.txn.equals(txn)) throw new IllegalStateException();
+ Txn current = txn();
+ if (current == null) setTxn(txn);
+ else if (!current.equals(txn)) throw new AssertionError();
}
public boolean handles(long epoch, Key someKey)
{
- if (!commandStore.hashIntersects(someKey))
+ if (!commandStore().hashIntersects(someKey))
return false;
- KeyRanges ranges = commandStore.ranges().at(epoch);
+ KeyRanges ranges = commandStore().ranges().at(epoch);
if (ranges == null)
return false;
return ranges.contains(someKey);
@@ -569,32 +626,32 @@ public class Command implements Listener, Consumer<Listener>
private Id coordinator()
{
- if (promised.equals(Ballot.ZERO))
- return txnId.node;
- return promised.node;
+ if (promised().equals(Ballot.ZERO))
+ return txnId().node;
+ return promised().node;
}
- private Command directlyBlockedBy()
+ private PartialCommand directlyBlockedBy()
{
// firstly we're waiting on every dep to commit
- while (waitingOnCommit != null)
+ while (isWaitingOnCommit())
{
// TODO: when we change our liveness mechanism this may not be a problem
// cannot guarantee that listener updating this set is invoked before this method by another listener
// so we must check the entry is still valid, and potentially remove it if not
- Command waitingOn = waitingOnCommit.firstEntry().getValue();
+ PartialCommand waitingOn = firstWaitingOnCommit();
if (!waitingOn.hasBeen(Committed)) return waitingOn;
- onChange(waitingOn);
+ onChangeInternal(waitingOn);
}
- while (waitingOnApply != null)
+ while (isWaitingOnApply())
{
// TODO: when we change our liveness mechanism this may not be a problem
// cannot guarantee that listener updating this set is invoked before this method by another listener
// so we must check the entry is still valid, and potentially remove it if not
- Command waitingOn = waitingOnApply.firstEntry().getValue();
+ PartialCommand waitingOn = firstWaitingOnApply();
if (!waitingOn.hasBeen(Applied)) return waitingOn;
- onChange(waitingOn);
+ onChangeInternal(waitingOn);
}
return null;
@@ -610,11 +667,11 @@ public class Command implements Listener, Consumer<Listener>
public String toString()
{
return "Command{" +
- "txnId=" + txnId +
- ", status=" + status +
- ", txn=" + txn +
- ", executeAt=" + executeAt +
- ", deps=" + deps +
+ "txnId=" + txnId() +
+ ", status=" + status() +
+ ", txn=" + txn() +
+ ", executeAt=" + executeAt() +
+ ", deps=" + savedDeps() +
'}';
}
}
diff --git a/accord-core/src/main/java/accord/local/CommandStore.java b/accord-core/src/main/java/accord/local/CommandStore.java
index 06b934a..b6a4f4b 100644
--- a/accord-core/src/main/java/accord/local/CommandStore.java
+++ b/accord-core/src/main/java/accord/local/CommandStore.java
@@ -22,43 +22,25 @@ import accord.api.Agent;
import accord.api.Key;
import accord.local.CommandStores.ShardedRanges;
import accord.api.ProgressLog;
-import accord.primitives.KeyRange;
import accord.api.DataStore;
import accord.primitives.KeyRanges;
import accord.primitives.Keys;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
-import org.apache.cassandra.utils.concurrent.AsyncPromise;
import org.apache.cassandra.utils.concurrent.Future;
-import org.apache.cassandra.utils.concurrent.Promise;
import com.google.common.base.Preconditions;
import java.util.*;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
-
-import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
+import java.util.function.LongSupplier;
/**
* Single threaded internal shard of accord transaction metadata
*/
public abstract class CommandStore
{
- public interface Factory
- {
- CommandStore create(int generation,
- int shardIndex,
- int numShards,
- Node node,
- Agent agent,
- DataStore store,
- ProgressLog.Factory progressLogFactory,
- RangesForEpoch rangesForEpoch);
- }
-
public interface RangesForEpoch
{
KeyRanges at(long epoch);
@@ -69,19 +51,19 @@ public abstract class CommandStore
private final int generation;
private final int shardIndex;
private final int numShards;
- private final Node node;
+ private final Function<Timestamp, Timestamp> uniqueNow;
+ private final LongSupplier currentEpoch;
private final Agent agent;
private final DataStore store;
private final ProgressLog progressLog;
private final RangesForEpoch rangesForEpoch;
- private final NavigableMap<TxnId, Command> commands = new TreeMap<>();
- private final NavigableMap<Key, CommandsForKey> commandsForKey = new TreeMap<>();
public CommandStore(int generation,
int shardIndex,
int numShards,
- Node node,
+ Function<Timestamp, Timestamp> uniqueNow,
+ LongSupplier currentEpoch,
Agent agent,
DataStore store,
ProgressLog.Factory progressLogFactory,
@@ -91,42 +73,23 @@ public abstract class CommandStore
this.generation = generation;
this.shardIndex = shardIndex;
this.numShards = numShards;
- this.node = node;
+ this.uniqueNow = uniqueNow;
+ this.currentEpoch = currentEpoch;
this.agent = agent;
this.store = store;
this.progressLog = progressLogFactory.create(this);
this.rangesForEpoch = rangesForEpoch;
}
- public Command ifPresent(TxnId txnId)
- {
- return commands.get(txnId);
- }
+ public abstract Command ifPresent(TxnId txnId);
- public Command command(TxnId txnId)
- {
- return commands.computeIfAbsent(txnId, id -> new Command(this, id));
- }
+ public abstract Command command(TxnId txnId);
- public boolean hasCommand(TxnId txnId)
- {
- return commands.containsKey(txnId);
- }
+ public abstract CommandsForKey commandsForKey(Key key);
- public CommandsForKey commandsForKey(Key key)
- {
- return commandsForKey.computeIfAbsent(key, ignore -> new CommandsForKey());
- }
- public CommandsForKey maybeCommandsForKey(Key key)
- {
- return commandsForKey.get(key);
- }
-
- public boolean hasCommandsForKey(Key key)
- {
- return commandsForKey.containsKey(key);
- }
+ // TODO (now): is this needed?
+ public abstract CommandsForKey maybeCommandsForKey(Key key);
public DataStore store()
{
@@ -135,7 +98,7 @@ public abstract class CommandStore
public Timestamp uniqueNow(Timestamp atLeast)
{
- return node.uniqueNow(atLeast);
+ return uniqueNow.apply(atLeast);
}
public Agent agent()
@@ -148,11 +111,6 @@ public abstract class CommandStore
return progressLog;
}
- public Node node()
- {
- return node;
- }
-
public RangesForEpoch ranges()
{
return rangesForEpoch;
@@ -161,7 +119,7 @@ public abstract class CommandStore
public long latestEpoch()
{
// TODO: why not inject the epoch to each command store?
- return node.epoch();
+ return currentEpoch.getAsLong();
}
protected Timestamp maxConflict(Keys keys)
@@ -174,43 +132,17 @@ public abstract class CommandStore
.orElse(Timestamp.NONE);
}
- public void forEpochCommands(KeyRanges ranges, long epoch, Consumer<Command> consumer)
+
+ // TODO (now): rename to shardIndex
+ public int index()
{
- Timestamp minTimestamp = new Timestamp(epoch, Long.MIN_VALUE, Integer.MIN_VALUE, Node.Id.NONE);
- Timestamp maxTimestamp = new Timestamp(epoch, Long.MAX_VALUE, Integer.MAX_VALUE, Node.Id.MAX);
- for (KeyRange range : ranges)
- {
- Iterable<CommandsForKey> rangeCommands = commandsForKey.subMap(range.start(),
- range.startInclusive(),
- range.end(),
- range.endInclusive()).values();
- for (CommandsForKey commands : rangeCommands)
- {
- commands.forWitnessed(minTimestamp, maxTimestamp, consumer);
- }
- }
+ return shardIndex;
}
- public void forCommittedInEpoch(KeyRanges ranges, long epoch, Consumer<Command> consumer)
+ // TODO (now): rename to shardGeneration
+ public int generation()
{
- Timestamp minTimestamp = new Timestamp(epoch, Long.MIN_VALUE, Integer.MIN_VALUE, Node.Id.NONE);
- Timestamp maxTimestamp = new Timestamp(epoch, Long.MAX_VALUE, Integer.MAX_VALUE, Node.Id.MAX);
- for (KeyRange range : ranges)
- {
- Iterable<CommandsForKey> rangeCommands = commandsForKey.subMap(range.start(),
- range.startInclusive(),
- range.end(),
- range.endInclusive()).values();
- for (CommandsForKey commands : rangeCommands)
- {
-
- Collection<Command> committed = commands.committedByExecuteAt.subMap(minTimestamp,
- true,
- maxTimestamp,
- true).values();
- committed.forEach(consumer);
- }
- }
+ return generation;
}
public boolean hashIntersects(Key key)
@@ -223,226 +155,23 @@ public abstract class CommandStore
return keys.any(ranges, this::hashIntersects);
}
- public static void onEach(Collection<CommandStore> stores, Consumer<? super CommandStore> consumer)
+ public static void onEach(PreLoadContext scope, Collection<CommandStore> stores, Consumer<? super CommandStore> consumer)
{
for (CommandStore store : stores)
- store.process(consumer);
+ store.process(scope, consumer);
}
- <R> void processInternal(Function<? super CommandStore, R> function, Promise<R> promise)
- {
- try
- {
- promise.setSuccess(function.apply(this));
- }
- catch (Throwable e)
- {
- promise.tryFailure(e);
- }
- }
+ /**
+ * for interacting with the command store in a threadsafe way, without needing any txns loaded. For configuration
+ */
+ public abstract Future<Void> processSetup(Consumer<? super CommandStore> function);
- void processInternal(Consumer<? super CommandStore> consumer, Promise<Void> promise)
- {
- try
- {
- consumer.accept(this);
- promise.setSuccess(null);
- }
- catch (Throwable e)
- {
- promise.tryFailure(e);
- }
- }
-
- public abstract Future<Void> process(Consumer<? super CommandStore> consumer);
+ public abstract <T> Future<T> processSetup(Function<? super CommandStore, T> function);
- public abstract <T> Future<T> process(Function<? super CommandStore, T> function);
+ public abstract Future<Void> process(PreLoadContext context, Consumer<? super CommandStore> consumer);
- public void processBlocking(Consumer<? super CommandStore> consumer)
- {
- try
- {
- process(consumer).get();
- }
- catch (InterruptedException e)
- {
- throw new UncheckedInterruptedException(e);
- }
- catch (ExecutionException e)
- {
- throw new RuntimeException(e.getCause());
- }
- }
+ public abstract <T> Future<T> process(PreLoadContext context, Function<? super CommandStore, T> function);
public abstract void shutdown();
- public static class Synchronized extends CommandStore
- {
- public Synchronized(int generation, int index, int numShards, Node node,
- Agent agent, DataStore store,
- ProgressLog.Factory progressLogFactory,
- RangesForEpoch rangesForEpoch)
- {
- super(generation, index, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
- }
-
- @Override
- public synchronized Future<Void> process(Consumer<? super CommandStore> consumer)
- {
- AsyncPromise<Void> promise = new AsyncPromise<>();
- processInternal(consumer, promise);
- return promise;
- }
-
- @Override
- public <T> Future<T> process(Function<? super CommandStore, T> function)
- {
- AsyncPromise<T> promise = new AsyncPromise<>();
- processInternal(function, promise);
- return promise;
- }
-
- @Override
- public void shutdown() {}
- }
-
- public static class SingleThread extends CommandStore
- {
- private final ExecutorService executor;
-
- private class ConsumerWrapper extends AsyncPromise<Void> implements Runnable
- {
- private final Consumer<? super CommandStore> consumer;
-
- public ConsumerWrapper(Consumer<? super CommandStore> consumer)
- {
- this.consumer = consumer;
- }
-
- @Override
- public void run()
- {
- processInternal(consumer, this);
- }
- }
-
- private class FunctionWrapper<T> extends AsyncPromise<T> implements Runnable
- {
- private final Function<? super CommandStore, T> function;
-
- public FunctionWrapper(Function<? super CommandStore, T> function)
- {
- this.function = function;
- }
-
- @Override
- public void run()
- {
- processInternal(function, this);
- }
- }
-
- public SingleThread(int generation, int index, int numShards, Node node,
- Agent agent, DataStore store, ProgressLog.Factory progressLogFactory,
- RangesForEpoch rangesForEpoch)
- {
- super(generation, index, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
- executor = Executors.newSingleThreadExecutor(r -> {
- Thread thread = new Thread(r);
- thread.setName(CommandStore.class.getSimpleName() + '[' + node.id() + ':' + index + ']');
- return thread;
- });
- }
-
- @Override
- public Future<Void> process(Consumer<? super CommandStore> consumer)
- {
- ConsumerWrapper future = new ConsumerWrapper(consumer);
- executor.execute(future);
- return future;
- }
-
- @Override
- public <T> Future<T> process(Function<? super CommandStore, T> function)
- {
- FunctionWrapper<T> future = new FunctionWrapper<>(function);
- executor.execute(future);
- return future;
- }
-
- @Override
- public void shutdown()
- {
- executor.shutdown();
- }
- }
-
- static class Debug extends SingleThread
- {
- private final AtomicReference<Thread> expectedThread = new AtomicReference<>();
-
- public Debug(int generation, int index, int numShards, Node node,
- Agent agent, DataStore store, ProgressLog.Factory progressLogFactory,
- RangesForEpoch rangesForEpoch)
- {
- super(generation, index, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
- }
-
- private void assertThread()
- {
- Thread current = Thread.currentThread();
- Thread expected;
- while (true)
- {
- expected = expectedThread.get();
- if (expected != null)
- break;
- expectedThread.compareAndSet(null, Thread.currentThread());
- }
- Preconditions.checkState(expected == current);
- }
-
- @Override
- public Command command(TxnId txnId)
- {
- assertThread();
- return super.command(txnId);
- }
-
- @Override
- public boolean hasCommand(TxnId txnId)
- {
- assertThread();
- return super.hasCommand(txnId);
- }
-
- @Override
- public CommandsForKey commandsForKey(Key key)
- {
- assertThread();
- return super.commandsForKey(key);
- }
-
- @Override
- public boolean hasCommandsForKey(Key key)
- {
- assertThread();
- return super.hasCommandsForKey(key);
- }
-
- @Override
- <R> void processInternal(Function<? super CommandStore, R> function, Promise<R> future)
- {
- assertThread();
- super.processInternal(function, future);
- }
-
- @Override
- void processInternal(Consumer<? super CommandStore> consumer, Promise<Void> future)
- {
- assertThread();
- super.processInternal(consumer, future);
- }
- }
-
}
diff --git a/accord-core/src/main/java/accord/local/CommandStores.java b/accord-core/src/main/java/accord/local/CommandStores.java
index 254d38d..8990379 100644
--- a/accord-core/src/main/java/accord/local/CommandStores.java
+++ b/accord-core/src/main/java/accord/local/CommandStores.java
@@ -19,24 +19,29 @@
package accord.local;
import accord.api.Agent;
-import accord.api.Key;
import accord.api.DataStore;
-import accord.local.CommandStore.RangesForEpoch;
+import accord.api.Key;
import accord.api.ProgressLog;
+import accord.local.CommandStore.RangesForEpoch;
import accord.primitives.KeyRanges;
import accord.topology.Topology;
import accord.primitives.Keys;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
+import accord.messages.TxnRequest;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
-
+import org.apache.cassandra.utils.concurrent.FutureCombiner;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.IntFunction;
import java.util.*;
import java.util.concurrent.ExecutionException;
-import java.util.function.*;
-
-import static java.lang.Boolean.FALSE;
/**
* Manages the single threaded metadata shards
@@ -52,95 +57,23 @@ public abstract class CommandStores
ProgressLog.Factory progressLogFactory);
}
- interface Fold<I1, I2, O>
+ protected interface Fold<I1, I2, O>
{
O fold(CommandStore store, I1 i1, I2 i2, O accumulator);
}
- interface Select<Scope>
+ protected interface Select<Scope>
{
long select(ShardedRanges ranges, Scope scope, long minEpoch, long maxEpoch);
}
- static class KeysAndEpoch
- {
- final Keys keys;
- final long epoch;
-
- KeysAndEpoch(Keys keys, long epoch)
- {
- this.keys = keys;
- this.epoch = epoch;
- }
- }
-
- static class KeysAndEpochRange
- {
- final Keys keys;
- final long minEpoch;
- final long maxEpoch;
-
- KeysAndEpochRange(Keys keys, long minEpoch, long maxEpoch)
- {
- this.keys = keys;
- this.minEpoch = minEpoch;
- this.maxEpoch = maxEpoch;
- }
- }
-
- static class KeyAndEpoch
- {
- final Key key;
- final long epoch;
-
- KeyAndEpoch(Key key, long epoch)
- {
- this.key = key;
- this.epoch = epoch;
- }
- }
-
- private static class Supplier
- {
- private final Node node;
- private final Agent agent;
- private final DataStore store;
- private final ProgressLog.Factory progressLogFactory;
- private final CommandStore.Factory shardFactory;
- private final int numShards;
-
- Supplier(Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory, CommandStore.Factory shardFactory, int numShards)
- {
- this.node = node;
- this.agent = agent;
- this.store = store;
- this.progressLogFactory = progressLogFactory;
- this.shardFactory = shardFactory;
- this.numShards = numShards;
- }
-
- CommandStore create(int generation, int shardIndex, RangesForEpoch rangesForEpoch)
- {
- return shardFactory.create(generation, shardIndex, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
- }
-
- ShardedRanges createShardedRanges(int generation, long epoch, KeyRanges ranges, RangesForEpoch rangesForEpoch)
- {
- CommandStore[] newStores = new CommandStore[numShards];
- for (int i=0; i<numShards; i++)
- newStores[i] = create(generation, i, rangesForEpoch);
-
- return new ShardedRanges(newStores, epoch, ranges);
- }
- }
-
- static class ShardedRanges
+ protected static class ShardedRanges
{
final CommandStore[] shards;
final long[] epochs;
final KeyRanges[] ranges;
- public ShardedRanges(CommandStore[] shards, long epoch, KeyRanges ranges)
+ protected ShardedRanges(CommandStore[] shards, long epoch, KeyRanges ranges)
{
Preconditions.checkArgument(shards.length <= 64);
this.shards = shards;
@@ -190,12 +123,12 @@ public abstract class CommandStores
return i;
}
- long all()
+ public long all()
{
return -1L >>> (64 - shards.length);
}
- long shards(Keys keys, long minEpoch, long maxEpoch)
+ public long shards(Keys keys, long minEpoch, long maxEpoch)
{
long accumulate = 0L;
for (int i = Math.max(0, indexForEpoch(minEpoch)), maxi = indexForEpoch(maxEpoch); i <= maxi ; ++i)
@@ -205,6 +138,11 @@ public abstract class CommandStores
return accumulate;
}
+ long shards(TxnRequest request, long minEpoch, long maxEpoch)
+ {
+ return shards(request.scope(), minEpoch, maxEpoch);
+ }
+
long shard(Key scope, long minEpoch, long maxEpoch)
{
long result = 0L;
@@ -253,19 +191,22 @@ public abstract class CommandStores
}
}
- final Supplier supplier;
+ private final Node node;
+ private final Agent agent;
+ private final DataStore store;
+ private final ProgressLog.Factory progressLogFactory;
+ private final int numShards;
volatile Snapshot current;
- private CommandStores(Supplier supplier)
- {
- this.supplier = supplier;
- this.current = new Snapshot(new ShardedRanges[0], Topology.EMPTY, Topology.EMPTY);
- }
-
public CommandStores(int num, Node node, Agent agent, DataStore store,
- ProgressLog.Factory progressLogFactory, CommandStore.Factory shardFactory)
+ ProgressLog.Factory progressLogFactory)
{
- this(new Supplier(node, agent, store, progressLogFactory, shardFactory, num));
+ this.node = node;
+ this.agent = agent;
+ this.store = store;
+ this.progressLogFactory = progressLogFactory;
+ this.numShards = num;
+ this.current = new Snapshot(new ShardedRanges[0], Topology.EMPTY, Topology.EMPTY);
}
public Topology local()
@@ -278,6 +219,24 @@ public abstract class CommandStores
return current.global;
}
+ protected abstract CommandStore createCommandStore(int generation,
+ int index,
+ int numShards,
+ Node node,
+ Agent agent,
+ DataStore store,
+ ProgressLog.Factory progressLogFactory,
+ RangesForEpoch rangesForEpoch);
+
+ private ShardedRanges createShardedRanges(int generation, long epoch, KeyRanges ranges, RangesForEpoch rangesForEpoch)
+ {
+ CommandStore[] newStores = new CommandStore[numShards];
+ for (int i=0; i<numShards; i++)
+ newStores[i] = createCommandStore(generation, i, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
+
+ return new ShardedRanges(newStores, epoch, ranges);
+ }
+
private Snapshot updateTopology(Snapshot prev, Topology newTopology)
{
Preconditions.checkArgument(!newTopology.isSubset(), "Use full topology for CommandStores.updateTopology");
@@ -286,7 +245,7 @@ public abstract class CommandStores
if (epoch <= prev.global.epoch())
return prev;
- Topology newLocalTopology = newTopology.forNode(supplier.node.id());
+ Topology newLocalTopology = newTopology.forNode(node.id());
KeyRanges added = newLocalTopology.ranges().difference(prev.local.ranges());
KeyRanges subtracted = prev.local.ranges().difference(newLocalTopology.ranges());
// for (ShardedRanges range : stores.ranges)
@@ -305,7 +264,7 @@ public abstract class CommandStores
{
int newGeneration = prev.ranges.length;
System.arraycopy(prev.ranges, 0, result, 0, newGeneration);
- result[newGeneration] = supplier.createShardedRanges(newGeneration, epoch, added, rangesForEpochFunction(newGeneration));
+ result[newGeneration] = createShardedRanges(newGeneration, epoch, added, rangesForEpochFunction(newGeneration));
}
else
{
@@ -318,7 +277,7 @@ public abstract class CommandStores
result[i++] = ranges;
}
if (i < result.length)
- result[i] = supplier.createShardedRanges(i, epoch, added, rangesForEpochFunction(i));
+ result[i] = createShardedRanges(i, epoch, added, rangesForEpochFunction(i));
}
return new Snapshot(result, newTopology, newLocalTopology);
@@ -356,42 +315,115 @@ public abstract class CommandStores
commandStore.shutdown();
}
- protected abstract <S> void forEach(Select<S> select, S scope, long minEpoch, long maxEpoch, Consumer<? super CommandStore> forEach);
- protected abstract <S, T> T mapReduce(Select<S> select, S scope, long minEpoch, long maxEpoch, Function<? super CommandStore, T> map, BiFunction<T, T, T> reduce);
+ private static <T> Fold<PreLoadContext, Void, List<Future<T>>> mapFold(Function<CommandStore, T> map)
+ {
+ return (store, op, i, t) -> { t.add(store.process(op, map)); return t; };
+ }
- public void forEach(Consumer<CommandStore> forEach)
+ private static <T> T reduce(List<Future<T>> futures, BiFunction<T, T, T> reduce)
{
- forEach((s, i, min, max) -> s.all(), null, 0, 0, forEach);
+ T result = null;
+ for (Future<T> future : futures)
+ {
+ try
+ {
+ T next = future.get();
+ result = result == null ? next : reduce.apply(result, next);
+ }
+ catch (InterruptedException e)
+ {
+ throw new UncheckedInterruptedException(e);
+ }
+ catch (ExecutionException e)
+ {
+ throw new RuntimeException(e.getCause());
+ }
+ }
+ return result;
}
- public void forEach(Keys keys, long epoch, Consumer<CommandStore> forEach)
+ private <F, T> T setup(F f, Fold<F, Void, List<Future<T>>> fold, BiFunction<T, T, T> reduce)
{
- forEach(keys, epoch, epoch, forEach);
+ List<Future<T>> futures = foldl((s, i, mn, mx) -> s.all(), null, Long.MIN_VALUE, Long.MAX_VALUE, fold, f, null, ArrayList::new);
+ return reduce(futures, reduce);
}
- public void forEach(Keys keys, long minEpoch, long maxEpoch, Consumer<CommandStore> forEach)
+ private <S, T> T mapReduce(PreLoadContext context, Select<S> select, S scope, long minEpoch, long maxEpoch, Fold<PreLoadContext, Void, List<Future<T>>> fold, BiFunction<T, T, T> reduce)
{
- forEach(ShardedRanges::shards, keys, minEpoch, maxEpoch, forEach);
+ List<Future<T>> futures = foldl(select, scope, minEpoch, maxEpoch, fold, context, null, ArrayList::new);
+ if (futures == null)
+ return null;
+ return reduce(futures, reduce);
}
- public <T> T mapReduce(Keys keys, long epoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+ public <T> T mapReduce(PreLoadContext context, Key key, long minEpoch, long maxEpoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
{
- return mapReduce(keys, epoch, epoch, map, reduce);
+ return mapReduce(context, ShardedRanges::shard, key, minEpoch, maxEpoch, mapFold(map), reduce);
}
- public <T> T mapReduce(Keys keys, long minEpoch, long maxEpoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+ public <T> T mapReduce(PreLoadContext context, Key key, long epoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
{
- return mapReduce(ShardedRanges::shards, keys, minEpoch, maxEpoch, map, reduce);
+ return mapReduce(context, key, epoch, epoch, map, reduce);
}
- public <T> T mapReduce(Key key, long epoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+ public <T> T mapReduceSince(PreLoadContext context, Key key, long epoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
{
- return mapReduce(ShardedRanges::shard, key, epoch, epoch, map, reduce);
+ return mapReduce(context, key, epoch, Long.MAX_VALUE, map, reduce);
}
- public <T> T mapReduceSince(Key key, long epoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+ public <T> T mapReduce(PreLoadContext context, Keys keys, long minEpoch, long maxEpoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
{
- return mapReduce(ShardedRanges::shard, key, epoch, Long.MAX_VALUE, map, reduce);
+ // probably need to split txnOperation and scope stuff here
+ return mapReduce(context, ShardedRanges::shards, keys, minEpoch, maxEpoch, mapFold(map), reduce);
+ }
+
+ public <T> List<Future<T>> mapAsync(PreLoadContext context, Keys keys, long minEpoch, long maxEpoch, Function<CommandStore, T> map)
+ {
+ return foldl(ShardedRanges::shards, keys, minEpoch, maxEpoch, mapFold(map), context, null, ArrayList::new);
+ }
+
+ public void setup(Consumer<CommandStore> forEach)
+ {
+ setup(forEach, (store, f, i, t) -> { t.add(store.processSetup(f)); return t; }, (Void i1, Void i2) -> null);
+ }
+
+ public <T> T setup(Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+ {
+ return setup(map, (store, f, i, t) -> { t.add(store.processSetup(f)); return t; }, reduce);
+ }
+
+ private static Fold<PreLoadContext, Void, List<Future<Void>>> forEachFold(Consumer<CommandStore> forEach)
+ {
+ return (store, op, i, t) -> { t.add(store.process(op, forEach)); return t; };
+ }
+
+ public void forEach(PreLoadContext context, Keys keys, long minEpoch, long maxEpoch, Consumer<CommandStore> forEach)
+ {
+ mapReduce(context, ShardedRanges::shards, keys, minEpoch, maxEpoch, forEachFold(forEach), (o1, o2) -> null);
+ }
+
+ public void forEach(PreLoadContext context, Consumer<CommandStore> forEach)
+ {
+ mapReduce(context, (s, i, min, max) -> s.all(), null, 0, 0, forEachFold(forEach), (o1, o2) -> null);
+ }
+
+ public void forEach(TxnRequest request, long epoch, Consumer<CommandStore> forEach)
+ {
+ forEach(request, request.scope(), epoch, epoch, forEach);
+ }
+
+ public void forEach(PreLoadContext context, Keys keys, long epoch, Consumer<CommandStore> forEach)
+ {
+ forEach(context, keys, epoch, epoch, forEach);
+ }
+ public void forEachSince(PreLoadContext context, Keys keys, long epoch, Consumer<CommandStore> forEach)
+ {
+ forEach(context, keys, epoch, Long.MAX_VALUE, forEach);
+ }
+
+ public void forEach(TxnRequest request, long minEpoch, long maxEpoch, Consumer<CommandStore> forEach)
+ {
+ forEach(request, request.scope(), minEpoch, maxEpoch, forEach);
}
public <T extends Collection<CommandStore>> T collect(Keys keys, long epoch, IntFunction<T> factory)
@@ -445,7 +477,7 @@ public abstract class CommandStores
return accumulator;
}
- <S, I1, I2, O> O foldl(Select<S> select, S scope, long minEpoch, long maxEpoch, Fold<? super I1, ? super I2, O> fold, I1 param1, I2 param2, IntFunction<? extends O> factory)
+ protected <S, I1, I2, O> O foldl(Select<S> select, S scope, long minEpoch, long maxEpoch, Fold<? super I1, ? super I2, O> fold, I1 param1, I2 param2, IntFunction<? extends O> factory)
{
ShardedRanges[] ranges = current.ranges;
O accumulator = null;
@@ -500,88 +532,35 @@ public abstract class CommandStores
throw new IllegalArgumentException();
}
- public static class Synchronized extends CommandStores
+ public static Future<?> forEachNonBlocking(Collection<CommandStore> commandStores, PreLoadContext scope, Consumer<CommandStore> forEach)
{
- public Synchronized(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory)
- {
- super(num, node, agent, store, progressLogFactory, CommandStore.Synchronized::new);
- }
-
- public Synchronized(Supplier supplier)
- {
- super(supplier);
- }
-
- @Override
- protected <S, T> T mapReduce(Select<S> select, S scope, long minEpoch, long maxEpoch, Function<? super CommandStore, T> map, BiFunction<T, T, T> reduce)
- {
- return foldl(select, scope, minEpoch, maxEpoch, (store, f, r, t) -> t == null ? f.apply(store) : r.apply(t, f.apply(store)), map, reduce, ignore -> null);
- }
-
- @Override
- protected <S> void forEach(Select<S> select, S scope, long minEpoch, long maxEpoch, Consumer<? super CommandStore> forEach)
- {
- foldl(select, scope, minEpoch, maxEpoch, (store, f, r, t) -> { f.accept(store); return null; }, forEach, null, ignore -> FALSE);
- }
+ List<Future<Void>> futures = new ArrayList<>(commandStores.size());
+ for (CommandStore commandStore : commandStores)
+ futures.add(commandStore.process(scope, forEach));
+ return FutureCombiner.allOf(futures);
}
- public static class SingleThread extends CommandStores
+ public static void forEachBlocking(Collection<CommandStore> commandStores, PreLoadContext scope, Consumer<CommandStore> forEach)
{
- public SingleThread(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory)
- {
- this(num, node, agent, store, progressLogFactory, CommandStore.SingleThread::new);
- }
-
- public SingleThread(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory, CommandStore.Factory shardFactory)
- {
- super(num, node, agent, store, progressLogFactory, shardFactory);
- }
-
- private <S, F, T> T mapReduce(Select<S> select, S scope, long minEpoch, long maxEpoch, F f, Fold<F, ?, List<Future<T>>> fold, BiFunction<T, T, T> reduce)
+ try
{
- List<Future<T>> futures = foldl(select, scope, minEpoch, maxEpoch, fold, f, null, ArrayList::new);
- if (futures == null)
- return null;
-
- T result = null;
- for (Future<T> future : futures)
- {
- try
- {
- T next = future.get();
- if (result == null) result = next;
- else result = reduce.apply(result, next);
- }
- catch (InterruptedException e)
- {
- throw new UncheckedInterruptedException(e);
- }
- catch (ExecutionException e)
- {
- throw new RuntimeException(e.getCause());
- }
- }
- return result;
+ forEachNonBlocking(commandStores, scope, forEach).get();
}
-
- @Override
- protected <S, T> T mapReduce(Select<S> select, S scope, long minEpoch, long maxEpoch, Function<? super CommandStore, T> map, BiFunction<T, T, T> reduce)
+ catch (InterruptedException e)
{
- return mapReduce(select, scope, minEpoch, maxEpoch, map, (store, f, i, t) -> { t.add(store.process(f)); return t; }, reduce);
+ throw new UncheckedInterruptedException(e);
}
-
- protected <S> void forEach(Select<S> select, S scope, long minEpoch, long maxEpoch, Consumer<? super CommandStore> forEach)
+ catch (ExecutionException e)
{
- mapReduce(select, scope, minEpoch, maxEpoch, forEach, (store, f, i, t) -> { t.add(store.process(f)); return t; }, (Void i1, Void i2) -> null);
+ throw new RuntimeException(e);
}
}
- public static class Debug extends SingleThread
+ public static <T> T mapReduce(Collection<CommandStore> commandStores, PreLoadContext scope, Function<? super CommandStore, T> map, BiFunction<T, T, T> reduce)
{
- public Debug(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory)
- {
- super(num, node, agent, store, progressLogFactory, CommandStore.Debug::new);
- }
+ List<Future<T>> futures = new ArrayList<>(commandStores.size());
+ for (CommandStore commandStore : commandStores)
+ futures.add(commandStore.process(scope, map));
+ return reduce(futures, reduce);
}
-
}
diff --git a/accord-core/src/main/java/accord/local/CommandsForKey.java b/accord-core/src/main/java/accord/local/CommandsForKey.java
index 5bf925e..13034b0 100644
--- a/accord-core/src/main/java/accord/local/CommandsForKey.java
+++ b/accord-core/src/main/java/accord/local/CommandsForKey.java
@@ -19,40 +19,77 @@
package accord.local;
import java.util.Iterator;
-import java.util.NavigableMap;
-import java.util.TreeMap;
import java.util.function.Consumer;
+import java.util.stream.Stream;
+import accord.api.Key;
import accord.primitives.Timestamp;
+import accord.primitives.TxnId;
import com.google.common.collect.Iterators;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-public class CommandsForKey implements Listener, Iterable<Command>
+import static accord.utils.Utils.*;
+
+public abstract class CommandsForKey implements Listener, Iterable<PartialCommand.WithDeps>
{
- // TODO: efficiency
- public final NavigableMap<Timestamp, Command> uncommitted = new TreeMap<>();
- public final NavigableMap<Timestamp, Command> committedById = new TreeMap<>();
- public final NavigableMap<Timestamp, Command> committedByExecuteAt = new TreeMap<>();
+ private static final Logger logger = LoggerFactory.getLogger(CommandsForKey.class);
+
+ public interface CommandTimeseries
+ {
+ PartialCommand.WithDeps get(Timestamp timestamp);
+ void add(Timestamp timestamp, Command command);
+ void remove(Timestamp timestamp);
+
+ boolean isEmpty();
+
+ /**
+ * All commands before (exclusive of) the given timestamp
+ */
+ Stream<PartialCommand.WithDeps> before(Timestamp timestamp);
+
+ /**
+ * All commands after (exclusive of) the given timestamp
+ */
+ Stream<PartialCommand.WithDeps> after(Timestamp timestamp);
- private Timestamp max = Timestamp.NONE;
+ /**
+ * All commands between (inclusive of) the given timestamps
+ */
+ Stream<PartialCommand.WithDeps> between(Timestamp min, Timestamp max);
- public Timestamp max()
+ Stream<PartialCommand.WithDeps> all();
+ }
+
+ public abstract Key key();
+ public abstract CommandTimeseries uncommitted();
+ public abstract CommandTimeseries committedById();
+ public abstract CommandTimeseries committedByExecuteAt();
+
+ public abstract Timestamp max();
+ public abstract void updateMax(Timestamp timestamp);
+
+ @Override
+ public PreLoadContext listenerPreLoadContext(TxnId caller)
{
- return max;
+ return PreLoadContext.contextFor(caller, listOf(key()));
}
@Override
public void onChange(Command command)
{
- max = Timestamp.max(max, command.executeAt());
+ logger.trace("cfk[{}]: updating as listener in response to change on {} with status {} ({})",
+ key(), command.txnId(), command.status(), command);
+ updateMax(command.executeAt());
switch (command.status())
{
case Applied:
case Executed:
case Committed:
- committedById.put(command.txnId(), command);
- committedByExecuteAt.put(command.executeAt(), command);
+ committedById().add(command.txnId(), command);
+ committedByExecuteAt().add(command.executeAt(), command);
case Invalidated:
- uncommitted.remove(command.txnId());
+ uncommitted().remove(command.txnId());
command.removeListener(this);
break;
}
@@ -60,28 +97,28 @@ public class CommandsForKey implements Listener, Iterable<Command>
public void register(Command command)
{
- max = Timestamp.max(max, command.executeAt());
- uncommitted.put(command.txnId(), command);
+ updateMax(command.executeAt());
+ uncommitted().add(command.txnId(), command);
command.addListener(this);
}
- public void forWitnessed(Timestamp minTs, Timestamp maxTs, Consumer<Command> consumer)
+ public void forWitnessed(Timestamp minTs, Timestamp maxTs, Consumer<PartialCommand> consumer)
{
- uncommitted.subMap(minTs, true, maxTs, true).values().stream()
+ uncommitted().between(minTs, maxTs)
.filter(cmd -> cmd.hasBeen(Status.PreAccepted)).forEach(consumer);
- committedById.subMap(minTs, true, maxTs, true).values().forEach(consumer);
- committedByExecuteAt.subMap(minTs, true, maxTs, true).values().stream()
+ committedById().between(minTs, maxTs).forEach(consumer);
+ committedByExecuteAt().between(minTs, maxTs)
.filter(cmd -> cmd.txnId().compareTo(minTs) < 0 || cmd.txnId().compareTo(maxTs) > 0).forEach(consumer);
}
@Override
- public Iterator<Command> iterator()
+ public Iterator<PartialCommand.WithDeps> iterator()
{
- return Iterators.concat(uncommitted.values().iterator(), committedByExecuteAt.values().iterator());
+ return Iterators.concat(uncommitted().all().iterator(), committedByExecuteAt().all().iterator());
}
public boolean isEmpty()
{
- return uncommitted.isEmpty() && committedById.isEmpty();
+ return uncommitted().isEmpty() && committedById().isEmpty();
}
}
diff --git a/accord-core/src/main/java/accord/local/Listener.java b/accord-core/src/main/java/accord/local/Listener.java
index 4846bfc..46ec122 100644
--- a/accord-core/src/main/java/accord/local/Listener.java
+++ b/accord-core/src/main/java/accord/local/Listener.java
@@ -18,7 +18,22 @@
package accord.local;
+import accord.primitives.TxnId;
+
public interface Listener
{
void onChange(Command command);
+
+ /**
+ * Scope needed to run onChange
+ */
+ PreLoadContext listenerPreLoadContext(TxnId caller);
+
+ /**
+ * Indicates the command to listener relationship doesn't need to persist across restarts
+ */
+ default boolean isTransient()
+ {
+ return false;
+ }
}
diff --git a/accord-core/src/main/java/accord/local/Listeners.java b/accord-core/src/main/java/accord/local/Listeners.java
index e1b2e1a..15b02a8 100644
--- a/accord-core/src/main/java/accord/local/Listeners.java
+++ b/accord-core/src/main/java/accord/local/Listeners.java
@@ -20,6 +20,6 @@ package accord.local;
import accord.utils.DeterministicIdentitySet;
-class Listeners extends DeterministicIdentitySet<Listener>
+public class Listeners extends DeterministicIdentitySet<Listener>
{
}
diff --git a/accord-core/src/main/java/accord/local/Node.java b/accord-core/src/main/java/accord/local/Node.java
index 1435142..375f88a 100644
--- a/accord-core/src/main/java/accord/local/Node.java
+++ b/accord-core/src/main/java/accord/local/Node.java
@@ -28,6 +28,7 @@ import java.util.function.IntFunction;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
+import accord.messages.*;
import com.google.common.annotations.VisibleForTesting;
import accord.api.*;
@@ -44,7 +45,6 @@ import accord.api.ProgressLog;
import accord.api.Scheduler;
import accord.api.DataStore;
import accord.coordinate.Recover;
-import accord.messages.Callback;
import accord.messages.CheckStatus.CheckStatusOk;
import accord.messages.ReplyContext;
import accord.messages.Request;
@@ -59,7 +59,9 @@ import accord.primitives.Keys;
import accord.primitives.Timestamp;
import accord.txn.Txn;
import accord.primitives.TxnId;
+import com.google.common.util.concurrent.Futures;
import org.apache.cassandra.utils.concurrent.Future;
+import org.apache.cassandra.utils.concurrent.FutureCombiner;
public class Node implements ConfigurationService.Listener
{
@@ -150,6 +152,11 @@ public class Node implements ConfigurationService.Listener
onTopologyUpdate(topology, false);
}
+ public CommandStores commandStores()
+ {
+ return commandStores;
+ }
+
public ConfigurationService configService()
{
return configService;
@@ -252,74 +259,74 @@ public class Node implements ConfigurationService.Listener
return nowSupplier.getAsLong();
}
- public void forEachLocal(Consumer<CommandStore> forEach)
+ public void forEachLocal(TxnRequest request, long epoch, Consumer<CommandStore> forEach)
{
- commandStores.forEach(forEach);
+ commandStores.forEach(request, epoch, forEach);
}
- public void forEachLocal(Keys keys, long epoch, Consumer<CommandStore> forEach)
+ public void forEachLocal(TxnRequest request, long minEpoch, long maxEpoch, Consumer<CommandStore> forEach)
{
- commandStores.forEach(keys, epoch, forEach);
+ commandStores.forEach(request, minEpoch, maxEpoch, forEach);
}
- public void forEachLocal(Keys keys, long minEpoch, long maxEpoch, Consumer<CommandStore> forEach)
+ public void forEachLocal(PreLoadContext context, Keys keys, long minEpoch, long maxEpoch, Consumer<CommandStore> forEach)
{
- commandStores.forEach(keys, minEpoch, maxEpoch, forEach);
+ commandStores.forEach(context, keys, minEpoch, maxEpoch, forEach);
}
- public void forEachLocal(Keys keys, Timestamp minAt, Timestamp maxAt, Consumer<CommandStore> forEach)
+ public void forEachLocalSince(PreLoadContext context, Keys keys, Timestamp since, Consumer<CommandStore> forEach)
{
- commandStores.forEach(keys, minAt.epoch, maxAt.epoch, forEach);
+ commandStores.forEach(context, keys, since.epoch, Long.MAX_VALUE, forEach);
}
- public void forEachLocalSince(Keys keys, Timestamp since, Consumer<CommandStore> forEach)
+ public void forEachLocalSince(TxnRequest request, Timestamp since, Consumer<CommandStore> forEach)
{
- commandStores.forEach(keys, since.epoch, Long.MAX_VALUE, forEach);
+ commandStores.forEach(request, since.epoch, Long.MAX_VALUE, forEach);
}
- public void forEachLocalSince(Keys keys, long sinceEpoch, Consumer<CommandStore> forEach)
+ public void forEachLocalSince(PreLoadContext context, Keys keys, long sinceEpoch, Consumer<CommandStore> forEach)
{
- commandStores.forEach(keys, sinceEpoch, Long.MAX_VALUE, forEach);
+ commandStores.forEachSince(context, keys, sinceEpoch, forEach);
}
- public <T> T mapReduceLocal(Keys keys, Timestamp at, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+ public <T> T mapReduceLocal(TxnRequest request, long minEpoch, long maxEpoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
{
- return commandStores.mapReduce(keys, at.epoch, at.epoch, map, reduce);
+ return commandStores.mapReduce(request, request.scope(), minEpoch, maxEpoch, map, reduce);
}
- public <T> T mapReduceLocal(Keys keys, long epoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+ public <T> List<T> mapLocal(TxnRequest request, long minEpoch, long maxEpoch, Function<CommandStore, T> map)
{
- return commandStores.mapReduce(keys, epoch, epoch, map, reduce);
+ return Futures.getUnchecked(FutureCombiner.allOf(commandStores.mapAsync(request, request.scope(), minEpoch, maxEpoch, map)));
}
- public <T> T mapReduceLocal(Keys keys, long minEpoch, long maxEpoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+ public <T> T mapReduceLocalSince(PreLoadContext context, Keys keys, Timestamp since, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
{
- return commandStores.mapReduce(keys, minEpoch, maxEpoch, map, reduce);
+ return commandStores.mapReduce(context, keys, since.epoch, Long.MAX_VALUE, map, reduce);
}
- public <T> T mapReduceLocalSince(Keys keys, Timestamp since, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+ public <T> List<T> mapLocalSince(PreLoadContext context, Keys keys, Timestamp since, Function<CommandStore, T> map)
{
- return commandStores.mapReduce(keys, since.epoch, Long.MAX_VALUE, map, reduce);
+ return Futures.getUnchecked(FutureCombiner.allOf(commandStores.mapAsync(context, keys, since.epoch, Long.MAX_VALUE, map)));
}
- public <T> T ifLocal(Key key, Timestamp at, Function<CommandStore, T> ifLocal)
+ public <T> T ifLocal(PreLoadContext context, Key key, Timestamp at, Function<CommandStore, T> ifLocal)
{
- return ifLocal(key, at.epoch, ifLocal);
+ return ifLocal(context, key, at.epoch, ifLocal);
}
- public <T> T ifLocal(Key key, long epoch, Function<CommandStore, T> ifLocal)
+ public <T> T ifLocal(PreLoadContext context, Key key, long epoch, Function<CommandStore, T> ifLocal)
{
- return commandStores.mapReduce(key, epoch, ifLocal, (a, b) -> { throw new IllegalStateException();} );
+ return commandStores.mapReduce(context, key, epoch, ifLocal, (a, b) -> { throw new IllegalStateException();} );
}
- public <T> T ifLocalSince(Key key, Timestamp since, Function<CommandStore, T> ifLocal)
+ public <T> T ifLocalSince(PreLoadContext context, Key key, Timestamp since, Function<CommandStore, T> ifLocal)
{
- return ifLocalSince(key, since.epoch, ifLocal);
+ return ifLocalSince(context, key, since.epoch, ifLocal);
}
- public <T> T ifLocalSince(Key key, long epoch, Function<CommandStore, T> ifLocal)
+ public <T> T ifLocalSince(PreLoadContext context, Key key, long epoch, Function<CommandStore, T> ifLocal)
{
- return commandStores.mapReduceSince(key, epoch, ifLocal, (a, b) -> { throw new IllegalStateException();} );
+ return commandStores.mapReduceSince(context, key, epoch, ifLocal, (a, b) -> { throw new IllegalStateException();} );
}
public <T extends Collection<CommandStore>> T collectLocal(Keys keys, Timestamp at, IntFunction<T> factory)
@@ -405,7 +412,7 @@ public class Node implements ConfigurationService.Listener
public Future<Result> coordinate(TxnId txnId, Txn txn)
{
- // TODO: The combination of updating the epoch of the next timestamp with epochs we don’t have topologies for,
+ // TODO: The combination of updating the epoch of the next timestamp with epochs we don't have topologies for,
// and requiring preaccept to talk to its topology epoch means that learning of a new epoch via timestamp
// (ie not via config service) will halt any new txns from a node until it receives this topology
Future<Result> result = withEpoch(txnId.epoch, () -> initiateCoordination(txnId, txn));
@@ -417,11 +424,11 @@ public class Node implements ConfigurationService.Listener
private Future<Result> initiateCoordination(TxnId txnId, Txn txn)
{
- Key homeKey = trySelectHomeKey(txnId, txn.keys);
+ Key homeKey = trySelectHomeKey(txnId, txn.keys());
if (homeKey == null)
{
homeKey = selectRandomHomeKey(txnId);
- txn = new Txn(txn.keys.with(homeKey), txn.read, txn.query, txn.update);
+ txn = new Txn.InMemory(txn.keys().with(homeKey), txn.read(), txn.query(), txn.update());
}
return Coordinate.coordinate(this, txnId, txn, homeKey);
}
diff --git a/accord-core/src/main/java/accord/local/PartialCommand.java b/accord-core/src/main/java/accord/local/PartialCommand.java
new file mode 100644
index 0000000..95efdd0
--- /dev/null
+++ b/accord-core/src/main/java/accord/local/PartialCommand.java
@@ -0,0 +1,47 @@
+package accord.local;
+
+import accord.primitives.Deps;
+import accord.primitives.Keys;
+import accord.primitives.Timestamp;
+import accord.primitives.TxnId;
+import accord.txn.Txn;
+
+/**
+ * A minimal, read only view of a full command. Provided so C* can denormalize command data onto
+ * CommandsForKey and Command implementations. Implementer must guarantee that PartialCommand
+ * implementations behave as an immutable view and are always in sync with the 'real' command;
+ */
+public interface PartialCommand
+{
+ interface WithDeps extends PartialCommand
+ {
+ Deps savedDeps();
+ }
+
+ TxnId txnId();
+ Txn txn();
+
+ default Keys someKeys()
+ {
+ if (txn() != null)
+ return txn().keys();
+
+ return null;
+ }
+
+ Timestamp executeAt();
+
+ Status status();
+
+ default boolean hasBeen(Status status)
+ {
+ return status().hasBeen(status);
+ }
+
+ default boolean is(Status status)
+ {
+ return status() == status;
+ }
+
+ void removeListener(Listener listener);
+}
diff --git a/accord-core/src/main/java/accord/local/PreLoadContext.java b/accord-core/src/main/java/accord/local/PreLoadContext.java
new file mode 100644
index 0000000..65946e5
--- /dev/null
+++ b/accord-core/src/main/java/accord/local/PreLoadContext.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 accord.local;
+
+import accord.api.Key;
+import accord.primitives.TxnId;
+
+import java.util.Collections;
+
+/**
+ * Lists txnids and keys of commands and commands for key that will be needed for an operation. Used
+ * to ensure the necessary state is in memory for an operation before it executes.
+ */
+public interface PreLoadContext
+{
+ /**
+ * @return ids of the {@link Command} objects that need to be loaded into memory before this operation is run
+ */
+ Iterable<TxnId> txnIds();
+
+ /**
+ * @return keys of the {@link CommandsForKey} objects that need to be loaded into memory before this operation is run
+ */
+ Iterable<Key> keys();
+
+ static PreLoadContext contextFor(Iterable<TxnId> txnIds, Iterable<Key> keys)
+ {
+ return new PreLoadContext()
+ {
+ @Override
+ public Iterable<TxnId> txnIds() { return txnIds; }
+
+ @Override
+ public Iterable<Key> keys() { return keys; }
+ };
+ }
+
+ static PreLoadContext contextFor(TxnId txnId, Iterable<Key> keys)
+ {
+ return contextFor(Collections.singleton(txnId), keys);
+ }
+
+ static PreLoadContext contextFor(TxnId txnId)
+ {
+ return contextFor(Collections.singleton(txnId), Collections.emptyList());
+ }
+
+ static PreLoadContext contextFor(Key key)
+ {
+ return contextFor(Collections.emptyList(), Collections.singleton(key));
+ }
+}
diff --git a/accord-core/src/main/java/accord/messages/Accept.java b/accord-core/src/main/java/accord/messages/Accept.java
index 06e7a48..14e347f 100644
--- a/accord-core/src/main/java/accord/messages/Accept.java
+++ b/accord-core/src/main/java/accord/messages/Accept.java
@@ -18,22 +18,32 @@
package accord.messages;
+import accord.primitives.*;
+import accord.local.PreLoadContext;
import accord.messages.TxnRequest.WithUnsynced;
import accord.local.Node.Id;
-import accord.topology.Topologies;
import accord.api.Key;
-import accord.primitives.Ballot;
+import accord.local.CommandStore;
+import accord.topology.Topologies;
import accord.local.Node;
-import accord.primitives.Timestamp;
import accord.local.Command;
-import accord.primitives.Deps;
import accord.txn.Txn;
-import accord.primitives.TxnId;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.Collections;
import static accord.messages.PreAccept.calculateDeps;
public class Accept extends WithUnsynced
{
+ public static class SerializerSupport
+ {
+ public static Accept create(Keys scope, long epoch, TxnId txnId, Ballot ballot, Key homeKey, Txn txn, Timestamp executeAt, Deps deps)
+ {
+ return new Accept(scope, epoch, txnId, ballot, homeKey, txn, executeAt, deps);
+ }
+ }
+
public final Ballot ballot;
public final Key homeKey;
public final Txn txn;
@@ -42,7 +52,7 @@ public class Accept extends WithUnsynced
public Accept(Id to, Topologies topologies, Ballot ballot, TxnId txnId, Key homeKey, Txn txn, Timestamp executeAt, Deps deps)
{
- super(to, topologies, txn.keys, txnId);
+ super(to, topologies, txn.keys(), txnId);
this.ballot = ballot;
this.homeKey = homeKey;
this.txn = txn;
@@ -50,18 +60,33 @@ public class Accept extends WithUnsynced
this.deps = deps;
}
- public void process(Node node, Node.Id replyToNode, ReplyContext replyContext)
+ protected Accept(Keys scope, long epoch, TxnId txnId, Ballot ballot, Key homeKey, Txn txn, Timestamp executeAt, Deps deps)
+ {
+ super(scope, epoch, txnId);
+ this.ballot = ballot;
+ this.homeKey = homeKey;
+ this.txn = txn;
+ this.executeAt = executeAt;
+ this.deps = deps;
+ }
+
+ @VisibleForTesting
+ public AcceptReply process(CommandStore instance, Key progressKey)
{
- Key progressKey = progressKey(node, homeKey);
// TODO: when we begin expunging old epochs we need to ensure we handle the case where we do not fully handle the keys;
// since this will likely imply the transaction has been applied or aborted we can indicate the coordinator
// should enquire as to the result
- node.reply(replyToNode, replyContext, node.mapReduceLocal(scope(), minEpoch, executeAt.epoch, instance -> {
- Command command = instance.command(txnId);
- if (!command.accept(ballot, txn, homeKey, progressKey, executeAt, deps))
- return new AcceptNack(txnId, command.promised());
- return new AcceptOk(txnId, calculateDeps(instance, txnId, txn, executeAt));
- }, (r1, r2) -> {
+ Command command = instance.command(txnId);
+ if (!command.accept(ballot, txn, homeKey, progressKey, executeAt, deps))
+ return new AcceptNack(txnId, command.promised());
+ return new AcceptOk(txnId, calculateDeps(instance, txnId, txn, executeAt));
+ }
+
+ public void process(Node node, Node.Id replyToNode, ReplyContext replyContext)
+ {
+ Key progressKey = progressKey(node, homeKey);
+ node.reply(replyToNode, replyContext, node.mapReduceLocal(this, minEpoch, executeAt.epoch, cs -> process(cs, progressKey),
+ (r1, r2) -> {
if (!r1.isOK()) return r1;
if (!r2.isOK()) return r2;
AcceptOk ok1 = (AcceptOk) r1;
@@ -72,13 +97,26 @@ public class Accept extends WithUnsynced
}));
}
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Collections.singleton(txnId);
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return txn.keys();
+ }
+
@Override
public MessageType type()
{
return MessageType.ACCEPT_REQ;
}
- public static class Invalidate implements EpochRequest
+ // TODO (now): can EpochRequest inherit TxnOperation?
+ public static class Invalidate implements EpochRequest, PreLoadContext
{
public final Ballot ballot;
public final TxnId txnId;
@@ -93,7 +131,7 @@ public class Accept extends WithUnsynced
public void process(Node node, Node.Id replyToNode, ReplyContext replyContext)
{
- node.reply(replyToNode, replyContext, node.ifLocal(someKey, txnId.epoch, instance -> {
+ node.reply(replyToNode, replyContext, node.ifLocal(this, someKey, txnId.epoch, instance -> {
Command command = instance.command(txnId);
if (!command.acceptInvalidate(ballot))
return new AcceptNack(txnId, command.promised());
@@ -101,6 +139,18 @@ public class Accept extends WithUnsynced
}));
}
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Collections.singleton(txnId);
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return Collections.emptyList();
+ }
+
@Override
public MessageType type()
{
diff --git a/accord-core/src/main/java/accord/messages/Apply.java b/accord-core/src/main/java/accord/messages/Apply.java
index abcc10d..f96f983 100644
--- a/accord-core/src/main/java/accord/messages/Apply.java
+++ b/accord-core/src/main/java/accord/messages/Apply.java
@@ -19,6 +19,8 @@
package accord.messages;
import accord.api.Key;
+import accord.primitives.Keys;
+import accord.utils.ReducingFuture;
import accord.local.Node;
import accord.local.Node.Id;
import accord.api.Result;
@@ -28,15 +30,28 @@ import accord.primitives.Timestamp;
import accord.txn.Writes;
import accord.txn.Txn;
import accord.primitives.TxnId;
+import com.google.common.collect.Iterables;
+import org.apache.cassandra.utils.concurrent.Future;
+
+import java.util.Collections;
+import java.util.List;
import static accord.messages.MessageType.APPLY_REQ;
import static accord.messages.MessageType.APPLY_RSP;
public class Apply extends TxnRequest
{
+ public static class SerializationSupport
+ {
+ public static Apply create(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, Writes writes, Result result)
+ {
+ return new Apply(scope, waitForEpoch, txnId, txn, homeKey, executeAt, deps, writes, result);
+ }
+ }
+
public final TxnId txnId;
public final Txn txn;
- protected final Key homeKey;
+ public final Key homeKey;
public final Timestamp executeAt;
public final Deps deps;
public final Writes writes;
@@ -44,7 +59,7 @@ public class Apply extends TxnRequest
public Apply(Node.Id to, Topologies topologies, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, Writes writes, Result result)
{
- super(to, topologies, txn.keys);
+ super(to, topologies, txn.keys());
this.txnId = txnId;
this.txn = txn;
this.homeKey = homeKey;
@@ -54,13 +69,41 @@ public class Apply extends TxnRequest
this.result = result;
}
+ protected Apply(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, Writes writes, Result result)
+ {
+ super(scope, waitForEpoch);
+ this.txnId = txnId;
+ this.txn = txn;
+ this.homeKey = homeKey;
+ this.executeAt = executeAt;
+ this.deps = deps;
+ this.writes = writes;
+ this.result = result;
+ }
+
public void process(Node node, Id replyToNode, ReplyContext replyContext)
{
- Key progressKey = node.trySelectProgressKey(txnId, txn.keys, homeKey);
- node.forEachLocalSince(scope(), executeAt,
- instance -> instance.command(txnId).apply(txn, homeKey, progressKey, executeAt, deps, writes, result));
- // note, we do not also commit here if txnId.epoch != executeAt.epoch, as the scope() for a commit would be different
- node.reply(replyToNode, replyContext, ApplyOk.INSTANCE);
+ Key progressKey = node.trySelectProgressKey(txnId, txn.keys(), homeKey);
+ List<Future<Void>> futures = node.mapLocalSince(this, scope(), executeAt, instance -> instance.command(txnId).apply(txn, homeKey, progressKey, executeAt, deps, writes, result));
+
+ ReducingFuture.reduce(futures, (l, r) -> null).addCallback((unused, failure) -> {
+ if (failure == null)
+ // TODO: notify coordinator of failures
+ // note, we do not also commit here if txnId.epoch != executeAt.epoch, as the scope() for a commit would be different
+ node.reply(replyToNode, replyContext, ApplyOk.INSTANCE);
+ });
+ }
+
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Iterables.concat(Collections.singleton(txnId), deps.txnIds());
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return txn.keys();
}
@Override
diff --git a/accord-core/src/main/java/accord/messages/BeginInvalidate.java b/accord-core/src/main/java/accord/messages/BeginInvalidate.java
index dbd3452..d9954c6 100644
--- a/accord-core/src/main/java/accord/messages/BeginInvalidate.java
+++ b/accord-core/src/main/java/accord/messages/BeginInvalidate.java
@@ -24,6 +24,7 @@ import accord.local.Command;
import accord.local.Node;
import accord.local.Node.Id;
import accord.local.Status;
+import accord.local.PreLoadContext;
import accord.messages.BeginRecovery.RecoverNack;
import accord.messages.BeginRecovery.RecoverOk;
import accord.messages.BeginRecovery.RecoverReply;
@@ -34,11 +35,13 @@ import accord.txn.Txn;
import accord.primitives.TxnId;
import accord.txn.Writes;
-public class BeginInvalidate implements EpochRequest
+import java.util.Collections;
+
+public class BeginInvalidate implements EpochRequest, PreLoadContext
{
- final Ballot ballot;
- final TxnId txnId;
- final Key someKey;
+ public final Ballot ballot;
+ public final TxnId txnId;
+ public final Key someKey;
public BeginInvalidate(TxnId txnId, Key someKey, Ballot ballot)
{
@@ -49,7 +52,7 @@ public class BeginInvalidate implements EpochRequest
public void process(Node node, Id replyToNode, ReplyContext replyContext)
{
- RecoverReply reply = node.ifLocal(someKey, txnId, instance -> {
+ RecoverReply reply = node.ifLocal(this, someKey, txnId, instance -> {
Command command = instance.command(txnId);
if (!command.preAcceptInvalidate(ballot))
@@ -62,6 +65,18 @@ public class BeginInvalidate implements EpochRequest
node.reply(replyToNode, replyContext, reply);
}
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Collections.singleton(txnId);
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return Collections.emptyList();
+ }
+
@Override
public long waitForEpoch()
{
@@ -106,6 +121,12 @@ public class BeginInvalidate implements EpochRequest
{
return toString("InvalidateOk");
}
+
+ @Override
+ public MessageType type()
+ {
+ return MessageType.BEGIN_INVALIDATE_RSP;
+ }
}
public static class InvalidateNack extends RecoverNack
@@ -130,5 +151,11 @@ public class BeginInvalidate implements EpochRequest
{
return "InvalidateNack{supersededBy:" + supersededBy + '}';
}
+
+ @Override
+ public MessageType type()
+ {
+ return MessageType.BEGIN_INVALIDATE_RSP;
+ }
}
}
diff --git a/accord-core/src/main/java/accord/messages/BeginRecovery.java b/accord-core/src/main/java/accord/messages/BeginRecovery.java
index 8fb8445..610e622 100644
--- a/accord-core/src/main/java/accord/messages/BeginRecovery.java
+++ b/accord-core/src/main/java/accord/messages/BeginRecovery.java
@@ -18,6 +18,8 @@
package accord.messages;
+import accord.api.Key;
+import accord.local.*;
import accord.api.Result;
import accord.coordinate.Persist;
import accord.topology.Topologies;
@@ -25,22 +27,18 @@ import accord.topology.Topologies;
import java.util.List;
import java.util.stream.Stream;
-import accord.api.Key;
-import accord.local.CommandStore;
-import accord.local.CommandsForKey;
import accord.primitives.Keys;
import accord.txn.Writes;
import accord.primitives.Ballot;
-import accord.local.Node;
import accord.local.Node.Id;
import accord.primitives.Timestamp;
-import accord.local.Command;
import accord.primitives.Deps;
-import accord.local.Status;
import accord.txn.Txn;
import accord.primitives.TxnId;
import com.google.common.base.Preconditions;
+import java.util.Collections;
+
import static accord.local.Status.Accepted;
import static accord.local.Status.Applied;
import static accord.local.Status.Committed;
@@ -50,14 +48,23 @@ import static accord.messages.PreAccept.calculateDeps;
public class BeginRecovery extends TxnRequest
{
- final TxnId txnId;
- final Txn txn;
- final Key homeKey;
- final Ballot ballot;
+ public final TxnId txnId;
+ public final Txn txn;
+ public final Key homeKey;
+ public final Ballot ballot;
public BeginRecovery(Id to, Topologies topologies, TxnId txnId, Txn txn, Key homeKey, Ballot ballot)
{
- super(to, topologies, txn.keys);
+ super(to, topologies, txn.keys());
+ this.txnId = txnId;
+ this.txn = txn;
+ this.homeKey = homeKey;
+ this.ballot = ballot;
+ }
+
+ public BeginRecovery(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Key homeKey, Ballot ballot)
+ {
+ super(scope, waitForEpoch);
this.txnId = txnId;
this.txn = txn;
this.homeKey = homeKey;
@@ -66,8 +73,8 @@ public class BeginRecovery extends TxnRequest
public void process(Node node, Id replyToNode, ReplyContext replyContext)
{
- Key progressKey = node.selectProgressKey(txnId, txn.keys, homeKey);
- RecoverReply reply = node.mapReduceLocal(scope(), txnId.epoch, txnId.epoch, instance -> {
+ Key progressKey = node.selectProgressKey(txnId, txn.keys(), homeKey);
+ RecoverReply reply = node.mapReduceLocal(this, txnId.epoch, txnId.epoch, instance -> {
Command command = instance.command(txnId);
if (!command.recover(txn, homeKey, progressKey, ballot))
@@ -86,19 +93,19 @@ public class BeginRecovery extends TxnRequest
}
else
{
- rejectsFastPath = uncommittedStartedAfter(instance, txnId, txn.keys)
+ rejectsFastPath = uncommittedStartedAfter(instance, txnId, txn.keys())
.filter(c -> c.hasBeen(Accepted))
.anyMatch(c -> !c.savedDeps().contains(txnId));
if (!rejectsFastPath)
- rejectsFastPath = committedExecutesAfter(instance, txnId, txn.keys)
+ rejectsFastPath = committedExecutesAfter(instance, txnId, txn.keys())
.anyMatch(c -> !c.savedDeps().contains(txnId));
// TODO: introduce some good unit tests for verifying these two functions in a real repair scenario
// committed txns with an earlier txnid and have our txnid as a dependency
- earlierCommittedWitness = committedStartedBeforeAndDidWitness(instance, txnId, txn.keys);
+ earlierCommittedWitness = committedStartedBeforeAndDidWitness(instance, txnId, txn.keys());
// accepted txns with an earlier txnid that don't have our txnid as a dependency
- earlierAcceptedNoWitness = acceptedStartedBeforeAndDidNotWitness(instance, txnId, txn.keys);
+ earlierAcceptedNoWitness = acceptedStartedBeforeAndDidNotWitness(instance, txnId, txn.keys());
}
return new RecoverOk(txnId, command.status(), command.accepted(), command.executeAt(), deps, earlierCommittedWitness, earlierAcceptedNoWitness, rejectsFastPath, command.writes(), command.result());
}, (r1, r2) -> { // TODO: reduce function that constructs a list to reduce at the end
@@ -164,6 +171,18 @@ public class BeginRecovery extends TxnRequest
}
}
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Collections.singleton(txnId);
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return txn.keys();
+ }
+
@Override
public MessageType type()
{
@@ -303,7 +322,7 @@ public class BeginRecovery extends TxnRequest
// committed txns with an earlier txnid and have our txnid as a dependency
builder.nextKey(key);
- forKey.uncommitted.headMap(txnId, false).forEach((ts, command) -> {
+ forKey.uncommitted().before(txnId).forEach(command -> {
if (command.is(Accepted) && !command.savedDeps().contains(txnId) && command.executeAt().compareTo(txnId) > 0)
builder.add(command.txnId());
});
@@ -323,7 +342,7 @@ public class BeginRecovery extends TxnRequest
// committed txns with an earlier txnid and have our txnid as a dependency
builder.nextKey(key);
- forKey.committedById.headMap(txnId, false).forEach((ts, command) -> {
+ forKey.committedById().before(txnId).forEach(command -> {
if (command.savedDeps().contains(txnId))
builder.add(command.txnId());
});
@@ -332,23 +351,23 @@ public class BeginRecovery extends TxnRequest
}
}
- private static Stream<Command> uncommittedStartedAfter(CommandStore commandStore, TxnId startedAfter, Keys keys)
+ private static Stream<PartialCommand.WithDeps> uncommittedStartedAfter(CommandStore commandStore, TxnId startedAfter, Keys keys)
{
return keys.stream().flatMap(key -> {
CommandsForKey forKey = commandStore.maybeCommandsForKey(key);
if (forKey == null)
return Stream.of();
- return forKey.uncommitted.tailMap(startedAfter, false).values().stream();
+ return forKey.uncommitted().after(startedAfter);
});
}
- private static Stream<Command> committedExecutesAfter(CommandStore commandStore, TxnId startedAfter, Keys keys)
+ private static Stream<PartialCommand.WithDeps> committedExecutesAfter(CommandStore commandStore, TxnId startedAfter, Keys keys)
{
return keys.stream().flatMap(key -> {
CommandsForKey forKey = commandStore.maybeCommandsForKey(key);
if (forKey == null)
return Stream.of();
- return forKey.committedByExecuteAt.tailMap(startedAfter, false).values().stream();
+ return forKey.committedByExecuteAt().after(startedAfter);
});
}
}
diff --git a/accord-core/src/main/java/accord/messages/CheckStatus.java b/accord-core/src/main/java/accord/messages/CheckStatus.java
index 984297d..9bc94fa 100644
--- a/accord-core/src/main/java/accord/messages/CheckStatus.java
+++ b/accord-core/src/main/java/accord/messages/CheckStatus.java
@@ -27,11 +27,14 @@ import accord.local.Status;
import accord.primitives.Ballot;
import accord.primitives.Deps;
import accord.primitives.Timestamp;
+import accord.local.PreLoadContext;
import accord.txn.Txn;
import accord.primitives.TxnId;
import accord.txn.Writes;
-public class CheckStatus implements Request
+import java.util.Collections;
+
+public class CheckStatus implements Request, PreLoadContext
{
// order is important
public enum IncludeInfo
@@ -49,10 +52,10 @@ public class CheckStatus implements Request
}
}
- final TxnId txnId;
- final Key key; // the key's commandStore to consult - not necessarily the homeKey
- final long epoch;
- final IncludeInfo includeInfo;
+ public final TxnId txnId;
+ public final Key key; // the key's commandStore to consult - not necessarily the homeKey
+ public final long epoch;
+ public final IncludeInfo includeInfo;
public CheckStatus(TxnId txnId, Key key, long epoch, IncludeInfo includeInfo)
{
@@ -62,10 +65,22 @@ public class CheckStatus implements Request
this.includeInfo = includeInfo;
}
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Collections.singleton(txnId);
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return Collections.emptyList();
+ }
+
public void process(Node node, Id replyToNode, ReplyContext replyContext)
{
- Reply reply = node.ifLocal(key, epoch, instance -> {
+ Reply reply = node.ifLocal(this, key, epoch, instance -> {
Command command = instance.command(txnId);
boolean includeInfo = this.includeInfo.include(command.status());
if (includeInfo)
@@ -107,7 +122,7 @@ public class CheckStatus implements Request
public final boolean isCoordinating;
public final boolean hasExecutedOnAllShards;
- CheckStatusOk(Status status, Ballot promised, Ballot accepted, boolean isCoordinating, boolean hasExecutedOnAllShards)
+ public CheckStatusOk(Status status, Ballot promised, Ballot accepted, boolean isCoordinating, boolean hasExecutedOnAllShards)
{
this.status = status;
this.promised = promised;
@@ -187,7 +202,7 @@ public class CheckStatus implements Request
public final Writes writes;
public final Result result;
- CheckStatusOkFull(Status status, Ballot promised, Ballot accepted, boolean isCoordinating, boolean hasExecutedOnAllShards,
+ public CheckStatusOkFull(Status status, Ballot promised, Ballot accepted, boolean isCoordinating, boolean hasExecutedOnAllShards,
Txn txn, Key homeKey, Timestamp executeAt, Deps deps, Writes writes, Result result)
{
super(status, promised, accepted, isCoordinating, hasExecutedOnAllShards);
@@ -253,7 +268,7 @@ public class CheckStatus implements Request
return MessageType.CHECK_STATUS_RSP;
}
- static CheckStatusNack nack()
+ public static CheckStatusNack nack()
{
return instance;
}
diff --git a/accord-core/src/main/java/accord/messages/Commit.java b/accord-core/src/main/java/accord/messages/Commit.java
index 004d683..4daab5b 100644
--- a/accord-core/src/main/java/accord/messages/Commit.java
+++ b/accord-core/src/main/java/accord/messages/Commit.java
@@ -19,9 +19,11 @@
package accord.messages;
import java.util.Collections;
+import java.util.List;
import java.util.Set;
import accord.api.Key;
+import accord.utils.ReducingFuture;
import accord.local.Node;
import accord.local.Node.Id;
import accord.topology.Topologies;
@@ -30,17 +32,31 @@ import accord.primitives.Timestamp;
import accord.primitives.Deps;
import accord.txn.Txn;
import accord.primitives.TxnId;
+import com.google.common.collect.Iterables;
+import org.apache.cassandra.utils.concurrent.Future;
// TODO: CommitOk responses, so we can send again if no reply received? Or leave to recovery?
public class Commit extends ReadData
{
- public final Deps deps;
+ public static class SerializerSupport
+ {
+ public static Commit create(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Deps deps, Key homeKey, Timestamp executeAt, boolean read)
+ {
+ return new Commit(scope, waitForEpoch, txnId, txn, deps, homeKey, executeAt, read);
+ }
+ }
+
public final boolean read;
public Commit(Id to, Topologies topologies, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, boolean read)
{
- super(to, topologies, txnId, txn, homeKey, executeAt);
- this.deps = deps;
+ super(to, topologies, txnId, txn, deps, homeKey, executeAt);
+ this.read = read;
+ }
+
+ public Commit(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Deps deps, Key homeKey, Timestamp executeAt, boolean read)
+ {
+ super(scope, waitForEpoch, txnId, txn, deps, homeKey, executeAt);
this.read = read;
}
@@ -99,19 +115,39 @@ public class Commit extends ReadData
{
for (Node.Id to : commitTo.nodes())
{
- Invalidate send = new Invalidate(to, commitTo, txnId, someKeys);
+ // TODO (now) confirm somekeys == txnkeys
+ Invalidate send = new Invalidate(to, commitTo, txnId, someKeys, someKeys);
node.send(to, send);
}
}
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Iterables.concat(Collections.singleton(txnId), deps.txnIds());
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return txn.keys();
+ }
+
public void process(Node node, Id from, ReplyContext replyContext)
{
- Key progressKey = node.trySelectProgressKey(txnId, txn.keys, homeKey);
- node.forEachLocal(scope(), txnId.epoch, executeAt.epoch,
- instance -> instance.command(txnId).commit(txn, homeKey, progressKey, executeAt, deps));
+ Key progressKey = node.trySelectProgressKey(txnId, txn.keys(), homeKey);
+ List<Future<Void>> futures = node.mapLocal(this, txnId.epoch, executeAt.epoch,
+ instance -> instance.command(txnId).commitAndBeginExecution(txn, homeKey, progressKey, executeAt, deps));
if (read)
- super.process(node, from, replyContext);
+ {
+ ReducingFuture.reduce(futures, (l, r) -> null).addCallback((unused, throwable) -> {
+ if (throwable == null)
+ super.process(node, from, replyContext);
+ else
+ node.reply(from, replyContext, new ReadNack());
+ });
+ }
}
@Override
@@ -132,17 +168,46 @@ public class Commit extends ReadData
public static class Invalidate extends TxnRequest
{
- final TxnId txnId;
+ public static class SerializerSupport
+ {
+ public static Invalidate create(Keys scope, long waitForEpoch, TxnId txnId, Keys txnKeys)
+ {
+ return new Invalidate(scope, waitForEpoch, txnId, txnKeys);
+ }
+ }
+
+ public final TxnId txnId;
+ public final Keys txnKeys;
- public Invalidate(Id to, Topologies topologies, TxnId txnId, Keys someKeys)
+ public Invalidate(Id to, Topologies topologies, TxnId txnId, Keys txnKeys, Keys someKeys)
{
super(to, topologies, someKeys);
this.txnId = txnId;
+ this.txnKeys = txnKeys;
+ }
+
+ protected Invalidate(Keys scope, long waitForEpoch, TxnId txnId, Keys txnKeys)
+ {
+ super(scope, waitForEpoch);
+ this.txnId = txnId;
+ this.txnKeys = txnKeys;
+ }
+
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Collections.singleton(txnId);
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return Collections.emptyList();
}
public void process(Node node, Id from, ReplyContext replyContext)
{
- node.forEachLocal(scope(), txnId.epoch, instance -> instance.command(txnId).commitInvalidate());
+ node.forEachLocal(this, txnId.epoch, instance -> instance.command(txnId).commitInvalidate());
}
@Override
diff --git a/accord-core/src/main/java/accord/messages/InformOfPersistence.java b/accord-core/src/main/java/accord/messages/InformOfPersistence.java
index 9cd02c5..c9f44d4 100644
--- a/accord-core/src/main/java/accord/messages/InformOfPersistence.java
+++ b/accord-core/src/main/java/accord/messages/InformOfPersistence.java
@@ -18,23 +18,26 @@
package accord.messages;
+import java.util.Collections;
import java.util.Set;
import accord.api.Key;
+import accord.local.Command;
import accord.local.Node;
import accord.local.Node.Id;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
+import accord.local.PreLoadContext;
import static accord.messages.InformOfTxn.InformOfTxnNack.nack;
import static accord.messages.InformOfTxn.InformOfTxnOk.ok;
-public class InformOfPersistence implements Request
+public class InformOfPersistence implements Request, PreLoadContext
{
- final TxnId txnId;
- final Key homeKey;
- final Timestamp executeAt;
- final Set<Id> persistedOn;
+ public final TxnId txnId;
+ public final Key homeKey;
+ public final Timestamp executeAt;
+ public final Set<Id> persistedOn;
public InformOfPersistence(TxnId txnId, Key homeKey, Timestamp executeAt, Set<Id> persistedOn)
{
@@ -46,9 +49,10 @@ public class InformOfPersistence implements Request
public void process(Node node, Id replyToNode, ReplyContext replyContext)
{
- Reply reply = node.ifLocal(homeKey, txnId, instance -> {
- instance.command(txnId).setGloballyPersistent(homeKey, executeAt);
- instance.progressLog().executedOnAllShards(txnId, persistedOn);
+ Reply reply = node.ifLocal(this, homeKey, txnId, instance -> {
+ Command command = instance.command(txnId);
+ command.setGloballyPersistent(homeKey, executeAt);
+ instance.progressLog().executedOnAllShards(command, persistedOn);
return ok();
});
@@ -58,6 +62,18 @@ public class InformOfPersistence implements Request
node.reply(replyToNode, replyContext, reply);
}
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Collections.singleton(txnId);
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return Collections.emptyList();
+ }
+
@Override
public String toString()
{
diff --git a/accord-core/src/main/java/accord/messages/InformOfTxn.java b/accord-core/src/main/java/accord/messages/InformOfTxn.java
index d49ed14..7fe8e00 100644
--- a/accord-core/src/main/java/accord/messages/InformOfTxn.java
+++ b/accord-core/src/main/java/accord/messages/InformOfTxn.java
@@ -21,17 +21,20 @@ package accord.messages;
import accord.api.Key;
import accord.local.Node;
import accord.local.Node.Id;
+import accord.local.PreLoadContext;
import accord.txn.Txn;
import accord.primitives.TxnId;
+import java.util.Collections;
+
import static accord.messages.InformOfTxn.InformOfTxnNack.nack;
import static accord.messages.InformOfTxn.InformOfTxnOk.ok;
-public class InformOfTxn implements EpochRequest
+public class InformOfTxn implements EpochRequest, PreLoadContext
{
- final TxnId txnId;
- final Key homeKey;
- final Txn txn;
+ public final TxnId txnId;
+ public final Key homeKey;
+ public final Txn txn;
public InformOfTxn(TxnId txnId, Key homeKey, Txn txn)
{
@@ -40,10 +43,23 @@ public class InformOfTxn implements EpochRequest
this.txn = txn;
}
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Collections.singleton(txnId);
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return txn.keys();
+ }
+
+ // TODO (now): audit all messages to ensure requests passed to command store
public void process(Node node, Id replyToNode, ReplyContext replyContext)
{
- Key progressKey = node.selectProgressKey(txnId, txn.keys, homeKey);
- Reply reply = node.ifLocal(homeKey, txnId, instance -> {
+ Key progressKey = node.selectProgressKey(txnId, txn.keys(), homeKey);
+ Reply reply = node.ifLocal(this, homeKey, txnId, instance -> {
instance.command(txnId).preaccept(txn, homeKey, progressKey);
return ok();
});
@@ -78,7 +94,7 @@ public class InformOfTxn implements EpochRequest
return MessageType.INFORM_RSP;
}
- static InformOfTxnReply ok()
+ public static InformOfTxnReply ok()
{
return instance;
}
@@ -108,7 +124,7 @@ public class InformOfTxn implements EpochRequest
return MessageType.INFORM_RSP;
}
- static InformOfTxnReply nack()
+ public static InformOfTxnReply nack()
{
return instance;
}
diff --git a/accord-core/src/main/java/accord/messages/PreAccept.java b/accord-core/src/main/java/accord/messages/PreAccept.java
index 4e7d6df..ba1db7e 100644
--- a/accord-core/src/main/java/accord/messages/PreAccept.java
+++ b/accord-core/src/main/java/accord/messages/PreAccept.java
@@ -18,40 +18,45 @@
package accord.messages;
-import java.util.*;
-import java.util.function.BiConsumer;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.function.Consumer;
+import accord.local.*;
import com.google.common.annotations.VisibleForTesting;
import accord.api.Key;
-import accord.local.CommandStore;
-import accord.local.CommandsForKey;
-import accord.local.Node;
import accord.local.Node.Id;
import accord.messages.TxnRequest.WithUnsynced;
import accord.topology.Topologies;
import accord.primitives.Keys;
import accord.primitives.Timestamp;
-import accord.local.Command;
import accord.primitives.Deps;
import accord.txn.Txn;
import accord.primitives.TxnId;
public class PreAccept extends WithUnsynced
{
+ public static class SerializerSupport
+ {
+ public static PreAccept create(Keys scope, long epoch, TxnId txnId, Txn txn, Key homeKey)
+ {
+ return new PreAccept(scope, epoch, txnId, txn, homeKey);
+ }
+ }
+
public final Key homeKey;
public final Txn txn;
public final long maxEpoch;
public PreAccept(Id to, Topologies topologies, TxnId txnId, Txn txn, Key homeKey)
{
- super(to, topologies, txn.keys, txnId);
+ super(to, topologies, txn.keys(), txnId);
this.homeKey = homeKey;
this.txn = txn;
this.maxEpoch = topologies.currentEpoch();
}
- @VisibleForTesting
public PreAccept(Keys scope, long epoch, TxnId txnId, Txn txn, Key homeKey)
{
super(scope, epoch, txnId);
@@ -60,19 +65,36 @@ public class PreAccept extends WithUnsynced
this.maxEpoch = epoch;
}
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Collections.singleton(txnId);
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return txn.keys();
+ }
+
+ @VisibleForTesting
+ public PreAcceptReply process(CommandStore instance, Key progressKey)
+ {
+ // note: this diverges from the paper, in that instead of waiting for JoinShard,
+ // we PreAccept to both old and new topologies and require quorums in both.
+ // This necessitates sending to ALL replicas of old topology, not only electorate (as fast path may be unreachable).
+ Command command = instance.command(txnId);
+ if (!command.preaccept(txn, homeKey, progressKey))
+ return PreAcceptNack.INSTANCE;
+ return new PreAcceptOk(txnId, command.executeAt(), calculateDeps(instance, txnId, txn, txnId));
+ }
+
public void process(Node node, Id from, ReplyContext replyContext)
{
// TODO: verify we handle all of the scope() keys
Key progressKey = progressKey(node, homeKey);
- node.reply(from, replyContext, node.mapReduceLocal(scope(), minEpoch, maxEpoch, instance -> {
- // note: this diverges from the paper, in that instead of waiting for JoinShard,
- // we PreAccept to both old and new topologies and require quorums in both.
- // This necessitates sending to ALL replicas of old topology, not only electorate (as fast path may be unreachable).
- Command command = instance.command(txnId);
- if (!command.preaccept(txn, homeKey, progressKey))
- return PreAcceptNack.INSTANCE;
- return new PreAcceptOk(txnId, command.executeAt(), calculateDeps(instance, txnId, txn, txnId));
- }, (r1, r2) -> {
+ node.reply(from, replyContext, node.mapReduceLocal(this, minEpoch, maxEpoch, cs -> process(cs, progressKey),
+ (r1, r2) -> {
if (!r1.isOK()) return r1;
if (!r2.isOK()) return r2;
PreAcceptOk ok1 = (PreAcceptOk) r1;
@@ -170,14 +192,14 @@ public class PreAccept extends WithUnsynced
{
try (Deps.OrderedBuilder builder = Deps.orderedBuilder(false);)
{
- txn.keys.forEach(key -> {
+ txn.keys().forEach(key -> {
CommandsForKey forKey = commandStore.maybeCommandsForKey(key);
if (forKey == null)
return;
builder.nextKey(key);
- forKey.uncommitted.headMap(executeAt, false).forEach(conflicts(txnId, txn.isWrite(), builder));
- forKey.committedByExecuteAt.headMap(executeAt, false).forEach(conflicts(txnId, txn.isWrite(), builder));
+ forKey.uncommitted().before(executeAt).forEach(conflicts(txnId, txn.isWrite(), builder));
+ forKey.committedByExecuteAt().before(executeAt).forEach(conflicts(txnId, txn.isWrite(), builder));
});
return builder.build();
@@ -194,9 +216,9 @@ public class PreAccept extends WithUnsynced
'}';
}
- private static BiConsumer<Timestamp, Command> conflicts(TxnId txnId, boolean isWrite, Deps.OrderedBuilder builder)
+ private static Consumer<PartialCommand> conflicts(TxnId txnId, boolean isWrite, Deps.OrderedBuilder builder)
{
- return (ts, command) -> {
+ return command -> {
if (!txnId.equals(command.txnId()) && (isWrite || command.txn().isWrite()))
builder.add(command.txnId());
};
diff --git a/accord-core/src/main/java/accord/messages/ReadData.java b/accord-core/src/main/java/accord/messages/ReadData.java
index 6a5a951..fe2ac69 100644
--- a/accord-core/src/main/java/accord/messages/ReadData.java
+++ b/accord-core/src/main/java/accord/messages/ReadData.java
@@ -18,8 +18,11 @@
package accord.messages;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.Set;
+import accord.primitives.Deps;
import com.google.common.base.Preconditions;
import accord.api.Key;
@@ -32,34 +35,72 @@ import accord.primitives.Timestamp;
import accord.txn.Txn;
import accord.primitives.TxnId;
import accord.utils.DeterministicIdentitySet;
+import com.google.common.collect.Iterables;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class ReadData extends TxnRequest
{
- static class LocalRead implements Listener
+ private static final Logger logger = LoggerFactory.getLogger(ReadData.class);
+
+ static class LocalRead implements Listener, PreLoadContext
{
final TxnId txnId;
+ final Deps deps;
final Node node;
final Node.Id replyToNode;
- final Keys readKeys;
+ final Keys scope;
+ final Keys txnKeys;
final ReplyContext replyContext;
Data data;
boolean isObsolete; // TODO: respond with the Executed result we have stored?
Set<CommandStore> waitingOn;
- LocalRead(TxnId txnId, Node node, Id replyToNode, Keys readKeys, ReplyContext replyContext)
+ LocalRead(TxnId txnId, Deps deps, Node node, Id replyToNode, Keys scope, Keys txnKeys, ReplyContext replyContext)
{
- Preconditions.checkArgument(!readKeys.isEmpty());
+ Preconditions.checkArgument(!scope.isEmpty());
this.txnId = txnId;
+ this.deps = deps;
this.node = node;
this.replyToNode = replyToNode;
- this.readKeys = readKeys;
+ this.scope = scope;
+ this.txnKeys = txnKeys; // TODO (now): is this needed? Does the read update commands per key?
this.replyContext = replyContext;
}
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Iterables.concat(Collections.singleton(txnId), deps.txnIds());
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return txnKeys;
+ }
+
+ @Override
+ public PreLoadContext listenerPreLoadContext(TxnId caller)
+ {
+ Set<TxnId> ids = new HashSet<>(deps.txnIds());
+ ids.add(txnId);
+ ids.add(caller);
+ return PreLoadContext.contextFor(ids, keys());
+ }
+
+ @Override
+ public String toString()
+ {
+ return "ReadData$LocalRead{" + txnId + '}';
+ }
+
@Override
public synchronized void onChange(Command command)
{
+ logger.trace("{}: updating as listener in response to change on {} with status {} ({})",
+ this, command.txnId(), command.status(), command);
switch (command.status())
{
default: throw new IllegalStateException();
@@ -82,17 +123,36 @@ public class ReadData extends TxnRequest
read(command);
}
- private void read(Command command)
+ @Override
+ public boolean isTransient()
{
- // TODO: threading/futures (don't want to perform expensive reads within this mutually exclusive context)
- Data next = command.txn().read(command, readKeys);
- data = data == null ? next : data.merge(next);
+ return true;
+ }
+
+ private synchronized void readComplete(CommandStore commandStore, Data result)
+ {
+ logger.trace("{}: read completed on {}", txnId, commandStore);
+ data = data == null ? result : data.merge(result);
- waitingOn.remove(command.commandStore);
+ waitingOn.remove(commandStore);
if (waitingOn.isEmpty())
node.reply(replyToNode, replyContext, new ReadOk(data));
}
+ private void read(Command command)
+ {
+ logger.trace("{}: executing read", command.txnId());
+ command.read(scope).addCallback((next, throwable) -> {
+ if (throwable != null)
+ {
+ logger.trace("{}: read failed for {}: {}", txnId, command.commandStore(), throwable);
+ node.reply(replyToNode, replyContext, new ReadNack());
+ }
+ else
+ readComplete(command.commandStore(), next);
+ });
+ }
+
void obsolete()
{
if (!isObsolete)
@@ -104,12 +164,13 @@ public class ReadData extends TxnRequest
synchronized void setup(TxnId txnId, Txn txn, Key homeKey, Keys keys, Timestamp executeAt)
{
- Key progressKey = node.trySelectProgressKey(txnId, txn.keys, homeKey);
+ Key progressKey = node.trySelectProgressKey(txnId, txn.keys(), homeKey);
waitingOn = node.collectLocal(keys, executeAt, DeterministicIdentitySet::new);
- // FIXME: fix/check thread safety
- CommandStore.onEach(waitingOn, instance -> {
+ CommandStores.forEachNonBlocking(waitingOn, this, instance -> {
Command command = instance.command(txnId);
command.preaccept(txn, homeKey, progressKey); // ensure pre-accepted
+ logger.trace("{}: setting up read with status {} on {}", command.txnId(), command.status(), instance);
+
switch (command.status())
{
default:
@@ -137,26 +198,58 @@ public class ReadData extends TxnRequest
}
}
+ public static class SerializerSupport
+ {
+ public static ReadData create(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Deps deps, Key homeKey, Timestamp executeAt)
+ {
+ return new ReadData(scope, waitForEpoch, txnId, txn, deps, homeKey, executeAt);
+ }
+ }
+
public final TxnId txnId;
public final Txn txn;
- final Key homeKey;
+ public final Deps deps;
+ public final Key homeKey;
public final Timestamp executeAt;
- public ReadData(Node.Id to, Topologies topologies, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt)
+ public ReadData(Node.Id to, Topologies topologies, TxnId txnId, Txn txn, Deps deps, Key homeKey, Timestamp executeAt)
+ {
+ super(to, topologies, txn.keys());
+ this.txnId = txnId;
+ this.txn = txn;
+ this.deps = deps;
+ this.homeKey = homeKey;
+ this.executeAt = executeAt;
+ }
+
+ protected ReadData(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Deps deps, Key homeKey, Timestamp executeAt)
{
- super(to, topologies, txn.keys);
+ super(scope, waitForEpoch);
this.txnId = txnId;
this.txn = txn;
+ this.deps = deps;
this.homeKey = homeKey;
this.executeAt = executeAt;
}
public void process(Node node, Node.Id from, ReplyContext replyContext)
{
- new LocalRead(txnId, node, from, txn.read.keys().intersect(scope()), replyContext)
+ new LocalRead(txnId, deps, node, from, txn.read().keys().intersect(scope()), txn.keys(), replyContext)
.setup(txnId, txn, homeKey, scope(), executeAt);
}
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Iterables.concat(Collections.singleton(txnId), deps.txnIds());
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return Collections.emptyList();
+ }
+
@Override
public MessageType type()
{
@@ -195,6 +288,7 @@ public class ReadData extends TxnRequest
public static class ReadOk extends ReadReply
{
public final Data data;
+
public ReadOk(Data data)
{
this.data = data;
diff --git a/accord-core/src/main/java/accord/messages/Reply.java b/accord-core/src/main/java/accord/messages/Reply.java
index e82c4f9..d87e196 100644
--- a/accord-core/src/main/java/accord/messages/Reply.java
+++ b/accord-core/src/main/java/accord/messages/Reply.java
@@ -20,5 +20,4 @@ package accord.messages;
public interface Reply extends Message
{
- default boolean isFinal() { return true; }
}
diff --git a/accord-core/src/main/java/accord/messages/TxnRequest.java b/accord-core/src/main/java/accord/messages/TxnRequest.java
index bf98d83..eb05fba 100644
--- a/accord-core/src/main/java/accord/messages/TxnRequest.java
+++ b/accord-core/src/main/java/accord/messages/TxnRequest.java
@@ -25,6 +25,7 @@ import accord.api.Key;
import accord.local.Node;
import accord.local.Node.Id;
import accord.primitives.KeyRanges;
+import accord.local.PreLoadContext;
import accord.topology.Topologies;
import accord.topology.Topology;
import accord.primitives.Keys;
@@ -32,7 +33,7 @@ import accord.primitives.TxnId;
import static java.lang.Long.min;
-public abstract class TxnRequest implements EpochRequest
+public abstract class TxnRequest implements EpochRequest, PreLoadContext
{
public static abstract class WithUnsynced extends TxnRequest
{
diff --git a/accord-core/src/main/java/accord/messages/WaitOnCommit.java b/accord-core/src/main/java/accord/messages/WaitOnCommit.java
index f49c03d..47ef220 100644
--- a/accord-core/src/main/java/accord/messages/WaitOnCommit.java
+++ b/accord-core/src/main/java/accord/messages/WaitOnCommit.java
@@ -18,19 +18,33 @@
package accord.messages;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
+import accord.api.Key;
import accord.local.*;
import accord.local.Node.Id;
import accord.topology.Topologies;
import accord.primitives.TxnId;
import accord.primitives.Keys;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static accord.utils.Utils.listOf;
public class WaitOnCommit extends TxnRequest
{
- static class LocalWait implements Listener
+ private static final Logger logger = LoggerFactory.getLogger(WaitOnCommit.class);
+
+ public static class SerializerSupport
+ {
+ public static WaitOnCommit create(Keys scope, long waitForEpoch, TxnId txnId)
+ {
+ return new WaitOnCommit(scope, waitForEpoch, txnId);
+ }
+ }
+
+ static class LocalWait implements Listener, PreLoadContext
{
final Node node;
final Id replyToNode;
@@ -47,9 +61,35 @@ public class WaitOnCommit extends TxnRequest
this.replyContext = replyContext;
}
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Collections.singleton(txnId);
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public PreLoadContext listenerPreLoadContext(TxnId caller)
+ {
+ return PreLoadContext.contextFor(listOf(txnId, caller), keys());
+ }
+
+ @Override
+ public String toString()
+ {
+ return "WaitOnCommit$LocalWait{" + txnId + '}';
+ }
+
@Override
public synchronized void onChange(Command command)
{
+ logger.trace("{}: updating as listener in response to change on {} with status {} ({})",
+ this, command.txnId(), command.status(), command);
switch (command.status())
{
default: throw new IllegalStateException();
@@ -70,6 +110,12 @@ public class WaitOnCommit extends TxnRequest
ack();
}
+ @Override
+ public boolean isTransient()
+ {
+ return true;
+ }
+
private void ack()
{
if (waitingOn.decrementAndGet() == 0)
@@ -86,7 +132,7 @@ public class WaitOnCommit extends TxnRequest
case Accepted:
case AcceptedInvalidate:
command.addListener(this);
- instance.progressLog().waiting(txnId, keys);
+ instance.progressLog().waiting(command, keys);
break;
case Committed:
@@ -102,7 +148,7 @@ public class WaitOnCommit extends TxnRequest
{
List<CommandStore> instances = node.collectLocal(keys, txnId, ArrayList::new);
waitingOn.set(instances.size());
- instances.forEach(instance -> instance.processBlocking(ignore -> setup(keys, instance)));
+ CommandStore.onEach(this, instances, instance -> setup(keys, instance));
}
}
@@ -114,6 +160,24 @@ public class WaitOnCommit extends TxnRequest
this.txnId = txnId;
}
+ protected WaitOnCommit(Keys scope, long waitForEpoch, TxnId txnId)
+ {
+ super(scope, waitForEpoch);
+ this.txnId = txnId;
+ }
+
+ @Override
+ public Iterable<TxnId> txnIds()
+ {
+ return Collections.singleton(txnId);
+ }
+
+ @Override
+ public Iterable<Key> keys()
+ {
+ return scope();
+ }
+
public void process(Node node, Id replyToNode, ReplyContext replyContext)
{
new LocalWait(node, replyToNode, txnId, replyContext).setup(scope());
diff --git a/accord-core/src/main/java/accord/primitives/Deps.java b/accord-core/src/main/java/accord/primitives/Deps.java
index 6941f87..d42ceb5 100644
--- a/accord-core/src/main/java/accord/primitives/Deps.java
+++ b/accord-core/src/main/java/accord/primitives/Deps.java
@@ -32,6 +32,7 @@ import com.google.common.base.Preconditions;
import static accord.utils.ArrayBuffers.*;
import static accord.utils.SortedArrays.*;
+import static accord.utils.Utils.listOf;
/**
* A collection of dependencies for a transaction, organised by the key the dependency is adopted via.
@@ -52,6 +53,26 @@ public class Deps implements Iterable<Map.Entry<Key, TxnId>>
return new Deps(keys, NO_TXNIDS, keysToTxnId);
}
+ public static class SerializerSupport
+ {
+ private SerializerSupport() {}
+
+ public static int keyToTxnIdCount(Deps deps)
+ {
+ return deps.keyToTxnId.length;
+ }
+
+ public static int keyToTxnId(Deps deps, int idx)
+ {
+ return deps.keyToTxnId[idx];
+ }
+
+ public static Deps create(Keys keys, TxnId[] txnIds, int[] keyToTxnId)
+ {
+ return new Deps(keys, txnIds, keyToTxnId);
+ }
+ }
+
/**
* Expects Command to be provided in TxnId order
*/
@@ -520,6 +541,30 @@ public class Deps implements Iterable<Map.Entry<Key, TxnId>>
T construct(K[] keys, int keysLength, V[] values, int valuesLength, int[] out, int outLength);
}
+ private static boolean arraysEqual(int[] left, int[] right, int length)
+ {
+ if (left.length < length || right.length < length)
+ return false;
+
+ for (int i=0; i<length; i++)
+ if (left[i] !=right[i])
+ return false;
+
+ return true;
+ }
+
+ private static <T> boolean arraysEqual(T[] left, T[] right, int length)
+ {
+ if (left.length < length || right.length < length)
+ return false;
+
+ for (int i=0; i<length; i++)
+ if (!Objects.equals(left[i], right[i]))
+ return false;
+
+ return true;
+ }
+
// TODO: this method supports merging keyToTxnId OR txnIdToKey; we can perhaps save time and effort when constructing
// Deps on remote hosts by only producing txnIdToKey with OrderedCollector and serializing only this,
// and merging on the recipient before inverting, so that we only have to invert the final assembled deps
@@ -544,9 +589,9 @@ public class Deps implements Iterable<Map.Entry<Key, TxnId>>
remapLeft = remapToSuperset(leftValues, leftValuesLength, outValues, outTxnIdsLength, intBuffers);
remapRight = remapToSuperset(rightValues, rightValuesLength, outValues, outTxnIdsLength, intBuffers);
- if (remapLeft == null && remapRight == null && leftKeysLength == rightKeysLength
- && Arrays.equals(left, 0, leftLength, right, 0, rightLength)
- && Arrays.equals(leftKeys, 0, leftKeysLength, rightKeys, 0, rightKeysLength)
+ if (remapLeft == null && remapRight == null && leftLength == rightLength && leftKeysLength == rightKeysLength
+ && arraysEqual(left, right, rightLength)
+ && arraysEqual(leftKeys, rightKeys, rightKeysLength)
)
{
return constructor.construct(leftKeys, leftKeysLength, leftValues, leftValuesLength, left, leftLength);
@@ -1024,13 +1069,13 @@ public class Deps implements Iterable<Map.Entry<Key, TxnId>>
public Collection<TxnId> txnIds()
{
- return List.of(txnIds);
+ return listOf(txnIds);
}
@Override
public Iterator<Map.Entry<Key, TxnId>> iterator()
{
- return new Iterator<>()
+ return new Iterator<Map.Entry<Key, TxnId>>()
{
int i = keys.size(), k = 0;
diff --git a/accord-core/src/main/java/accord/primitives/KeyRanges.java b/accord-core/src/main/java/accord/primitives/KeyRanges.java
index 80244a0..0c80a1c 100644
--- a/accord-core/src/main/java/accord/primitives/KeyRanges.java
+++ b/accord-core/src/main/java/accord/primitives/KeyRanges.java
@@ -27,6 +27,7 @@ import com.google.common.collect.Iterators;
import java.util.*;
import static accord.utils.SortedArrays.Search.FAST;
+import static accord.utils.Utils.toArray;
public class KeyRanges implements Iterable<KeyRange>
{
@@ -42,7 +43,7 @@ public class KeyRanges implements Iterable<KeyRange>
public KeyRanges(List<KeyRange> ranges)
{
- this(ranges.toArray(KeyRange[]::new));
+ this(toArray(ranges, KeyRange[]::new));
}
@Override
@@ -205,7 +206,7 @@ public class KeyRanges implements Iterable<KeyRange>
if (thisRange != null)
result.add(thisRange);
}
- return new KeyRanges(result.toArray(KeyRange[]::new));
+ return new KeyRanges(toArray(result, KeyRange[]::new));
}
/**
diff --git a/accord-core/src/main/java/accord/primitives/Keys.java b/accord-core/src/main/java/accord/primitives/Keys.java
index a6d6fcd..38a82f5 100644
--- a/accord-core/src/main/java/accord/primitives/Keys.java
+++ b/accord-core/src/main/java/accord/primitives/Keys.java
@@ -25,7 +25,7 @@ import java.util.stream.Stream;
import accord.api.Key;
import accord.utils.*;
-import org.apache.cassandra.utils.concurrent.Inline;
+import net.nicoulaj.compilecommand.annotations.Inline;
import static accord.utils.ArrayBuffers.cachedKeys;
@@ -38,14 +38,35 @@ public class Keys implements Iterable<Key>
final Key[] keys;
+ private static Key[] toKeyArray(SortedSet<? extends Key> keys)
+ {
+ Key[] akeys = new Key[keys.size()];
+ int idx=0;
+ for (Key key : keys)
+ akeys[idx++] = key;
+
+ return akeys;
+ }
+
+ private static Key[] toKeyArray(Collection<? extends Key> keys)
+ {
+ Key[] akeys = new Key[keys.size()];
+ int idx=0;
+ for (Key key : keys)
+ akeys[idx++] = key;
+
+ Arrays.sort(akeys);
+ return akeys;
+ }
+
public Keys(SortedSet<? extends Key> keys)
{
- this.keys = keys.toArray(Key[]::new);
+ this.keys = toKeyArray(keys);
}
public Keys(Collection<? extends Key> keys)
{
- this.keys = keys.toArray(Key[]::new);
+ this.keys = toKeyArray(keys);
Arrays.sort(this.keys);
}
@@ -153,7 +174,7 @@ public class Keys implements Iterable<Key>
@Override
public Iterator<Key> iterator()
{
- return new Iterator<>()
+ return new Iterator<Key>()
{
int i = 0;
@Override
@@ -176,6 +197,15 @@ public class Keys implements Iterable<Key>
return new Keys(keys);
}
+ public static Keys of(List<Key> keys)
+ {
+ Key[] akeys = new Key[keys.size()];
+ for (int i=0; i<akeys.length; i++)
+ akeys[i] = keys.get(i);
+
+ return of(akeys);
+ }
+
public static Keys ofSorted(Key ... keys)
{
for (int i = 1 ; i < keys.length ; ++i)
diff --git a/accord-core/src/main/java/accord/primitives/Timestamp.java b/accord-core/src/main/java/accord/primitives/Timestamp.java
index be0d5de..0a02fab 100644
--- a/accord-core/src/main/java/accord/primitives/Timestamp.java
+++ b/accord-core/src/main/java/accord/primitives/Timestamp.java
@@ -59,6 +59,7 @@ public class Timestamp implements Comparable<Timestamp>
@Override
public int compareTo(Timestamp that)
{
+ if (this == that) return 0;
int c = Long.compare(this.epoch, that.epoch);
if (c == 0) c = Long.compare(this.real, that.real);
if (c == 0) c = Integer.compare(this.logical, that.logical);
diff --git a/accord-core/src/main/java/accord/topology/Topologies.java b/accord-core/src/main/java/accord/topology/Topologies.java
index df22406..95acfcf 100644
--- a/accord-core/src/main/java/accord/topology/Topologies.java
+++ b/accord-core/src/main/java/accord/topology/Topologies.java
@@ -70,7 +70,7 @@ public interface Topologies
}
}
- private static boolean equals(Topologies t, Object o)
+ static boolean equals(Topologies t, Object o)
{
if (o == t)
return true;
@@ -90,7 +90,7 @@ public interface Topologies
return true;
}
- private static int hashCode(Topologies t)
+ static int hashCode(Topologies t)
{
int hashCode = 1;
for (int i=0, mi=t.size(); i<mi; i++) {
@@ -99,7 +99,7 @@ public interface Topologies
return hashCode;
}
- private static String toString(Topologies t)
+ static String toString(Topologies t)
{
StringBuilder sb = new StringBuilder("[");
for (int i=0, mi=t.size(); i<mi; i++)
@@ -154,7 +154,7 @@ public interface Topologies
public Topology get(int i)
{
if (i != 0)
- throw new IndexOutOfBoundsException(i);
+ throw new IndexOutOfBoundsException(Integer.toString(i));
return topology;
}
diff --git a/accord-core/src/main/java/accord/topology/Topology.java b/accord-core/src/main/java/accord/topology/Topology.java
index aa67a37..08f5378 100644
--- a/accord-core/src/main/java/accord/topology/Topology.java
+++ b/accord-core/src/main/java/accord/topology/Topology.java
@@ -346,23 +346,6 @@ public class Topology extends AbstractCollection<Shard>
consumer.accept(i, shards[supersetRangeIndexes[i]]);
}
- public <T> T[] select(Keys select, T[] indexedByShard, IntFunction<T[]> constructor)
- {
- List<T> selection = new ArrayList<>();
- for (int i = 0, j = 0 ; i < select.size() && j < supersetRangeIndexes.length ;)
- {
- Key k = select.get(i);
- Shard shard = shards[supersetRangeIndexes[j]];
-
- int c = shard.range.compareKey(k);
- if (c < 0) ++i;
- else if (c == 0) { selection.add(indexedByShard[j++]); i++; }
- else j++;
- }
-
- return selection.toArray(constructor);
- }
-
@Override
public Iterator<Shard> iterator()
{
diff --git a/accord-core/src/main/java/accord/txn/Txn.java b/accord-core/src/main/java/accord/txn/Txn.java
index 7d44429..5c4d3f0 100644
--- a/accord-core/src/main/java/accord/txn/Txn.java
+++ b/accord-core/src/main/java/accord/txn/Txn.java
@@ -18,40 +18,86 @@
package accord.txn;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import accord.api.*;
import accord.local.*;
import accord.primitives.Keys;
import accord.primitives.Timestamp;
+import accord.utils.ReducingFuture;
+import org.apache.cassandra.utils.concurrent.Future;
-public class Txn
-{
- enum Kind { READ, WRITE }
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
- final Kind kind;
- public final Keys keys;
- public final Read read;
- public final Query query;
- public final Update update;
+public abstract class Txn
+{
+ public enum Kind { READ, WRITE }
- public Txn(Keys keys, Read read, Query query)
+ public static class InMemory extends Txn
{
- this.kind = Kind.READ;
- this.keys = keys;
- this.read = read;
- this.query = query;
- this.update = null;
+ private final Kind kind;
+ private final Keys keys;
+ private final Read read;
+ private final Query query;
+ private final Update update;
+
+ public InMemory(@Nonnull Keys keys, @Nonnull Read read, @Nonnull Query query)
+ {
+ this.kind = Kind.READ;
+ this.keys = keys;
+ this.read = read;
+ this.query = query;
+ this.update = null;
+ }
+
+ public InMemory(@Nonnull Keys keys, @Nonnull Read read, @Nonnull Query query, @Nullable Update update)
+ {
+ this.kind = Kind.WRITE;
+ this.keys = keys;
+ this.read = read;
+ this.update = update;
+ this.query = query;
+ }
+
+ @Override
+ public Kind kind()
+ {
+ return kind;
+ }
+
+ @Override
+ public Keys keys()
+ {
+ return keys;
+ }
+
+ @Override
+ public Read read()
+ {
+ return read;
+ }
+
+ @Override
+ public Query query()
+ {
+ return query;
+ }
+
+ @Override
+ public Update update()
+ {
+ return update;
+ }
}
- public Txn(Keys keys, Read read, Query query, Update update)
- {
- this.kind = Kind.WRITE;
- this.keys = keys;
- this.read = read;
- this.update = update;
- this.query = query;
- }
+ public abstract @Nonnull Kind kind();
+ public abstract @Nonnull Keys keys();
+ public abstract @Nonnull Read read();
+ public abstract @Nonnull Query query();
+ public abstract @Nullable Update update();
@Override
public boolean equals(Object o)
@@ -59,52 +105,53 @@ public class Txn
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Txn txn = (Txn) o;
- return kind == txn.kind && keys.equals(txn.keys) && read.equals(txn.read) && query.equals(txn.query) && Objects.equals(update, txn.update);
+ return kind() == txn.kind()
+ && keys().equals(txn.keys())
+ && read().equals(txn.read())
+ && query().equals(txn.query())
+ && Objects.equals(update(), txn.update());
}
@Override
public int hashCode()
{
- return Objects.hash(kind, keys, read, query, update);
+ return Objects.hash(kind(), keys(), read(), query(), update());
}
public boolean isWrite()
{
- return kind == Kind.WRITE;
+ return kind() == Kind.WRITE;
}
public Result result(Data data)
{
- return query.compute(data, read, update);
+ return query().compute(data, read(), update());
}
public Writes execute(Timestamp executeAt, Data data)
{
- if (update == null)
- return new Writes(executeAt, keys, null);
+ if (update() == null)
+ return new Writes(executeAt, keys(), null);
- return new Writes(executeAt, keys, update.apply(data));
- }
-
- public Keys keys()
- {
- return keys;
+ return new Writes(executeAt, keys(), update().apply(data));
}
public String toString()
{
- return "{read:" + read.toString() + (update != null ? ", update:" + update : "") + '}';
+ return "{read:" + read().toString() + (update() != null ? ", update:" + update() : "") + '}';
}
- public Data read(Command command, Keys keys)
+ public Future<Data> read(Command command)
{
- return keys.foldl(command.commandStore.ranges().at(command.executeAt().epoch), (index, key, accumulate) -> {
- CommandStore commandStore = command.commandStore;
+ List<Future<Data>> futures = keys().foldl(command.commandStore().ranges().at(command.executeAt().epoch), (index, key, accumulate) -> {
+ CommandStore commandStore = command.commandStore();
if (!commandStore.hashIntersects(key))
return accumulate;
- Data result = read.read(key, command.executeAt(), commandStore.store());
- return accumulate != null ? accumulate.merge(result) : result;
- }, null);
+ Future<Data> result = read().read(key, isWrite(), command.commandStore(), command.executeAt(), commandStore.store());
+ accumulate.add(result);
+ return accumulate;
+ }, new ArrayList<>());
+ return ReducingFuture.reduce(futures, Data::merge);
}
}
diff --git a/accord-core/src/main/java/accord/txn/Writes.java b/accord-core/src/main/java/accord/txn/Writes.java
index 527e399..77ad829 100644
--- a/accord-core/src/main/java/accord/txn/Writes.java
+++ b/accord-core/src/main/java/accord/txn/Writes.java
@@ -23,9 +23,18 @@ import accord.local.CommandStore;
import accord.primitives.Keys;
import accord.primitives.Timestamp;
import accord.primitives.KeyRanges;
+import accord.utils.ReducingFuture;
+import com.google.common.base.Preconditions;
+import org.apache.cassandra.utils.concurrent.Future;
+import org.apache.cassandra.utils.concurrent.ImmediateFuture;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
public class Writes
{
+ public static final Future<Void> SUCCESS = ImmediateFuture.success(null);
public final Timestamp executeAt;
public final Keys keys;
public final Write write;
@@ -37,20 +46,36 @@ public class Writes
this.write = write;
}
- public void apply(CommandStore commandStore)
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Writes writes = (Writes) o;
+ return executeAt.equals(writes.executeAt) && keys.equals(writes.keys) && write.equals(writes.write);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(executeAt, keys, write);
+ }
+
+ public Future<Void> apply(CommandStore commandStore)
{
if (write == null)
- return;
+ return SUCCESS;
KeyRanges ranges = commandStore.ranges().since(executeAt.epoch);
if (ranges == null)
- return;
+ return SUCCESS;
- keys.foldl(ranges, (index, key, accumulate) -> {
+ List<Future<Void>> futures = keys.foldl(ranges, (index, key, accumulate) -> {
if (commandStore.hashIntersects(key))
- write.apply(key, executeAt, commandStore.store());
+ accumulate.add(write.apply(key, commandStore, executeAt, commandStore.store()));
return accumulate;
- }, null);
+ }, new ArrayList<>());
+ return ReducingFuture.reduce(futures, (l, r) -> null);
}
@Override
diff --git a/accord-core/src/main/java/accord/utils/DeterministicIdentitySet.java b/accord-core/src/main/java/accord/utils/DeterministicIdentitySet.java
index a5c6ac2..8bafc79 100644
--- a/accord-core/src/main/java/accord/utils/DeterministicIdentitySet.java
+++ b/accord-core/src/main/java/accord/utils/DeterministicIdentitySet.java
@@ -18,10 +18,9 @@
package accord.utils;
-import java.util.AbstractSet;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
+import com.google.common.collect.Iterables;
+
+import java.util.*;
import java.util.function.Consumer;
public class DeterministicIdentitySet<T> extends AbstractSet<T>
@@ -77,6 +76,27 @@ public class DeterministicIdentitySet<T> extends AbstractSet<T>
};
}
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (!(o instanceof Iterable))
+ return false;
+
+ return Iterables.elementsEqual(this, (Iterable<?>) o);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = 1;
+
+ for (T element : this)
+ result = 31 * result + (element == null ? 0 : element.hashCode());
+
+ return result;
+ }
+
@Override
public int size()
{
diff --git a/accord-core/src/main/java/accord/utils/ReducingFuture.java b/accord-core/src/main/java/accord/utils/ReducingFuture.java
new file mode 100644
index 0000000..4e02cba
--- /dev/null
+++ b/accord-core/src/main/java/accord/utils/ReducingFuture.java
@@ -0,0 +1,54 @@
+package accord.utils;
+
+import com.google.common.base.Preconditions;
+import org.apache.cassandra.utils.concurrent.AsyncPromise;
+import org.apache.cassandra.utils.concurrent.Future;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.function.BiFunction;
+
+public class ReducingFuture<V> extends AsyncPromise<V>
+{
+ private static final AtomicIntegerFieldUpdater<ReducingFuture> PENDING_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ReducingFuture.class, "pending");
+ private final List<? extends Future<V>> futures;
+ private final BiFunction<V, V, V> reducer;
+ private volatile int pending;
+
+ private ReducingFuture(List<? extends Future<V>> futures, BiFunction<V, V, V> reducer)
+ {
+ this.futures = futures;
+ this.reducer = reducer;
+ this.pending = futures.size();
+ futures.forEach(f -> f.addListener(this::operationComplete));
+ }
+
+ private <F extends io.netty.util.concurrent.Future<?>> void operationComplete(F future) throws Exception
+ {
+ if (isDone())
+ return;
+
+ if (!future.isSuccess())
+ {
+ tryFailure(future.cause());
+ }
+ else if (PENDING_UPDATER.decrementAndGet(this) == 0)
+ {
+ V result = futures.get(0).getNow();
+ for (int i=1, mi=futures.size(); i<mi; i++)
+ result = reducer.apply(result, futures.get(i).getNow());
+
+ trySuccess(result);
+ }
+ }
+
+ public static <T> Future<T> reduce(List<? extends Future<T>> futures, BiFunction<T, T, T> reducer)
+ {
+ Preconditions.checkArgument(!futures.isEmpty(), "future list is empty");
+
+ if (futures.size() == 1)
+ return futures.get(0);
+
+ return new ReducingFuture<>(futures, reducer);
+ }
+}
diff --git a/accord-core/src/main/java/accord/utils/SortedArrays.java b/accord-core/src/main/java/accord/utils/SortedArrays.java
index 0be50fc..a4864a7 100644
--- a/accord-core/src/main/java/accord/utils/SortedArrays.java
+++ b/accord-core/src/main/java/accord/utils/SortedArrays.java
@@ -23,7 +23,7 @@ import java.util.function.IntFunction;
import accord.utils.ArrayBuffers.ObjectBuffers;
import accord.utils.ArrayBuffers.IntBufferAllocator;
-import org.apache.cassandra.utils.concurrent.Inline;
+import net.nicoulaj.compilecommand.annotations.Inline;
import javax.annotation.Nullable;
diff --git a/accord-core/src/test/java/accord/impl/TopologyFactory.java b/accord-core/src/main/java/accord/utils/Utils.java
similarity index 50%
copy from accord-core/src/test/java/accord/impl/TopologyFactory.java
copy to accord-core/src/main/java/accord/utils/Utils.java
index 3deabce..6995c7d 100644
--- a/accord-core/src/test/java/accord/impl/TopologyFactory.java
+++ b/accord-core/src/main/java/accord/utils/Utils.java
@@ -16,41 +16,39 @@
* limitations under the License.
*/
-package accord.impl;
-
-
-import accord.local.Node;
-import accord.api.Key;
-import accord.primitives.KeyRange;
-import accord.primitives.KeyRanges;
-import accord.topology.Topology;
+package accord.utils;
import java.util.*;
+import java.util.function.IntFunction;
-public class TopologyFactory
+// TODO: remove when jdk8 support is dropped
+public class Utils
{
- public final int rf;
- // TODO: convert to KeyRanges
- final KeyRange[] ranges;
-
- public TopologyFactory(int rf, KeyRange... ranges)
- {
- this.rf = rf;
- this.ranges = ranges;
- }
-
- public Topology toTopology(Node.Id[] cluster)
+ // reimplements Collection#toArray
+ public static <T> T[] toArray(Collection<T> src, IntFunction<T[]> factory)
{
- return TopologyUtils.initialTopology(cluster, KeyRanges.ofSortedAndDeoverlapped(ranges), rf);
+ T[] dst = factory.apply(src.size());
+ int i = 0;
+ for (T item : src)
+ dst[i++] = item;
+ return dst;
}
- public Topology toTopology(List<Node.Id> cluster)
+ // reimplements Collection#toArray
+ public static <T> T[] toArray(List<T> src, IntFunction<T[]> factory)
{
- return toTopology(cluster.toArray(Node.Id[]::new));
+ T[] dst = factory.apply(src.size());
+ for (int i=0; i<dst.length; i++)
+ dst[i] = src.get(i);
+ return dst;
}
- public static Topology toTopology(List<Node.Id> cluster, int rf, KeyRange... ranges)
+ // reimplements List#of
+ public static <T> List<T> listOf(T... src)
{
- return new TopologyFactory(rf, ranges).toTopology(cluster);
+ List<T> dst = new ArrayList<>(src.length);
+ for (int i=0; i<src.length; i++)
+ dst.add(src[i]);
+ return dst;
}
}
diff --git a/accord-core/src/main/java/org/apache/cassandra/concurrent/ExecutionFailure.java b/accord-core/src/main/java/org/apache/cassandra/concurrent/ExecutionFailure.java
deleted file mode 100644
index a38820f..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/concurrent/ExecutionFailure.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.concurrent;
-
-//import org.apache.cassandra.db.compaction.CompactionInterruptedException;
-//import org.apache.cassandra.utils.Closeable;
-//import org.apache.cassandra.utils.JVMStabilityInspector;
-//import org.apache.cassandra.utils.WithResources;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.Future;
-
-///**
-// * Standardised handling of failures during execution - mostly this involves invoking a thread's
-// * {@link Thread.UncaughtExceptionHandler} or
-// * {@link JVMStabilityInspector#uncaughtException(Thread, Throwable)},
-// * with special handling for {@link CompactionInterruptedException}.
-// * This class also provides wrappers for {@link WithResources} with {@link Runnable} and {@link Callable}.
-// */
-public class ExecutionFailure
-{
- private static final Logger logger = LoggerFactory.getLogger(ExecutionFailure.class);
-
-// /**
-// * Invoke the relevant {@link Thread.UncaughtExceptionHandler},
-// * ignoring (except for logging) any {@link CompactionInterruptedException}
-// */
- public static void handle(Throwable t)
- {
- try
- {
-// if (t instanceof CompactionInterruptedException)
-// {
-// // TODO: should we check to see there aren't nested CompactionInterruptedException?
-// logger.info(t.getMessage());
-// if (t.getSuppressed() != null && t.getSuppressed().length > 0)
-// logger.warn("Interruption of compaction encountered exceptions:", t);
-// else
-// logger.trace("Full interruption stack trace:", t);
-// }
-// else
-// {
-// Thread thread = Thread.currentThread();
-// Thread.UncaughtExceptionHandler handler = thread.getUncaughtExceptionHandler();
-// if (handler == null || handler.equals(thread.getThreadGroup()))
-// handler = JVMStabilityInspector::uncaughtException;
-// handler.uncaughtException(thread, t);
-// }
- Thread thread = Thread.currentThread();
- thread.getUncaughtExceptionHandler().uncaughtException(thread, t);
- }
- catch (Throwable shouldNeverHappen)
- {
- logger.error("Unexpected error while handling unexpected error", shouldNeverHappen);
- }
- }
-
-// /**
-// * See {@link #propagating(WithResources, Runnable)}
-// */
-// static Runnable propagating(Runnable wrap)
-// {
-// return wrap instanceof FutureTask<?> ? wrap : propagating(WithResources.none(), wrap);
-// }
-//
-// /**
-// * In the case of plain executions, we want to handle exceptions without the full {@link FutureTask} machinery
-// * while still propagating the exception to the encapsulating Future
-// */
-// static Runnable propagating(WithResources withResources, Runnable wrap)
-// {
-// return enforceOptions(withResources, wrap, true);
-// }
-//
-// /**
-// * See {@link #suppressing(WithResources, Runnable)}
-// */
-// static Runnable suppressing(Runnable wrap)
-// {
-// return wrap instanceof FutureTask<?> ? wrap : suppressing(WithResources.none(), wrap);
-// }
-//
-// /**
-// * In the case of scheduled periodic tasks, we don't want exceptions propagating to cancel the recurring execution.
-// */
-// static Runnable suppressing(WithResources withResources, Runnable wrap)
-// {
-// return enforceOptions(withResources, wrap, false);
-// }
-//
-// /**
-// * Encapsulate the execution, propagating or suppressing any exceptions as requested.
-// *
-// * note that if {@code wrap} is a {@link Future} its exceptions may not be captured,
-// * however the codebase should be using our internal {@link Future} variants which handle exceptions in the
-// * desired way.
-// */
-// private static Runnable enforceOptions(WithResources withResources, Runnable wrap, boolean propagate)
-// {
-// return new Runnable()
-// {
-// @Override
-// public void run()
-// {
-// try (Closeable close = withResources.get())
-// {
-// wrap.run();
-// }
-// catch (Throwable t)
-// {
-// handle(t);
-// if (propagate)
-// throw t;
-// }
-// }
-//
-// @Override
-// public String toString()
-// {
-// return wrap.toString();
-// }
-// };
-// }
-//
-// /**
-// * See {@link #enforceOptions(WithResources, Callable)}
-// */
-// static <V> Callable<V> propagating(Callable<V> wrap)
-// {
-// return enforceOptions(WithResources.none(), wrap);
-// }
-//
-// /**
-// * In the case of non-recurring scheduled tasks, we want to handle exceptions without the full {@link FutureTask}
-// * machinery, while still propagating the exception to the encapsulating Future
-// */
-// static <V> Callable<V> enforceOptions(WithResources withResources, Callable<V> wrap)
-// {
-// return new Callable<V>()
-// {
-// @Override
-// public V call() throws Exception
-// {
-// try (Closeable close = withResources.get())
-// {
-// return wrap.call();
-// }
-// catch (Throwable t)
-// {
-// handle(t);
-// throw t;
-// }
-// }
-//
-// @Override
-// public String toString()
-// {
-// return wrap.toString();
-// }
-// };
-// }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/concurrent/ExecutorPlus.java b/accord-core/src/main/java/org/apache/cassandra/concurrent/ExecutorPlus.java
deleted file mode 100644
index 4ed759c..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/concurrent/ExecutorPlus.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.concurrent;
-
-import org.apache.cassandra.utils.Shared;
-//import org.apache.cassandra.utils.WithResources;
-import org.apache.cassandra.utils.concurrent.Future;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.*;
-
-import static org.apache.cassandra.utils.Shared.Recursive.INTERFACES;
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-///**
-// * Cassandra's extension of {@link ExecutorService}, using our own {@link Future}, supporting
-// * {@link #inExecutor()}, and execution with associated resources {@link #execute(WithResources, Runnable)}
-// * (which is primarily used for encapsulating {@link ExecutorLocals} without leaking implementing classes).
-// */
-@Shared(scope = SIMULATION, inner = INTERFACES)
-//public interface ExecutorPlus extends ExecutorService, ResizableThreadPool
-public interface ExecutorPlus extends ExecutorService
-{
- interface MaximumPoolSizeListener
- {
- /**
- * Listener to follow changes to the maximum pool size
- */
- void onUpdateMaximumPoolSize(int maximumPoolSize);
- }
-
- /**
- * MAY execute {@code task} immediately, if the calling thread is permitted to do so.
- */
- default void maybeExecuteImmediately(Runnable task)
- {
- execute(task);
- }
-
- /**
- * Overrides {@link ExecutorService#submit(Callable)} to return a Cassandra {@link Future}
- */
- @Override
- <T> Future<T> submit(Callable<T> task);
-
- /**
- * Overrides {@link ExecutorService#submit(Runnable, Object)} to return a Cassandra {@link Future}
- */
- @Override
- <T> Future<T> submit(Runnable task, T result);
-
- /**
- * Overrides {@link ExecutorService#submit(Runnable)} to return a Cassandra {@link Future}
- */
- @Override
- Future<?> submit(Runnable task);
-//
-// /*
-// * ==============================================
-// * WithResources variants of submit and execute.
-// *
-// * (We need a way to inject a TraceState directly into the Executor context without going through
-// * the global Tracing sessions; see CASSANDRA-5668)
-// * ==============================================
-// */
-//
-// /**
-// * Invoke {@code task}. The invoking thread will first instantiate the resources provided before
-// * invoking {@code task}, so that thread state may be modified and cleaned up.
-// *
-// * The invoking thread will execute something semantically equivlent to:
-// *
-// * <code>
-// * try (Closeable close = withResources.get())
-// * {
-// * task.run();
-// * }
-// * </code>
-// *
-// * @param withResources the resources to create and hold while executing {@code task}
-// * @param task the task to execute
-// */
-// void execute(WithResources withResources, Runnable task);
-//
-// /**
-// * Invoke {@code task}, returning a future representing this computation.
-// * The invoking thread will first instantiate the resources provided before
-// * invoking {@code task}, so that thread state may be modified and cleaned up.
-// *
-// * The invoking thread will execute something semantically equivlent to:
-// *
-// * <code>
-// * try (Closeable close = withResources.get())
-// * {
-// * return task.call();
-// * }
-// * </code>
-// *
-// * @param withResources the resources to create and hold while executing {@code task}
-// * @param task the task to execute
-// */
-// <T> Future<T> submit(WithResources withResources, Callable<T> task);
-//
-// /**
-// * Invoke {@code task}, returning a future yielding {@code null} if successful,
-// * or the abnormal termination of {@code task} otherwise.
-// *
-// * The invoking thread will first instantiate the resources provided before
-// * invoking {@code task}, so that thread state may be modified and cleaned up
-// *
-// * <code>
-// * try (Closeable close = withResources.get())
-// * {
-// * task.run();
-// * return null;
-// * }
-// * </code>
-// *
-// * @param withResources the resources to create and hold while executing {@code task}
-// * @param task the task to execute
-// */
-// Future<?> submit(WithResources withResources, Runnable task);
-//
-// /**
-// * Invoke {@code task}, returning a future yielding {@code result} if successful,
-// * or the abnormal termination of {@code task} otherwise.
-// *
-// * The invoking thread will first instantiate the resources provided before
-// * invoking {@code task}, so that thread state may be modified and cleaned up.
-// *
-// * The invoking thread will execute something semantically equivlent to:
-// *
-// * <code>
-// * try (Closeable close = withResources.get())
-// * {
-// * task.run();
-// * return result;
-// * }
-// * </code>
-// *
-// * @param withResources the resources to create and hold while executing {@code task}
-// * @param task the task to execute
-// * @param result the result if successful
-// */
-// <T> Future<T> submit(WithResources withResources, Runnable task, T result);
-
- /**
- * @return true iff the caller is a worker thread actively serving this executor
- */
- boolean inExecutor();
-
- default <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException
- {
- throw new UnsupportedOperationException();
- }
- default <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException
- {
- throw new UnsupportedOperationException();
- }
- default <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException
- {
- throw new UnsupportedOperationException();
- }
- default <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
- {
- throw new UnsupportedOperationException();
- }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/concurrent/ImmediateExecutor.java b/accord-core/src/main/java/org/apache/cassandra/concurrent/ImmediateExecutor.java
deleted file mode 100644
index 703dbdf..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/concurrent/ImmediateExecutor.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.concurrent;
-
-//import org.apache.cassandra.utils.Closeable;
-//import org.apache.cassandra.utils.WithResources;
-//import org.apache.cassandra.utils.concurrent.Future;
-//import org.apache.cassandra.utils.concurrent.ImmediateFuture;
-//
-//import java.util.Collections;
-//import java.util.List;
-//import java.util.concurrent.Callable;
-import java.util.concurrent.Executor;
-//import java.util.concurrent.TimeUnit;
-
-//public class ImmediateExecutor implements LocalAwareExecutorPlus
-public class ImmediateExecutor implements Executor
-{
- public static final ImmediateExecutor INSTANCE = new ImmediateExecutor();
-
- private ImmediateExecutor() {}
-
- @Override
- public void execute(Runnable command)
- {
- command.run();
- }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/Clock.java b/accord-core/src/main/java/org/apache/cassandra/utils/Clock.java
deleted file mode 100644
index d1c8da6..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/Clock.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.concurrent.TimeUnit;
-
-//import static org.apache.cassandra.config.CassandrandraRelevantProperties.CLOCK_GLOBAL;
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * Wrapper around time related functions that are either implemented by using the default JVM calls
- * or by using a custom implementation for testing purposes.
- *
- * See {@link Global#instance} for how to use a custom implementation.
- *
- * Please note that {@link java.time.Clock} wasn't used, as it would not be possible to provide an
- * implementation for {@link #nanoTime()} with the exact same properties of {@link System#nanoTime()}.
- */
-@Shared(scope = SIMULATION)
-public interface Clock
-{
- static final Logger logger = LoggerFactory.getLogger(Clock.class);
-
- public static class Global
- {
- /**
- * Static singleton object that will be instantiated by default with a system clock
- * implementation. Set <code>cassandra.clock</code> system property to a FQCN to use a
- * different implementation instead.
- */
- private static final Clock instance;
-
- static
- {
-// String classname = CLOCK_GLOBAL.getString();
- String classname = System.getProperty("cassandra.clock");
- Clock clock = new Default();
- if (classname != null)
- {
- try
- {
- logger.debug("Using custom clock implementation: {}", classname);
- clock = (Clock) Class.forName(classname).newInstance();
- }
- catch (Exception e)
- {
- logger.error("Failed to load clock implementation {}", classname, e);
- }
- }
- instance = clock;
- }
-
- /**
- * Semantically equivalent to {@link System#nanoTime()}
- */
- public static long nanoTime()
- {
- return instance.nanoTime();
- }
-
- /**
- * Semantically equivalent to {@link System#currentTimeMillis()}
- */
- public static long currentTimeMillis()
- {
- return instance.currentTimeMillis();
- }
- }
-
- public static class Default implements Clock
- {
- /**
- * {@link System#nanoTime()}
- */
- public long nanoTime()
- {
- return System.nanoTime(); // checkstyle: permit system clock
- }
-
- /**
- * {@link System#currentTimeMillis()}
- */
- public long currentTimeMillis()
- {
- return System.currentTimeMillis(); // checkstyle: permit system clock
- }
- }
-
- /**
- * Semantically equivalent to {@link System#nanoTime()}
- */
- public long nanoTime();
-
- /**
- * Semantically equivalent to {@link System#currentTimeMillis()}
- */
- public long currentTimeMillis();
-
- @Intercept
- public static void waitUntil(long deadlineNanos) throws InterruptedException
- {
- long waitNanos = Global.nanoTime() - deadlineNanos;
- if (waitNanos > 0)
- TimeUnit.NANOSECONDS.sleep(waitNanos);
- }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/Intercept.java b/accord-core/src/main/java/org/apache/cassandra/utils/Intercept.java
deleted file mode 100644
index b81947b..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/Intercept.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-// a marker to indicate that the method is intercepted by the Simulator
-@Retention(RetentionPolicy.SOURCE)
-@Target({ ElementType.METHOD })
-public @interface Intercept
-{
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/Shared.java b/accord-core/src/main/java/org/apache/cassandra/utils/Shared.java
deleted file mode 100644
index abc8ad2..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/Shared.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-///**
-// * Tells jvm-dtest that a class should be shared accross all {@link ClassLoader}s.
-// *
-// * Jvm-dtest relies on classloader isolation to run multiple cassandra instances in the same JVM, this makes it
-// * so some classes do not get shared (outside a blesssed set of classes/packages). When the default behavior
-// * is not desirable, this annotation will tell jvm-dtest to share the class accross all class loaders.
-// *
-// * This is the oposite of {@link Isolated}.
-// */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ ElementType.TYPE })
-public @interface Shared
-{
- enum Scope { ANY, SIMULATION }
- enum Recursive { NONE, INTERFACES /*(and enums and exceptions) */, ALL }
- Scope[] scope() default Scope.ANY;
- Recursive inner() default Recursive.NONE;
- Recursive ancestors() default Recursive.NONE;
- Recursive members() default Recursive.NONE;
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AbstractFuture.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AbstractFuture.java
deleted file mode 100644
index e203d0e..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AbstractFuture.java
+++ /dev/null
@@ -1,508 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-import com.google.common.base.Preconditions;
-import com.google.common.util.concurrent.AsyncFunction;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.ListenableFuture;
-import io.netty.util.concurrent.GenericFutureListener;
-import io.netty.util.internal.ThrowableUtil;
-import org.apache.cassandra.utils.concurrent.ListenerList.*;
-
-import javax.annotation.Nullable;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
-import static org.apache.cassandra.utils.concurrent.ListenerList.*;
-
-/**
- * Our default {@link Future} implementation, with all state being managed without locks (except those used by the JVM).
- *
- * Some implementation comments versus Netty's default promise:
- * - We permit efficient initial state declaration, avoiding unnecessary CAS or lock acquisitions when mutating
- * a Promise we are ourselves constructing (and can easily add more; only those we use have been added)
- * - We guarantee the order of invocation of listeners (and callbacks etc, and with respect to each other)
- * - We save some space when registering listeners, especially if there is only one listener, as we perform no
- * extra allocations in this case.
- * - We implement our invocation list as a concurrent stack, that is cleared on notification
- * - We handle special values slightly differently.
- * - We do not use a special value for null, instead using a special value to indicate the result has not been set.
- * This means that once isSuccess() holds, the result must be a correctly typed object (modulo generics pitfalls).
- * - All special values are also instances of FailureHolder, which simplifies a number of the logical conditions.
- */
-@SuppressWarnings({ "rawtypes", "unchecked" })
-public abstract class AbstractFuture<V> implements Future<V>
-{
- protected static final FailureHolder UNSET = new FailureHolder(null);
- protected static final FailureHolder UNCANCELLABLE = new FailureHolder(null);
- protected static final FailureHolder CANCELLED = new FailureHolder(ThrowableUtil.unknownStackTrace(new CancellationException(), AbstractFuture.class, "cancel(...)"));
-
- static class FailureHolder
- {
- final Throwable cause;
- FailureHolder(Throwable cause)
- {
- this.cause = cause;
- }
- }
-
- private static Throwable cause(Object result)
- {
- return result instanceof FailureHolder ? ((FailureHolder) result).cause : null;
- }
- static boolean isSuccess(Object result)
- {
- return !(result instanceof FailureHolder);
- }
- static boolean isCancelled(Object result)
- {
- return result == CANCELLED;
- }
- static boolean isDone(Object result)
- {
- return result != UNSET && result != UNCANCELLABLE;
- }
-
- volatile Object result;
- volatile ListenerList<V> listeners; // either a ListenerList or GenericFutureListener (or null)
- static final AtomicReferenceFieldUpdater<AbstractFuture, Object> resultUpdater = newUpdater(AbstractFuture.class, Object.class, "result");
- static final AtomicReferenceFieldUpdater<AbstractFuture, ListenerList> listenersUpdater = newUpdater(AbstractFuture.class, ListenerList.class, "listeners");
-
- protected AbstractFuture(FailureHolder initialState)
- {
- // TODO: document visibility of constructor (i.e. must be safe published)
- resultUpdater.lazySet(this, initialState);
- }
-
- public AbstractFuture()
- {
- this(UNSET);
- }
-
- protected AbstractFuture(V immediateSuccess)
- {
- resultUpdater.lazySet(this, immediateSuccess);
- }
-
- protected AbstractFuture(Throwable immediateFailure)
- {
- this(new FailureHolder(immediateFailure));
- }
-
- protected AbstractFuture(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
- {
- this();
- listenersUpdater.lazySet(this, new GenericFutureListenerList(listener));
- }
-
- protected AbstractFuture(FailureHolder initialState, GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
- {
- this(initialState);
- listenersUpdater.lazySet(this, new GenericFutureListenerList(listener));
- }
-
- public Executor notifyExecutor()
- {
- return null;
- }
-
- protected boolean trySuccess(V v)
- {
- return trySet(v);
- }
-
- protected boolean tryFailure(Throwable throwable)
- {
- return trySet(new FailureHolder(throwable));
- }
-
- protected boolean setUncancellable()
- {
- if (trySet(UNCANCELLABLE))
- return true;
- return isUncancellable();
- }
-
- protected boolean setUncancellableExclusive()
- {
- return trySet(UNCANCELLABLE);
- }
-
- protected boolean isUncancellable()
- {
- Object result = this.result;
- return result == UNCANCELLABLE || (isDone(result) && !isCancelled(result));
- }
-
- public boolean cancel(boolean b)
- {
- return trySet(CANCELLED);
- }
-
- /**
- * Shared implementation of various promise completion methods.
- * Updates the result if it is possible to do so, returning success/failure.
- *
- * If the promise is UNSET the new value will succeed;
- * if it is UNCANCELLABLE it will succeed only if the new value is not CANCELLED
- * otherwise it will fail, as isDone() is implied
- *
- * If the update succeeds, and the new state implies isDone(), any listeners and waiters will be notified
- */
- abstract boolean trySet(Object v);
-
- @Override
- public boolean isSuccess()
- {
- return isSuccess(result);
- }
-
- @Override
- public boolean isCancelled()
- {
- return isCancelled(result);
- }
-
- @Override
- public boolean isDone()
- {
- return isDone(result);
- }
-
- @Override
- public boolean isCancellable()
- {
- return result == UNSET;
- }
-
- @Override
- public Throwable cause()
- {
- return cause(result);
- }
-
- /**
- * if isSuccess(), returns the value, otherwise returns null
- */
- @Override
- public V getNow()
- {
- Object result = this.result;
- if (isSuccess(result))
- return (V) result;
- return null;
- }
-
- /**
- * Shared implementation of get() after suitable await(); assumes isDone(), and returns
- * either the success result or throws the suitable exception under failure
- */
- protected V getWhenDone() throws ExecutionException
- {
- Object result = this.result;
- if (isSuccess(result))
- return (V) result;
- if (result == CANCELLED)
- throw new CancellationException();
- throw new ExecutionException(((FailureHolder) result).cause);
- }
-
- @Override
- public V get() throws InterruptedException, ExecutionException
- {
- await();
- return getWhenDone();
- }
-
- @Override
- public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
- {
- if (!await(timeout, unit))
- throw new TimeoutException();
- return getWhenDone();
- }
-
- /**
- * Logically append {@code newListener} to {@link #listeners}
- * (at this stage it is a stack, so we actually prepend)
- */
- abstract void appendListener(ListenerList<V> newListener);
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
- *
- * See {@link #addListener(GenericFutureListener)} for ordering semantics.
- */
- @Override
- public AbstractFuture<V> addCallback(FutureCallback<? super V> callback)
- {
- appendListener(new CallbackListener<>(this, callback));
- return this;
- }
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
- *
- * See {@link #addListener(GenericFutureListener)} for ordering semantics.
- */
- @Override
- public AbstractFuture<V> addCallback(BiConsumer<? super V, Throwable> callback)
- {
- appendListener(new CallbackBiConsumerListener<>(this, callback, null));
- return this;
- }
-
- @Override
- public Future<V> addCallback(BiConsumer<? super V, Throwable> callback, Executor executor)
- {
- appendListener(new CallbackBiConsumerListener<>(this, callback, executor));
- return this;
- }
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
- *
- * See {@link #addListener(GenericFutureListener)} for ordering semantics.
- */
- @Override
- public AbstractFuture<V> addCallback(FutureCallback<? super V> callback, Executor executor)
- {
- Preconditions.checkNotNull(executor);
- appendListener(new CallbackListenerWithExecutor<>(this, callback, executor));
- return this;
- }
-
- /**
- * Support more fluid version of {@link com.google.common.util.concurrent.Futures#addCallback}
- *
- * See {@link #addListener(GenericFutureListener)} for ordering semantics.
- */
- @Override
- public AbstractFuture<V> addCallback(Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure)
- {
- appendListener(new CallbackLambdaListener<>(this, onSuccess, onFailure, null));
- return this;
- }
-
- /**
- * Support more fluid version of {@link com.google.common.util.concurrent.Futures#addCallback}
- *
- * See {@link #addListener(GenericFutureListener)} for ordering semantics.
- */
- @Override
- public AbstractFuture<V> addCallback(Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure, Executor executor)
- {
- appendListener(new CallbackLambdaListener<>(this, onSuccess, onFailure, executor));
- return this;
- }
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#transform(ListenableFuture, com.google.common.base.Function, Executor)} natively
- *
- * See {@link #addListener(GenericFutureListener)} for ordering semantics.
- */
- protected <T> Future<T> map(AbstractFuture<T> result, Function<? super V, ? extends T> mapper, @Nullable Executor executor)
- {
- addListener(() -> {
- try
- {
- if (isSuccess()) result.trySet(mapper.apply(getNow()));
- else result.tryFailure(cause());
- }
- catch (Throwable t)
- {
- result.tryFailure(t);
- throw t;
- }
- }, executor);
- return result;
- }
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively
- *
- * See {@link #addListener(GenericFutureListener)} for ordering semantics.
- */
- protected <T> Future<T> flatMap(AbstractFuture<T> result, Function<? super V, ? extends Future<T>> flatMapper, @Nullable Executor executor)
- {
- addListener(() -> {
- try
- {
- if (isSuccess()) flatMapper.apply(getNow()).addListener(propagate(result));
- else result.tryFailure(cause());
- }
- catch (Throwable t)
- {
- result.tryFailure(t);
- throw t;
- }
- }, executor);
- return result;
- }
-
- /**
- * Add a listener to be invoked once this future completes.
- * Listeners are submitted to {@link #notifyExecutor} in the order they are added (or the specified executor
- * in the case of {@link #addListener(Runnable, Executor)}.
- * if {@link #notifyExecutor} is unset, they are invoked in the order they are added.
- * The ordering holds across all variants of this method.
- */
- public Future<V> addListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
- {
- appendListener(new GenericFutureListenerList(listener));
- return this;
- }
-
- /**
- * Add a listener to be invoked once this future completes.
- * Listeners are submitted to their {@code #executor} (or {@link #notifyExecutor}) in the order they are added;
- * if {@link #notifyExecutor} is unset, they are invoked in the order they are added.
- * The ordering holds across all variants of this method.
- */
- public void addListener(Runnable task, @Nullable Executor executor)
- {
- appendListener(new RunnableWithExecutor(task, executor));
- }
-
- /**
- * Add a listener to be invoked once this future completes.
- * Listeners are submitted to {@link #notifyExecutor} in the order they are added (or the specified executor
- * in the case of {@link #addListener(Runnable, Executor)}.
- * if {@link #notifyExecutor} is unset, they are invoked in the order they are added.
- * The ordering holds across all variants of this method.
- */
- public void addListener(Runnable task)
- {
- appendListener(new RunnableWithNotifyExecutor(task));
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public Future<V> addListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... listeners)
- {
- // this could be more efficient if we cared, but we do not
- return addListener(future -> {
- for (GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener : listeners)
- notifyListener((GenericFutureListener<io.netty.util.concurrent.Future<? super V>>)listener, future);
- });
- }
-
- @Override
- public Future<V> removeListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
- {
- throw new UnsupportedOperationException();
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public Future<V> removeListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... listeners)
- {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean await(long timeout, TimeUnit unit) throws InterruptedException
- {
- return Awaitable.await(this, timeout, unit);
- }
-
- @Override
- public boolean awaitThrowUncheckedOnInterrupt(long time, TimeUnit units) throws UncheckedInterruptedException
- {
- return Awaitable.awaitThrowUncheckedOnInterrupt(this, time, units);
- }
-
- @Override
- public boolean awaitUninterruptibly(long timeout, TimeUnit unit)
- {
- return Awaitable.awaitUninterruptibly(this, timeout, unit);
- }
-
- @Override
- public boolean awaitUntilThrowUncheckedOnInterrupt(long nanoTimeDeadline) throws UncheckedInterruptedException
- {
- return Awaitable.awaitUntilThrowUncheckedOnInterrupt(this, nanoTimeDeadline);
- }
-
- @Override
- public boolean awaitUntilUninterruptibly(long nanoTimeDeadline)
- {
- return Awaitable.awaitUntilUninterruptibly(this, nanoTimeDeadline);
- }
-
- /**
- * Wait for this future to complete {@link Awaitable#awaitUninterruptibly()}
- */
- @Override
- public Future<V> awaitUninterruptibly()
- {
- return Awaitable.awaitUninterruptibly(this);
- }
-
- /**
- * Wait for this future to complete {@link Awaitable#awaitThrowUncheckedOnInterrupt()}
- */
- @Override
- public Future<V> awaitThrowUncheckedOnInterrupt() throws UncheckedInterruptedException
- {
- return Awaitable.awaitThrowUncheckedOnInterrupt(this);
- }
-
- public String toString()
- {
- String description = description();
- String state = state();
- return description == null ? state : (state + ' ' + description);
- }
-
- private String state()
- {
- Object result = this.result;
- if (isSuccess(result))
- return "(success: " + result + ')';
- if (result == UNCANCELLABLE)
- return "(uncancellable)";
- if (result == CANCELLED)
- return "(cancelled)";
- if (isDone(result))
- return "(failure: " + ((FailureHolder) result).cause + ')';
- return "(incomplete)";
- }
-
- protected String description()
- {
- return null;
- }
-
- /**
- * @return a listener that will propagate to {@code to} the result of the future it is invoked with
- */
- private static <V> GenericFutureListener<? extends Future<V>> propagate(AbstractFuture<? super V> to)
- {
- return from -> {
- if (from.isSuccess()) to.trySuccess(from.getNow());
- else to.tryFailure(from.cause());
- };
- }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AsyncFuture.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AsyncFuture.java
deleted file mode 100644
index bace7bd..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AsyncFuture.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-import com.google.common.util.concurrent.AsyncFunction;
-import com.google.common.util.concurrent.ListenableFuture;
-import io.netty.util.concurrent.GenericFutureListener;
-
-import javax.annotation.Nullable;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import java.util.function.Function;
-
-/**
- * Our default {@link Future} implementation, with all state being managed without locks (except those used by the JVM).
- *
- * Some implementation comments versus Netty's default promise:
- * - We permit efficient initial state declaration, avoiding unnecessary CAS or lock acquisitions when mutating
- * a Promise we are ourselves constructing (and can easily add more; only those we use have been added)
- * - We guarantee the order of invocation of listeners (and callbacks etc, and with respect to each other)
- * - We save some space when registering listeners, especially if there is only one listener, as we perform no
- * extra allocations in this case.
- * - We implement our invocation list as a concurrent stack, that is cleared on notification
- * - We handle special values slightly differently.
- * - We do not use a special value for null, instead using a special value to indicate the result has not been set.
- * This means that once isSuccess() holds, the result must be a correctly typed object (modulo generics pitfalls).
- * - All special values are also instances of FailureHolder, which simplifies a number of the logical conditions.
- */
-public class AsyncFuture<V> extends AbstractFuture<V>
-{
- @SuppressWarnings({ "rawtypes" })
- private static final AtomicReferenceFieldUpdater<AsyncFuture, WaitQueue> waitingUpdater = AtomicReferenceFieldUpdater.newUpdater(AsyncFuture.class, WaitQueue.class, "waiting");
- @SuppressWarnings({ "unused" })
- private volatile WaitQueue waiting;
-
- public AsyncFuture()
- {
- super();
- }
-
- protected AsyncFuture(V immediateSuccess)
- {
- super(immediateSuccess);
- }
-
- protected AsyncFuture(Throwable immediateFailure)
- {
- super(immediateFailure);
- }
-
- protected AsyncFuture(FailureHolder initialState)
- {
- super(initialState);
- }
-
- protected AsyncFuture(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
- {
- super(listener);
- }
-
- protected AsyncFuture(FailureHolder initialState, GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
- {
- super(initialState, listener);
- }
-
- /**
- * Shared implementation of various promise completion methods.
- * Updates the result if it is possible to do so, returning success/failure.
- *
- * If the promise is UNSET the new value will succeed;
- * if it is UNCANCELLABLE it will succeed only if the new value is not CANCELLED
- * otherwise it will fail, as isDone() is implied
- *
- * If the update succeeds, and the new state implies isDone(), any listeners and waiters will be notified
- */
- boolean trySet(Object v)
- {
- while (true)
- {
- Object current = result;
- if (isDone(current) || (current == UNCANCELLABLE && (v == CANCELLED || v == UNCANCELLABLE)))
- return false;
- if (resultUpdater.compareAndSet(this, current, v))
- {
- if (v != UNCANCELLABLE)
- {
- ListenerList.notify(listenersUpdater, this);
- AsyncAwaitable.signalAll(waitingUpdater, this);
- }
- return true;
- }
- }
- }
-
- /**
- * Logically append {@code newListener} to {@link #listeners}
- * (at this stage it is a stack, so we actually prepend)
- *
- * @param newListener must be either a {@link ListenerList} or {@link GenericFutureListener}
- */
- void appendListener(ListenerList<V> newListener)
- {
- ListenerList.push(listenersUpdater, this, newListener);
- if (isDone())
- ListenerList.notify(listenersUpdater, this);
- }
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#transform(ListenableFuture, com.google.common.base.Function, Executor)} natively
- *
- * See {@link #addListener(GenericFutureListener)} for ordering semantics.
- */
- @Override
- public <T> Future<T> map(Function<? super V, ? extends T> mapper, Executor executor)
- {
- return map(new AsyncFuture<>(), mapper, executor);
- }
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively
- *
- * See {@link #addListener(GenericFutureListener)} for ordering semantics.
- */
- @Override
- public <T> Future<T> flatMap(Function<? super V, ? extends Future<T>> flatMapper, @Nullable Executor executor)
- {
- return flatMap(new AsyncFuture<>(), flatMapper, executor);
- }
-
- /**
- * Wait for this future to complete {@link Awaitable#await()}
- */
- @Override
- public AsyncFuture<V> await() throws InterruptedException
- {
- //noinspection unchecked
- return AsyncAwaitable.await(waitingUpdater, Future::isDone, this);
- }
-
- @Override
- public boolean awaitUntil(long nanoTimeDeadline) throws InterruptedException
- {
- return AsyncAwaitable.awaitUntil(waitingUpdater, Future::isDone, this, nanoTimeDeadline);
- }
-}
-
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AsyncPromise.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AsyncPromise.java
deleted file mode 100644
index b97c3a2..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AsyncPromise.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-import com.google.common.util.concurrent.FutureCallback;
-import io.netty.util.concurrent.Future;
-import io.netty.util.concurrent.GenericFutureListener;
-
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Extends {@link AsyncFuture} to implement the {@link Promise} interface.
- */
-public class AsyncPromise<V> extends AsyncFuture<V> implements Promise<V>
-{
- public static class WithExecutor<V> extends AsyncPromise<V>
- {
- final Executor notifyExecutor;
- protected WithExecutor(Executor notifyExecutor)
- {
- this.notifyExecutor = notifyExecutor;
- }
-
- protected WithExecutor(Executor notifyExecutor, FailureHolder initialState)
- {
- super(initialState);
- this.notifyExecutor = notifyExecutor;
- }
-
- protected WithExecutor(Executor notifyExecutor, GenericFutureListener<? extends Future<? super V>> listener)
- {
- super(listener);
- this.notifyExecutor = notifyExecutor;
- }
-
- @Override
- public Executor notifyExecutor()
- {
- return notifyExecutor;
- }
- }
-
- public AsyncPromise() {}
-
- AsyncPromise(FailureHolder initialState)
- {
- super(initialState);
- }
-
- public AsyncPromise(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
- {
- super(listener);
- }
-
- AsyncPromise(FailureHolder initialState, GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
- {
- super(initialState, listener);
- }
-
- public static <V> AsyncPromise<V> withExecutor(Executor executor)
- {
- return new WithExecutor<>(executor);
- }
-
- public static <V> AsyncPromise<V> uncancellable()
- {
- return new AsyncPromise<>(UNCANCELLABLE);
- }
-
- public static <V> AsyncPromise<V> uncancellable(Executor executor)
- {
- return new WithExecutor<>(executor, UNCANCELLABLE);
- }
-
- public static <V> AsyncPromise<V> uncancellable(GenericFutureListener<? extends Future<? super V>> listener)
- {
- return new AsyncPromise<>(UNCANCELLABLE, listener);
- }
-
- /**
- * Complete the promise successfully if not already complete
- * @throws IllegalStateException if already set
- */
- @Override
- public Promise<V> setSuccess(V v)
- {
- if (!trySuccess(v))
- throw new IllegalStateException("complete already: " + this);
- return this;
- }
-
- /**
- * Complete the promise successfully if not already complete
- * @return true iff completed promise
- */
- @Override
- public boolean trySuccess(V v)
- {
- return super.trySuccess(v);
- }
-
- /**
- * Complete the promise abnormally if not already complete
- * @throws IllegalStateException if already set
- */
- @Override
- public Promise<V> setFailure(Throwable throwable)
- {
- if (!tryFailure(throwable))
- throw new IllegalStateException("complete already: " + this);
- return this;
- }
-
- /**
- * Complete the promise abnormally if not already complete
- * @return true iff completed promise
- */
- @Override
- public boolean tryFailure(Throwable throwable)
- {
- return super.tryFailure(throwable);
- }
-
- /**
- * Prevent a future caller from cancelling this promise
- * @return true if the promise is now uncancellable (whether or not we did this)
- */
- @Override
- public boolean setUncancellable()
- {
- return super.setUncancellable();
- }
-
- /**
- * Prevent a future caller from cancelling this promise
- * @return true iff this invocation set it to uncancellable, whether or not now uncancellable
- */
- @Override
- public boolean setUncancellableExclusive()
- {
- return super.setUncancellableExclusive();
- }
-
- @Override
- public boolean isUncancellable()
- {
- return super.isUncancellable();
- }
-
- /**
- * waits for completion; in case of failure rethrows the original exception without a new wrapping exception
- * so may cause problems for reporting stack traces
- */
- @Override
- public Promise<V> sync() throws InterruptedException
- {
- super.sync();
- return this;
- }
-
- /**
- * waits for completion; in case of failure rethrows the original exception without a new wrapping exception
- * so may cause problems for reporting stack traces
- */
- @Override
- public Promise<V> syncUninterruptibly()
- {
- super.syncUninterruptibly();
- return this;
- }
-
- @Override
- public AsyncPromise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener)
- {
- super.addListener(listener);
- return this;
- }
-
- @Override
- public AsyncPromise<V> addListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... listeners)
- {
- super.addListeners(listeners);
- return this;
- }
-
- @Override
- public AsyncPromise<V> removeListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
- {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public AsyncPromise<V> removeListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... listeners)
- {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public AsyncPromise<V> addCallback(FutureCallback<? super V> callback)
- {
- super.addCallback(callback);
- return this;
- }
-
- @Override
- public AsyncPromise<V> addCallback(FutureCallback<? super V> callback, Executor executor)
- {
- super.addCallback(callback, executor);
- return this;
- }
-
- @Override
- public AsyncPromise<V> addCallback(Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure)
- {
- super.addCallback(onSuccess, onFailure);
- return this;
- }
-
- /**
- * Wait for this promise to complete
- * @throws InterruptedException if interrupted
- */
- @Override
- public AsyncPromise<V> await() throws InterruptedException
- {
- super.await();
- return this;
- }
-
- /**
- * Wait uninterruptibly for this promise to complete
- */
- @Override
- public AsyncPromise<V> awaitUninterruptibly()
- {
- super.awaitUninterruptibly();
- return this;
- }
-
- /**
- * Wait for this promise to complete, throwing any interrupt as an UncheckedInterruptedException
- * @throws UncheckedInterruptedException if interrupted
- */
- @Override
- public AsyncPromise<V> awaitThrowUncheckedOnInterrupt() throws UncheckedInterruptedException
- {
- super.awaitThrowUncheckedOnInterrupt();
- return this;
- }
-}
-
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Awaitable.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Awaitable.java
deleted file mode 100644
index f75582d..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Awaitable.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import java.util.function.Predicate;
-
-//import net.nicoulaj.compilecommand.annotations.Inline;
-import org.apache.cassandra.utils.Shared;
-
-import org.apache.cassandra.utils.Intercept;
-
-import static org.apache.cassandra.utils.Clock.Global.nanoTime;
-import static org.apache.cassandra.utils.concurrent.WaitQueue.newWaitQueue;
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * A generic signal consumer, supporting all of the typical patterns used in Cassandra.
- * All of the methods defined in {@link Awaitable} may be waited on without a loop,
- * as this interface declares that there are no spurious wake-ups.
- */
-@Shared(scope = SIMULATION)
-public interface Awaitable
-{
- /**
- * Await until the deadline (in nanoTime), throwing any interrupt.
- * No spurious wakeups.
- * @return true if we were signalled, false if the deadline elapsed
- * @throws InterruptedException if interrupted
- */
- boolean awaitUntil(long nanoTimeDeadline) throws InterruptedException;
-
- /**
- * Await until the deadline (in nanoTime), throwing any interrupt as an unchecked exception.
- * No spurious wakeups.
- * @return true if we were signalled, false if the deadline elapsed
- * @throws UncheckedInterruptedException if interrupted
- */
- boolean awaitUntilThrowUncheckedOnInterrupt(long nanoTimeDeadline) throws UncheckedInterruptedException;
-
- /**
- * Await until the deadline (in nanoTime), ignoring interrupts (but maintaining the interrupt flag on exit).
- * No spurious wakeups.
- * @return true if we were signalled, false if the deadline elapsed
- */
- boolean awaitUntilUninterruptibly(long nanoTimeDeadline);
-
- /**
- * Await for the specified period, throwing any interrupt.
- * No spurious wakeups.
- * @return true if we were signalled, false if the timeout elapses
- * @throws InterruptedException if interrupted
- */
- boolean await(long time, TimeUnit units) throws InterruptedException;
-
- /**
- * Await for the specified period, throwing any interrupt as an unchecked exception.
- * No spurious wakeups.
- * @return true if we were signalled, false if the timeout elapses
- * @throws UncheckedInterruptedException if interrupted
- */
- boolean awaitThrowUncheckedOnInterrupt(long time, TimeUnit units) throws UncheckedInterruptedException;
-
- /**
- * Await until the deadline (in nanoTime), ignoring interrupts (but maintaining the interrupt flag on exit).
- * No spurious wakeups.
- * @return true if we were signalled, false if the timeout elapses
- */
- boolean awaitUninterruptibly(long time, TimeUnit units);
-
- /**
- * Await indefinitely, throwing any interrupt.
- * No spurious wakeups.
- * @throws InterruptedException if interrupted
- */
- Awaitable await() throws InterruptedException;
-
- /**
- * Await indefinitely, throwing any interrupt as an unchecked exception.
- * No spurious wakeups.
- * @throws UncheckedInterruptedException if interrupted
- */
- Awaitable awaitThrowUncheckedOnInterrupt() throws UncheckedInterruptedException;
-
- /**
- * Await indefinitely, ignoring interrupts (but maintaining the interrupt flag on exit).
- * No spurious wakeups.
- */
- Awaitable awaitUninterruptibly();
-
- public static boolean await(Awaitable await, long time, TimeUnit unit) throws InterruptedException
- {
- return await.awaitUntil(nanoTime() + unit.toNanos(time));
- }
-
- public static boolean awaitThrowUncheckedOnInterrupt(Awaitable await, long time, TimeUnit units) throws UncheckedInterruptedException
- {
- return awaitUntilThrowUncheckedOnInterrupt(await, nanoTime() + units.toNanos(time));
- }
-
- public static boolean awaitUninterruptibly(Awaitable await, long time, TimeUnit units)
- {
- return awaitUntilUninterruptibly(await, nanoTime() + units.toNanos(time));
- }
-
- public static <A extends Awaitable> A awaitThrowUncheckedOnInterrupt(A await) throws UncheckedInterruptedException
- {
- try
- {
- await.await();
- }
- catch (InterruptedException e)
- {
- throw new UncheckedInterruptedException();
- }
- return await;
- }
-
- public static boolean awaitUntilThrowUncheckedOnInterrupt(Awaitable await, long nanoTimeDeadline) throws UncheckedInterruptedException
- {
- try
- {
- return await.awaitUntil(nanoTimeDeadline);
- }
- catch (InterruptedException e)
- {
- throw new UncheckedInterruptedException();
- }
- }
-
- /**
- * {@link Awaitable#awaitUntilUninterruptibly(long)}
- */
- public static boolean awaitUntilUninterruptibly(Awaitable await, long nanoTimeDeadline)
- {
- boolean interrupted = false;
- boolean result;
- while (true)
- {
- try
- {
- result = await.awaitUntil(nanoTimeDeadline);
- break;
- }
- catch (InterruptedException e)
- {
- interrupted = true;
- }
- }
- if (interrupted)
- Thread.currentThread().interrupt();
- return result;
- }
-
- /**
- * {@link Awaitable#awaitUninterruptibly()}
- */
- public static <A extends Awaitable> A awaitUninterruptibly(A await)
- {
- boolean interrupted = false;
- while (true)
- {
- try
- {
- await.await();
- break;
- }
- catch (InterruptedException e)
- {
- interrupted = true;
- }
- }
- if (interrupted)
- Thread.currentThread().interrupt();
- return await;
- }
-
- abstract class AbstractAwaitable implements Awaitable
- {
- protected AbstractAwaitable() {}
-
- /**
- * {@link Awaitable#await(long, TimeUnit)}
- */
- @Override
- public boolean await(long time, TimeUnit unit) throws InterruptedException
- {
- return Awaitable.await(this, time, unit);
- }
-
- /**
- * {@link Awaitable#awaitThrowUncheckedOnInterrupt(long, TimeUnit)}
- */
- @Override
- public boolean awaitThrowUncheckedOnInterrupt(long time, TimeUnit units) throws UncheckedInterruptedException
- {
- return Awaitable.awaitThrowUncheckedOnInterrupt(this, time, units);
- }
-
- /**
- * {@link Awaitable#awaitUninterruptibly(long, TimeUnit)}
- */
- public boolean awaitUninterruptibly(long time, TimeUnit units)
- {
- return awaitUntilUninterruptibly(nanoTime() + units.toNanos(time));
- }
-
- /**
- * {@link Awaitable#awaitThrowUncheckedOnInterrupt()}
- */
- public Awaitable awaitThrowUncheckedOnInterrupt() throws UncheckedInterruptedException
- {
- return Awaitable.awaitThrowUncheckedOnInterrupt(this);
- }
-
- /**
- * {@link Awaitable#awaitUntilThrowUncheckedOnInterrupt(long)}
- */
- public boolean awaitUntilThrowUncheckedOnInterrupt(long nanoTimeDeadline) throws UncheckedInterruptedException
- {
- return Awaitable.awaitUntilThrowUncheckedOnInterrupt(this, nanoTimeDeadline);
- }
-
- /**
- * {@link Awaitable#awaitUntilUninterruptibly(long)}
- */
- public boolean awaitUntilUninterruptibly(long nanoTimeDeadline)
- {
- return Awaitable.awaitUntilUninterruptibly(this, nanoTimeDeadline);
- }
-
- /**
- * {@link Awaitable#awaitUninterruptibly()}
- */
- public Awaitable awaitUninterruptibly()
- {
- return Awaitable.awaitUninterruptibly(this);
- }
- }
-
- /**
- * A barebones asynchronous {@link Awaitable}.
- * If your state is minimal, or can be updated concurrently, extend this class.
- */
- abstract class AsyncAwaitable extends AbstractAwaitable
- {
- /**
- * Maintain an internal variable containing a lazily-initialized wait queue
- * @return null if is done
- */
- @Inline
- private static <A extends Awaitable> WaitQueue.Signal register(AtomicReferenceFieldUpdater<A, WaitQueue> waitingUpdater, Predicate<A> isDone, A awaitable)
- {
- if (isDone.test(awaitable))
- return null;
-
- WaitQueue waiting = waitingUpdater.get(awaitable);
- if (waiting == null)
- {
- if (!waitingUpdater.compareAndSet(awaitable, null, waiting = newWaitQueue()))
- {
- waiting = waitingUpdater.get(awaitable);
- if (waiting == null)
- {
- assert isDone.test(awaitable);
- return null;
- }
- }
- }
-
- WaitQueue.Signal s = waiting.register();
- if (!isDone.test(awaitable))
- return s;
-
- s.cancel();
- return null;
- }
-
- @Inline
- static <A extends Awaitable> A await(AtomicReferenceFieldUpdater<A, WaitQueue> waitingUpdater, Predicate<A> isDone, A awaitable) throws InterruptedException
- {
- WaitQueue.Signal s = register(waitingUpdater, isDone, awaitable);
- if (s != null)
- s.await();
- return awaitable;
- }
-
- @Inline
- static <A extends Awaitable> boolean awaitUntil(AtomicReferenceFieldUpdater<A, WaitQueue> waitingUpdater, Predicate<A> isDone, A awaitable, long nanoTimeDeadline) throws InterruptedException
- {
- WaitQueue.Signal s = register(waitingUpdater, isDone, awaitable);
- return s == null || s.awaitUntil(nanoTimeDeadline) || isDone.test(awaitable);
- }
-
- @Inline
- static <A extends Awaitable> void signalAll(AtomicReferenceFieldUpdater<A, WaitQueue> waitingUpdater, A awaitable)
- {
- WaitQueue waiting = waitingUpdater.get(awaitable);
- if (waiting == null)
- return;
-
- waiting.signalAll();
- waitingUpdater.lazySet(awaitable, null);
- }
-
- private static final AtomicReferenceFieldUpdater<AsyncAwaitable, WaitQueue> waitingUpdater = AtomicReferenceFieldUpdater.newUpdater(AsyncAwaitable.class, WaitQueue.class, "waiting");
- private volatile WaitQueue waiting;
-
- protected AsyncAwaitable() {}
-
- /**
- * {@link Awaitable#await()}
- */
- public Awaitable await() throws InterruptedException
- {
- return await(waitingUpdater, AsyncAwaitable::isSignalled, this);
- }
-
- /**
- * {@link Awaitable#awaitUntil(long)}
- */
- public boolean awaitUntil(long nanoTimeDeadline) throws InterruptedException
- {
- return awaitUntil(waitingUpdater, AsyncAwaitable::isSignalled, this, nanoTimeDeadline);
- }
-
- /**
- * Signal any waiting threads; {@link #isSignalled()} must return {@code true} before this method is invoked.
- */
- protected void signal()
- {
- signalAll(waitingUpdater, this);
- }
-
- /**
- * Return true once signalled. Unidirectional; once true, must never again be false.
- */
- protected abstract boolean isSignalled();
- }
-
- /**
- * A barebones {@link Awaitable} that uses mutual exclusion.
- * If your state will be updated while holding the object monitor, extend this class.
- */
- abstract class SyncAwaitable extends AbstractAwaitable
- {
- protected SyncAwaitable() {}
-
- /**
- * {@link Awaitable#await()}
- */
- public synchronized Awaitable await() throws InterruptedException
- {
- while (!isSignalled())
- wait();
- return this;
- }
-
- /**
- * {@link Awaitable#awaitUntil(long)}
- */
- public synchronized boolean awaitUntil(long nanoTimeDeadline) throws InterruptedException
- {
- while (true)
- {
- if (isSignalled()) return true;
- if (!waitUntil(this, nanoTimeDeadline)) return false;
- }
- }
-
- /**
- * Return true once signalled. Unidirectional; once true, must never again be false.
- */
- protected abstract boolean isSignalled();
-
- @Intercept
- public static boolean waitUntil(Object monitor, long deadlineNanos) throws InterruptedException
- {
- long wait = deadlineNanos - nanoTime();
- if (wait <= 0)
- return false;
-
- monitor.wait((wait + 999999) / 1000000);
- return true;
- }
- }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Condition.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Condition.java
deleted file mode 100644
index eb97848..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Condition.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-import org.apache.cassandra.utils.Intercept;
-import org.apache.cassandra.utils.Shared;
-
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * Simpler API than java.util.concurrent.Condition; would be nice to extend it, but also nice
- * to share API with Future, for which Netty's API is incompatible with java.util.concurrent.Condition
- *
- * {@link Awaitable} for explicit external signals.
- */
-@Shared(scope = SIMULATION)
-public interface Condition extends Awaitable
-{
- /**
- * Returns true once signalled. Unidirectional; once true, will never again be false.
- */
- boolean isSignalled();
-
- /**
- * Signal the condition as met, and wake all waiting threads.
- */
- void signal();
-
- /**
- * Signal the condition as met, and wake all waiting threads.
- */
- default void signalAll() { signal(); }
-
- /**
- * Factory method used to capture and redirect instantiations for simulation
- */
- @Intercept
- static Condition newOneTimeCondition()
- {
- return new Async();
- }
-
- /**
- * An asynchronous {@link Condition}. Typically lower overhead than {@link Sync}.
- */
- public static class Async extends AsyncAwaitable implements Condition
- {
- private volatile boolean signaled = false;
-
- // WARNING: if extending this class, consider simulator interactions
- protected Async() {}
-
- public boolean isSignalled()
- {
- return signaled;
- }
-
- public void signal()
- {
- signaled = true;
- super.signal();
- }
- }
-
- /**
- * A {@link Condition} based on its object monitor.
- * WARNING: lengthy operations performed while holding the lock may prevent timely notification of waiting threads
- * that a deadline has passed.
- */
- public static class Sync extends SyncAwaitable implements Condition
- {
- private boolean signaled = false;
-
- // this can be instantiated directly, as we intercept monitors directly with byte weaving
- public Sync() {}
-
- public synchronized boolean isSignalled()
- {
- return signaled;
- }
-
- public synchronized void signal()
- {
- signaled = true;
- notifyAll();
- }
- }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Future.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Future.java
deleted file mode 100644
index 69dc83d..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Future.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-import java.util.concurrent.Executor;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-import com.google.common.util.concurrent.AsyncFunction;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.ListenableFuture; // checkstyle: permit this import
-
-import io.netty.util.concurrent.GenericFutureListener;
-
-import io.netty.util.internal.PlatformDependent;
-import org.apache.cassandra.utils.Shared;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.apache.cassandra.utils.Shared.Recursive.INTERFACES;
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * A Future that integrates several different (but equivalent) APIs used within Cassandra into a single concept,
- * integrating also with our {@link Awaitable} abstraction, to overall improve coherency and clarity in the codebase.
- */
-@Shared(scope = SIMULATION, ancestors = INTERFACES)
-public interface Future<V> extends io.netty.util.concurrent.Future<V>, ListenableFuture<V>, Awaitable
-{
- /**
- * Wait indefinitely for this future to complete, throwing any interrupt
- * @throws InterruptedException if interrupted
- */
- @Override
- Future<V> await() throws InterruptedException;
-
- /**
- * Wait indefinitely for this future to complete
- */
- @Override
- Future<V> awaitUninterruptibly();
-
- /**
- * Wait indefinitely for this promise to complete, throwing any interrupt as an UncheckedInterruptedException
- * @throws UncheckedInterruptedException if interrupted
- */
- @Override
- Future<V> awaitThrowUncheckedOnInterrupt();
-
- default void rethrowIfFailed()
- {
- Throwable cause = this.cause();
- if (cause != null)
- {
- PlatformDependent.throwException(cause);
- }
- }
-
- /**
- * waits for completion; in case of failure rethrows the original exception without a new wrapping exception
- * so may cause problems for reporting stack traces
- */
- @Override
- default Future<V> sync() throws InterruptedException
- {
- await();
- rethrowIfFailed();
- return this;
- }
-
- /**
- * waits for completion; in case of failure rethrows the original exception without a new wrapping exception
- * so may cause problems for reporting stack traces
- */
- @Override
- default Future<V> syncUninterruptibly()
- {
- awaitUninterruptibly();
- rethrowIfFailed();
- return this;
- }
-
- @Deprecated
- @Override
- default boolean await(long l) throws InterruptedException
- {
- return await(l, MILLISECONDS);
- }
-
- @Deprecated
- @Override
- default boolean awaitUninterruptibly(long l)
- {
- return awaitUninterruptibly(l, MILLISECONDS);
- }
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
- */
- Future<V> addCallback(BiConsumer<? super V, Throwable> callback);
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
- */
- Future<V> addCallback(BiConsumer<? super V, Throwable> callback, Executor executor);
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
- */
- Future<V> addCallback(FutureCallback<? super V> callback);
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
- */
- Future<V> addCallback(FutureCallback<? super V> callback, Executor executor);
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
- */
- Future<V> addCallback(Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure);
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
- */
- Future<V> addCallback(Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure, Executor executor);
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#transform(ListenableFuture, com.google.common.base.Function, Executor)} natively
- */
- default <T> Future<T> map(Function<? super V, ? extends T> mapper)
- {
- return map(mapper, null);
- }
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#transform(ListenableFuture, com.google.common.base.Function, Executor)} natively
- */
- <T> Future<T> map(Function<? super V, ? extends T> mapper, Executor executor);
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively
- */
- default <T> Future<T> flatMap(Function<? super V, ? extends Future<T>> flatMapper)
- {
- return flatMap(flatMapper, null);
- }
-
- /**
- * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively
- */
- <T> Future<T> flatMap(Function<? super V, ? extends Future<T>> flatMapper, Executor executor);
-
- /**
- * Invoke {@code runnable} on completion, using {@code executor}.
- *
- * Tasks are submitted to their executors in the order they were added to this Future.
- */
- @Override
- void addListener(Runnable runnable, Executor executor);
-
- /**
- * Invoke {@code runnable} on completion. Depending on the implementation and its configuration, this
- * may be executed immediately by the notifying/completing thread, or asynchronously by an executor.
- * Tasks are executed, or submitted to the executor, in the order they were added to this Future.
- */
- void addListener(Runnable runnable);
-
- Executor notifyExecutor();
-
- @Override Future<V> addListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> genericFutureListener);
- @Override Future<V> addListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... genericFutureListeners);
- @Override Future<V> removeListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> genericFutureListener);
- @Override Future<V> removeListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... genericFutureListeners);
-}
-
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/ImmediateFuture.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/ImmediateFuture.java
deleted file mode 100644
index 159d580..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/ImmediateFuture.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-public class ImmediateFuture<V> extends AsyncFuture<V>
-{
- private ImmediateFuture(V value)
- {
- super(value);
- }
-
- private ImmediateFuture(Throwable cause)
- {
- super(cause);
- }
-
- private ImmediateFuture(FailureHolder failure)
- {
- super(failure);
- }
-
- public static <V> ImmediateFuture<V> success(V value)
- {
- return new ImmediateFuture<>(value);
- }
-
- public static <V> ImmediateFuture<V> failure(Throwable cause)
- {
- return new ImmediateFuture<>(cause);
- }
-
- public static <V> ImmediateFuture<V> cancelled()
- {
- return new ImmediateFuture<>(CANCELLED);
- }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Inline.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Inline.java
deleted file mode 100644
index 1602bb7..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Inline.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-public @interface Inline
-{
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/IntrusiveStack.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/IntrusiveStack.java
deleted file mode 100644
index 7ff7387..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/IntrusiveStack.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-//import net.nicoulaj.compilecommand.annotations.Inline;
-
-import java.util.Iterator;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import java.util.function.BiFunction;
-import java.util.function.Consumer;
-
-/**
- * An efficient stack/list that is expected to be ordinarily either empty or close to, and for which
- * we need concurrent insertions and do not need to support removal - i.e. the list is semi immutable.
- *
- * This is an intrusive stack, and for simplicity we treat all
- *
- * @param <T>
- */
-public class IntrusiveStack<T extends IntrusiveStack<T>> implements Iterable<T>
-{
- static class Itr<T extends IntrusiveStack<T>> implements Iterator<T>
- {
- private T next;
-
- Itr(T next)
- {
- this.next = next;
- }
-
- @Override
- public boolean hasNext()
- {
- return next != null;
- }
-
- @Override
- public T next()
- {
- T result = next;
- next = result.next;
- return result;
- }
- }
-
- T next;
-
- @Inline
- protected static <O, T extends IntrusiveStack<T>> void push(AtomicReferenceFieldUpdater<? super O, T> headUpdater, O owner, T prepend)
- {
- push(headUpdater, owner, prepend, (prev, next) -> {
- next.next = prev;
- return next;
- });
- }
-
- protected static <O, T extends IntrusiveStack<T>> void push(AtomicReferenceFieldUpdater<O, T> headUpdater, O owner, T prepend, BiFunction<T, T, T> combine)
- {
- while (true)
- {
- T head = headUpdater.get(owner);
- if (headUpdater.compareAndSet(owner, head, combine.apply(head, prepend)))
- return;
- }
- }
-
- protected static <O, T extends IntrusiveStack<T>> void pushExclusive(AtomicReferenceFieldUpdater<O, T> headUpdater, O owner, T prepend, BiFunction<T, T, T> combine)
- {
- T head = headUpdater.get(owner);
- headUpdater.lazySet(owner, combine.apply(head, prepend));
- }
-
- protected static <T extends IntrusiveStack<T>, O> Iterable<T> iterable(AtomicReferenceFieldUpdater<O, T> headUpdater, O owner)
- {
- return iterable(headUpdater.get(owner));
- }
-
- protected static <T extends IntrusiveStack<T>> Iterable<T> iterable(T list)
- {
- return list == null ? () -> iterator(null) : list;
- }
-
- protected static <T extends IntrusiveStack<T>> Iterator<T> iterator(T list)
- {
- return new Itr<>(list);
- }
-
- protected static int size(IntrusiveStack<?> list)
- {
- int size = 0;
- while (list != null)
- {
- ++size;
- list = list.next;
- }
- return size;
- }
-
- // requires exclusive ownership (incl. with readers)
- protected T reverse()
- {
- return reverse((T) this);
- }
-
- // requires exclusive ownership (incl. with readers)
- protected static <T extends IntrusiveStack<T>> T reverse(T list)
- {
- T prev = null;
- T cur = list;
- while (cur != null)
- {
- T next = cur.next;
- cur.next = prev;
- prev = cur;
- cur = next;
- }
- return prev;
- }
-
- @Override
- public void forEach(Consumer<? super T> forEach)
- {
- forEach((T)this, forEach);
- }
-
- protected static <T extends IntrusiveStack<T>> void forEach(T list, Consumer<? super T> forEach)
- {
- while (list != null)
- {
- forEach.accept(list);
- list = list.next;
- }
- }
-
- @Override
- public Iterator<T> iterator()
- {
- return new Itr<>((T) this);
- }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/ListenerList.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/ListenerList.java
deleted file mode 100644
index c82b80c..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/ListenerList.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-import com.google.common.util.concurrent.FutureCallback;
-import io.netty.util.concurrent.EventExecutor;
-import io.netty.util.concurrent.GenericFutureListener;
-//import net.nicoulaj.compilecommand.annotations.Inline;
-import org.apache.cassandra.concurrent.ExecutionFailure;
-import org.apache.cassandra.concurrent.ExecutorPlus;
-import org.apache.cassandra.concurrent.ImmediateExecutor;
-
-import javax.annotation.Nullable;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-
-import static org.apache.cassandra.utils.concurrent.ListenerList.Notifying.NOTIFYING;
-
-/**
- * Encapsulate one or more items in a linked-list that is immutable whilst shared, forming a prepend-only list (or stack).
- * Once the list is ready to consume, exclusive ownership is taken by clearing the shared variable containing it, after
- * which the list may be invoked using {@link #notify}, which reverses the list before invoking the work it contains.
- */
-abstract class ListenerList<V> extends IntrusiveStack<ListenerList<V>>
-{
- abstract void notifySelf(Executor notifyExecutor, Future<V> future);
-
- static ListenerList pushHead(ListenerList prev, ListenerList next)
- {
- if (prev instanceof Notifying<?>)
- {
- Notifying result = new Notifying();
- result.next = next;
- next.next = prev == NOTIFYING ? null : prev;
- return result;
- }
- next.next = prev;
- return next;
- }
-
- /**
- * Logically append {@code newListener} to {@link #listeners}
- * (at this stage it is a stack, so we actually prepend)
- *
- * @param newListener must be either a {@link ListenerList} or {@link GenericFutureListener}
- */
- @Inline
- static <T> void push(AtomicReferenceFieldUpdater<? super T, ListenerList> updater, T in, ListenerList newListener)
- {
- IntrusiveStack.push(updater, in, newListener, ListenerList::pushHead);
- }
-
- /**
- * Logically append {@code newListener} to {@link #listeners}
- * (at this stage it is a stack, so we actually prepend)
- *
- * @param newListener must be either a {@link ListenerList} or {@link GenericFutureListener}
- */
- @Inline
- static <T> void pushExclusive(AtomicReferenceFieldUpdater<? super T, ListenerList> updater, T in, ListenerList newListener)
- {
- IntrusiveStack.pushExclusive(updater, in, newListener, ListenerList::pushHead);
- }
-
- static <V, T extends Future<V>> void notify(AtomicReferenceFieldUpdater<? super T, ListenerList> updater, T in)
- {
- while (true)
- {
- ListenerList<V> listeners = updater.get(in);
- if (listeners == null || listeners instanceof Notifying)
- return; // either no listeners, or we are already notifying listeners, so we'll get to the new one when ready
-
- if (updater.compareAndSet(in, listeners, NOTIFYING))
- {
- while (true)
- {
- notify(listeners, in);
- if (updater.compareAndSet(in, NOTIFYING, null))
- return;
-
- listeners = updater.getAndSet(in, NOTIFYING);
- }
- }
- }
- }
-
- /**
- * Requires exclusive ownership of {@code head}.
- *
- * Task submission occurs in the order the operations were submitted; if all of the executors
- * are immediate or unspecified this guarantees execution order.
- * Tasks are submitted to the executor individually, as this simplifies semantics and
- * we anticipate few listeners in practice, and even fewer with indirect executors.
- *
- * @param head must be either a {@link ListenerList} or {@link GenericFutureListener}
- */
- static <T> void notify(ListenerList<T> head, Future<T> future)
- {
- Executor notifyExecutor = future.notifyExecutor();
- if (inExecutor(notifyExecutor))
- notifyExecutor = null;
-
- notify(head, notifyExecutor, future);
- }
-
- private static <T> void notify(ListenerList<T> head, Executor notifyExecutor, Future<T> future)
- {
- head = reverse(head);
- forEach(head, i -> i.notifySelf(notifyExecutor, future));
- }
-
- /**
- * Notify {@code listener} on the invoking thread, handling any exceptions
- */
- static <F extends io.netty.util.concurrent.Future<?>> void notifyListener(GenericFutureListener<F> listener, F future)
- {
- try
- {
- listener.operationComplete(future);
- }
- catch (Throwable t)
- {
- // TODO: suboptimal package interdependency - move FutureTask etc here?
- ExecutionFailure.handle(t);
- }
- }
-
- /**
- * Notify {@code listener} using {@code notifyExecutor} if set, and handling exceptions otherwise
- */
- static <F extends io.netty.util.concurrent.Future<?>> void notifyListener(Executor notifyExecutor, GenericFutureListener<F> listener, F future)
- {
- if (notifyExecutor == null) notifyListener(listener, future);
- else safeExecute(notifyExecutor, () -> notifyListener(listener, future));
- }
-
- /**
- * Notify {@code listener} using {@code notifyExecutor} if set, and handling exceptions otherwise
- */
- static void notifyListener(@Nullable Executor notifyExecutor, Runnable listener)
- {
- safeExecute(notifyExecutor, listener);
- }
-
- private static void safeExecute(@Nullable Executor notifyExecutor, Runnable runnable)
- {
- if (notifyExecutor == null)
- notifyExecutor = ImmediateExecutor.INSTANCE;
- try
- {
- notifyExecutor.execute(runnable);
- }
- catch (Exception | Error e)
- {
- // TODO: suboptimal package interdependency - move FutureTask etc here?
- ExecutionFailure.handle(e);
- }
- }
-
- /**
- * Encapsulate a regular listener in a linked list
- */
- static class GenericFutureListenerList<V> extends ListenerList<V>
- {
- final GenericFutureListener listener;
-
- GenericFutureListenerList(GenericFutureListener listener)
- {
- this.listener = listener;
- }
-
- @Override
- void notifySelf(Executor notifyExecutor, Future<V> future)
- {
- notifyListener(notifyExecutor, listener, future);
- }
- }
-
- /**
- * Encapsulates the invocation of a callback with everything needed to submit for execution
- * without incurring significant further overhead as a list
- */
- static class CallbackListener<V> extends ListenerList<V> implements Runnable
- {
- final Future<V> future;
- final FutureCallback<? super V> callback;
-
- CallbackListener(Future<V> future, FutureCallback<? super V> callback)
- {
- this.future = future;
- this.callback = callback;
- }
-
- @Override
- public void run()
- {
- if (future.isSuccess()) callback.onSuccess(future.getNow());
- else callback.onFailure(future.cause());
- }
-
- @Override
- void notifySelf(Executor notifyExecutor, Future<V> future)
- {
- notifyListener(notifyExecutor, this);
- }
- }
-
- /**
- * Encapsulates the invocation of a callback with everything needed to submit for execution
- * without incurring significant further overhead as a list
- */
- static class CallbackBiConsumerListener<V> extends ListenerList<V> implements Runnable
- {
- final Future<V> future;
- final BiConsumer<? super V, Throwable> callback;
- final Executor executor;
-
- CallbackBiConsumerListener(Future<V> future, BiConsumer<? super V, Throwable> callback, Executor executor)
- {
- this.future = future;
- this.callback = callback;
- this.executor = executor;
- }
-
- @Override
- public void run()
- {
- if (future.isSuccess()) callback.accept(future.getNow(), null);
- else callback.accept(null, future.cause());
- }
-
- @Override
- void notifySelf(Executor notifyExecutor, Future<V> future)
- {
- notifyListener(executor == null ? notifyExecutor : executor, this);
- }
- }
-
- /**
- * Encapsulates the invocation of a callback with everything needed to submit for execution
- * without incurring significant further overhead as a list
- */
- static class CallbackListenerWithExecutor<V> extends CallbackListener<V>
- {
- final Executor executor;
- CallbackListenerWithExecutor(Future<V> future, FutureCallback<? super V> callback, Executor executor)
- {
- super(future, callback);
- this.executor = executor;
- }
-
- @Override
- void notifySelf(Executor notifyExecutor, Future<V> future)
- {
- notifyListener(executor, this);
- }
- }
-
- /**
- * Encapsulates the invocation of a callback with everything needed to submit for execution
- * without incurring significant further overhead as a list
- */
- static class CallbackLambdaListener<V> extends ListenerList<V> implements Runnable
- {
- final Future<V> future;
- final Consumer<? super V> onSuccess;
- final Consumer<? super Throwable> onFailure;
- final Executor executor;
-
- CallbackLambdaListener(Future<V> future, Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure, Executor executor)
- {
- this.future = future;
- this.onSuccess = onSuccess;
- this.onFailure = onFailure;
- this.executor = executor;
- }
-
- @Override
- public void run()
- {
- if (future.isSuccess()) onSuccess.accept(future.getNow());
- else onFailure.accept(future.cause());
- }
-
- @Override
- void notifySelf(Executor notifyExecutor, Future future)
- {
- notifyListener(executor == null ? notifyExecutor : executor, this);
- }
- }
-
- /**
- * Encapsulate a task, executable on completion by {@link Future#notifyExecutor}, in a linked list for storing in
- * {@link #listeners}, either as a listener on its own (since we need to encapsulate it anyway), or alongside
- * other listeners in a list
- */
- static class RunnableWithNotifyExecutor<V> extends ListenerList<V>
- {
- final Runnable task;
- RunnableWithNotifyExecutor(Runnable task)
- {
- this.task = task;
- }
-
- @Override
- void notifySelf(Executor notifyExecutor, Future<V> future)
- {
- notifyListener(notifyExecutor, task);
- }
- }
-
- /**
- * Encapsulate a task executable on completion in a linked list for storing in {@link #listeners},
- * either as a listener on its own (since we need to encapsulate it anyway), or alongside other listeners
- * in a list
- */
- static class RunnableWithExecutor<V> extends ListenerList<V>
- {
- final Runnable task;
- @Nullable final Executor executor;
- RunnableWithExecutor(Runnable task, @Nullable Executor executor)
- {
- this.task = task;
- this.executor = executor;
- }
-
- @Override
- void notifySelf(Executor notifyExecutor, Future<V> future)
- {
- notifyListener(inExecutor(executor) ? null : executor, task);
- }
- }
-
- /**
- * Dummy that indicates listeners are already being notified after the future was completed,
- * so we cannot notify them ourselves whilst maintaining the guaranteed invocation order.
- * The invocation of the list can be left to the thread already notifying listeners.
- */
- static class Notifying<V> extends ListenerList<V>
- {
- static final Notifying NOTIFYING = new Notifying();
-
- @Override
- void notifySelf(Executor notifyExecutor, Future<V> future)
- {
- }
- }
-
- /**
- * @return true iff the invoking thread is executing {@code executor}
- */
- static boolean inExecutor(Executor executor)
- {
- return (executor instanceof EventExecutor && ((EventExecutor) executor).inEventLoop())
- || (executor instanceof ExecutorPlus && ((ExecutorPlus) executor).inExecutor());
- }
-}
-
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Promise.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Promise.java
deleted file mode 100644
index 2e84d33..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Promise.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-import com.google.common.util.concurrent.FutureCallback;
-import io.netty.util.concurrent.GenericFutureListener;
-import org.apache.cassandra.utils.Shared;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
-
-import static org.apache.cassandra.utils.Shared.Recursive.INTERFACES;
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * A Promise that integrates {@link io.netty.util.concurrent.Promise} with our {@link Future} API
- * to improve clarity and coherence in the codebase.
- */
-@Shared(scope = SIMULATION, ancestors = INTERFACES)
-public interface Promise<V> extends io.netty.util.concurrent.Promise<V>, Future<V>
-{
- public static <V> GenericFutureListener<? extends Future<V>> listener(FutureCallback<V> callback)
- {
- return future -> {
- if (future.isSuccess()) callback.onSuccess(future.getNow());
- else callback.onFailure(future.cause());
- };
- }
-
- public static <V> GenericFutureListener<? extends Future<V>> listener(ExecutorService executor, FutureCallback<V> callback)
- {
- return future -> executor.execute(() -> {
- if (future.isSuccess()) callback.onSuccess(future.getNow());
- else callback.onFailure(future.cause());
- });
- }
-
- @Override
- Promise<V> addCallback(FutureCallback<? super V> callback);
-
- Promise<V> addCallback(FutureCallback<? super V> callback, Executor executor);
-
- Promise<V> addCallback(Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure);
-
- @Override
- Promise<V> addListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> var1);
-
- @Override
- Promise<V> addListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... var1);
-
- @Override
- Promise<V> removeListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> var1);
-
- @Override
- Promise<V> removeListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... var1);
-
- /**
- * Complete the promise successfully if not already complete
- * @throws IllegalStateException if already set
- */
- @Override
- Promise<V> setSuccess(V v) throws IllegalStateException;
-
- /**
- * Complete the promise abnormally if not already complete
- * @throws IllegalStateException if already set
- */
- @Override
- Promise<V> setFailure(Throwable throwable) throws IllegalStateException;
-
- /**
- * Prevent a future caller from cancelling this promise
- * @return true iff this invocation set it to uncancellable, whether or not now uncancellable
- */
- boolean setUncancellableExclusive();
-
- /**
- * Wait indefinitely for this promise to complete, throwing any interrupt
- * @throws InterruptedException if interrupted
- */
- @Override
- Promise<V> await() throws InterruptedException;
-
- /**
- * Wait indefinitely for this promise to complete
- */
- @Override
- Promise<V> awaitUninterruptibly();
-
- /**
- * Wait indefinitely for this promise to complete, throwing any interrupt as an UncheckedInterruptedException
- * @throws UncheckedInterruptedException if interrupted
- */
- @Override
- Promise<V> awaitThrowUncheckedOnInterrupt();
-
- /**
- * waits for completion; in case of failure rethrows the original exception without a new wrapping exception
- * so may cause problems for reporting stack traces
- */
- @Override
- Promise<V> sync() throws InterruptedException;
-
- /**
- * waits for completion; in case of failure rethrows the original exception without a new wrapping exception
- * so may cause problems for reporting stack traces
- */
- @Override
- Promise<V> syncUninterruptibly();
-}
-
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/UncheckedInterruptedException.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/UncheckedInterruptedException.java
deleted file mode 100644
index d7248e8..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/UncheckedInterruptedException.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-import org.apache.cassandra.utils.Shared;
-
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * Unchecked {@link InterruptedException}, to be thrown in places where an interrupt is unexpected
- */
-@Shared(scope = SIMULATION)
-public class UncheckedInterruptedException extends RuntimeException
-{
- public UncheckedInterruptedException()
- {
- }
- public UncheckedInterruptedException(InterruptedException cause)
- {
- super(cause);
- }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/WaitQueue.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/WaitQueue.java
deleted file mode 100644
index d2a841d..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/WaitQueue.java
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.cassandra.utils.concurrent;
-
-import java.util.Iterator;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
-import java.util.concurrent.locks.LockSupport;
-import java.util.function.BooleanSupplier;
-import java.util.function.Consumer;
-
-import org.apache.cassandra.utils.Intercept;
-import org.apache.cassandra.utils.Shared;
-import org.apache.cassandra.utils.concurrent.Awaitable.AbstractAwaitable;
-
-import static org.apache.cassandra.utils.Clock.Global.nanoTime;
-import static org.apache.cassandra.utils.Shared.Recursive.INTERFACES;
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * <p>A relatively easy to use utility for general purpose thread signalling.</p>
- * <p>Usage on a thread awaiting a state change using a WaitQueue q is:</p>
- * <pre>
- * {@code
- * while (!conditionMet())
- * Signal s = q.register();
- * if (!conditionMet()) // or, perhaps more correctly, !conditionChanged()
- * s.await();
- * else
- * s.cancel();
- * }
- * </pre>
- * A signalling thread, AFTER changing the state, then calls q.signal() to wake up one, or q.signalAll()
- * to wake up all, waiting threads.
- * <p>To understand intuitively how this class works, the idea is simply that a thread, once it considers itself
- * incapable of making progress, registers to be awoken once that changes. Since this could have changed between
- * checking and registering (in which case the thread that made this change would have been unable to signal it),
- * it checks the condition again, sleeping only if it hasn't changed/still is not met.</p>
- * <p>This thread synchronisation scheme has some advantages over Condition objects and Object.wait/notify in that no monitor
- * acquisition is necessary and, in fact, besides the actual waiting on a signal, all operations are non-blocking.
- * As a result consumers can never block producers, nor each other, or vice versa, from making progress.
- * Threads that are signalled are also put into a RUNNABLE state almost simultaneously, so they can all immediately make
- * progress without having to serially acquire the monitor/lock, reducing scheduler delay incurred.</p>
- *
- * <p>A few notes on utilisation:</p>
- * <p>1. A thread will only exit await() when it has been signalled, but this does not guarantee the condition has not
- * been altered since it was signalled, and depending on your design it is likely the outer condition will need to be
- * checked in a loop, though this is not always the case.</p>
- * <p>2. Each signal is single use, so must be re-registered after each await(). This is true even if it times out.</p>
- * <p>3. If you choose not to wait on the signal (because the condition has been met before you waited on it)
- * you must cancel() the signal if the signalling thread uses signal() to awake waiters; otherwise signals will be
- * lost. If signalAll() is used but infrequent, and register() is frequent, cancel() should still be used to prevent the
- * queue growing unboundedly. Similarly, if you provide a TimerContext, cancel should be used to ensure it is not erroneously
- * counted towards wait time.</p>
- * <p>4. Care must be taken when selecting conditionMet() to ensure we are waiting on the condition that actually
- * indicates progress is possible. In some complex cases it may be tempting to wait on a condition that is only indicative
- * of local progress, not progress on the task we are aiming to complete, and a race may leave us waiting for a condition
- * to be met that we no longer need.
- * <p>5. This scheme is not fair</p>
- * <p>6. Only the thread that calls register() may call await()</p>
- *
- * TODO: this class should not be backed by CLQ (should use an intrusive linked-list with lower overhead)
- */
-@Shared(scope = SIMULATION, inner = INTERFACES)
-public interface WaitQueue
-{
- /**
- * A Signal is a one-time-use mechanism for a thread to wait for notification that some condition
- * state has transitioned that it may be interested in (and hence should check if it is).
- * It is potentially transient, i.e. the state can change in the meantime, it only indicates
- * that it should be checked, not necessarily anything about what the expected state should be.
- *
- * Signal implementations should never wake up spuriously, they are always woken up by a
- * signal() or signalAll().
- *
- * This abstract definition of Signal does not need to be tied to a WaitQueue.
- * Whilst RegisteredSignal is the main building block of Signals, this abstract
- * definition allows us to compose Signals in useful ways. The Signal is 'owned' by the
- * thread that registered itself with WaitQueue(s) to obtain the underlying RegisteredSignal(s);
- * only the owning thread should use a Signal.
- */
- public static interface Signal extends Condition
- {
- /**
- * @return true if cancelled; once cancelled, must be discarded by the owning thread.
- */
- public boolean isCancelled();
-
- /**
- * @return isSignalled() || isCancelled(). Once true, the state is fixed and the Signal should be discarded
- * by the owning thread.
- */
- public boolean isSet();
-
- /**
- * atomically: cancels the Signal if !isSet(), or returns true if isSignalled()
- *
- * @return true if isSignalled()
- */
- public boolean checkAndClear();
-
- /**
- * Should only be called by the owning thread. Indicates the signal can be retired,
- * and if signalled propagates the signal to another waiting thread
- */
- public abstract void cancel();
- }
-
- /**
- * The calling thread MUST be the thread that uses the signal
- */
- public Signal register();
-
- /**
- * The calling thread MUST be the thread that uses the signal.
- * If the Signal is waited on, context.stop() will be called when the wait times out, the Signal is signalled,
- * or the waiting thread is interrupted.
- */
- public <V> Signal register(V supplyOnDone, Consumer<V> receiveOnDone);
-
- /**
- * Signal one waiting thread
- */
- public boolean signal();
-
- /**
- * Signal all waiting threads
- */
- public void signalAll();
-
-// /** getWaiting() > 0 */
- public boolean hasWaiters();
-
- /** Return how many threads are waiting */
- public int getWaiting();
-
- /**
- * Factory method used to capture and redirect instantiations for simulation
- */
- @Intercept
- public static WaitQueue newWaitQueue()
- {
- return new Standard();
- }
-
- class Standard implements WaitQueue
- {
- private static final int CANCELLED = -1;
- private static final int SIGNALLED = 1;
- private static final int NOT_SET = 0;
-
- private static final AtomicIntegerFieldUpdater<RegisteredSignal> signalledUpdater = AtomicIntegerFieldUpdater.newUpdater(RegisteredSignal.class, "state");
-
- // the waiting signals
- private final ConcurrentLinkedQueue<RegisteredSignal> queue = new ConcurrentLinkedQueue<>();
-
- protected Standard() {}
-
- /**
- * The calling thread MUST be the thread that uses the signal
- * @return x
- */
- public Signal register()
- {
- RegisteredSignal signal = new RegisteredSignal();
- queue.add(signal);
- return signal;
- }
-
- /**
- * The calling thread MUST be the thread that uses the signal.
- * If the Signal is waited on, context.stop() will be called when the wait times out, the Signal is signalled,
- * or the waiting thread is interrupted.
- */
- public <V> Signal register(V supplyOnDone, Consumer<V> receiveOnDone)
- {
- RegisteredSignal signal = new SignalWithListener<>(supplyOnDone, receiveOnDone);
- queue.add(signal);
- return signal;
- }
-
- /**
- * Signal one waiting thread
- */
- public boolean signal()
- {
- while (true)
- {
- RegisteredSignal s = queue.poll();
- if (s == null || s.doSignal() != null)
- return s != null;
- }
- }
-
- /**
- * Signal all waiting threads
- */
- public void signalAll()
- {
- if (!hasWaiters())
- return;
-
- // to avoid a race where the condition is not met and the woken thread managed to wait on the queue before
- // we finish signalling it all, we pick a random thread we have woken-up and hold onto it, so that if we encounter
- // it again we know we're looping. We reselect a random thread periodically, progressively less often.
- // the "correct" solution to this problem is to use a queue that permits snapshot iteration, but this solution is sufficient
- // TODO: this is only necessary because we use CLQ - which is only for historical any-NIH reasons
- int i = 0, s = 5;
- Thread randomThread = null;
- Iterator<RegisteredSignal> iter = queue.iterator();
- while (iter.hasNext())
- {
- RegisteredSignal signal = iter.next();
- Thread signalled = signal.doSignal();
-
- if (signalled != null)
- {
- if (signalled == randomThread)
- break;
-
- if (++i == s)
- {
- randomThread = signalled;
- s <<= 1;
- }
- }
-
- iter.remove();
- }
- }
-
- private void cleanUpCancelled()
- {
- // TODO: attempt to remove the cancelled from the beginning only (need atomic cas of head)
- queue.removeIf(RegisteredSignal::isCancelled);
- }
-
- public boolean hasWaiters()
- {
- return !queue.isEmpty();
- }
-
- /**
- * @return how many threads are waiting
- */
- public int getWaiting()
- {
- if (!hasWaiters())
- return 0;
- Iterator<RegisteredSignal> iter = queue.iterator();
- int count = 0;
- while (iter.hasNext())
- {
- Signal next = iter.next();
- if (!next.isCancelled())
- count++;
- }
- return count;
- }
-
- /**
- * An abstract signal implementation
- *
- * TODO: use intrusive linked list
- */
- public static abstract class AbstractSignal extends AbstractAwaitable implements Signal
- {
- public Signal await() throws InterruptedException
- {
- while (!isSignalled())
- {
- checkInterrupted();
- LockSupport.park();
- }
- checkAndClear();
- return this;
- }
-
- public boolean awaitUntil(long nanoTimeDeadline) throws InterruptedException
- {
- long now;
- while (nanoTimeDeadline > (now = nanoTime()) && !isSignalled())
- {
- checkInterrupted();
- long delta = nanoTimeDeadline - now;
- LockSupport.parkNanos(delta);
- }
- return checkAndClear();
- }
-
- private void checkInterrupted() throws InterruptedException
- {
- if (Thread.interrupted())
- {
- cancel();
- throw new InterruptedException();
- }
- }
- }
-
- /**
- * A signal registered with this WaitQueue
- */
- private class RegisteredSignal extends AbstractSignal
- {
- private volatile Thread thread = Thread.currentThread();
- volatile int state;
-
- public boolean isSignalled()
- {
- return state == SIGNALLED;
- }
-
- public boolean isCancelled()
- {
- return state == CANCELLED;
- }
-
- public boolean isSet()
- {
- return state != NOT_SET;
- }
-
- private Thread doSignal()
- {
- if (!isSet() && signalledUpdater.compareAndSet(this, NOT_SET, SIGNALLED))
- {
- Thread thread = this.thread;
- LockSupport.unpark(thread);
- this.thread = null;
- return thread;
- }
- return null;
- }
-
- public void signal()
- {
- doSignal();
- }
-
- public boolean checkAndClear()
- {
- if (!isSet() && signalledUpdater.compareAndSet(this, NOT_SET, CANCELLED))
- {
- thread = null;
- cleanUpCancelled();
- return false;
- }
- // must now be signalled assuming correct API usage
- return true;
- }
-
- /**
- * Should only be called by the registered thread. Indicates the signal can be retired,
- * and if signalled propagates the signal to another waiting thread
- */
- public void cancel()
- {
- if (isCancelled())
- return;
- if (!signalledUpdater.compareAndSet(this, NOT_SET, CANCELLED))
- {
- // must already be signalled - switch to cancelled and
- state = CANCELLED;
- // propagate the signal
- Standard.this.signal();
- }
- thread = null;
- cleanUpCancelled();
- }
- }
-
- /**
- * A RegisteredSignal that stores a TimerContext, and stops the timer when either cancelled or
- * finished waiting. i.e. if the timer is started when the signal is registered it tracks the
- * time in between registering and invalidating the signal.
- */
- private final class SignalWithListener<V> extends RegisteredSignal
- {
- private final V supplyOnDone;
- private final Consumer<V> receiveOnDone;
-
- private SignalWithListener(V supplyOnDone, Consumer<V> receiveOnDone)
- {
- this.receiveOnDone = receiveOnDone;
- this.supplyOnDone = supplyOnDone;
- }
-
-
- @Override
- public boolean checkAndClear()
- {
- receiveOnDone.accept(supplyOnDone);
- return super.checkAndClear();
- }
-
- @Override
- public void cancel()
- {
- if (!isCancelled())
- {
- receiveOnDone.accept(supplyOnDone);
- super.cancel();
- }
- }
- }
- }
-
- /**
- * Loops waiting on the supplied condition and WaitQueue and will not return until the condition is true
- */
- public static void waitOnCondition(BooleanSupplier condition, WaitQueue queue) throws InterruptedException
- {
- while (!condition.getAsBoolean())
- {
- Signal s = queue.register();
- if (!condition.getAsBoolean()) s.await();
- else s.cancel();
- }
- }
-}
diff --git a/accord-core/src/test/java/accord/NetworkFilter.java b/accord-core/src/test/java/accord/NetworkFilter.java
index f0e4bd3..42711d7 100644
--- a/accord-core/src/test/java/accord/NetworkFilter.java
+++ b/accord-core/src/test/java/accord/NetworkFilter.java
@@ -73,7 +73,7 @@ public class NetworkFilter
public static Predicate<Message> notMessageType(Class<? extends Message> klass)
{
- return Predicate.not(isMessageType(klass));
+ return msg -> !klass.isAssignableFrom(msg.getClass());
}
/**
diff --git a/accord-core/src/test/java/accord/Utils.java b/accord-core/src/test/java/accord/Utils.java
index 50dcbdf..f9766e4 100644
--- a/accord-core/src/test/java/accord/Utils.java
+++ b/accord-core/src/test/java/accord/Utils.java
@@ -84,12 +84,12 @@ public class Utils
public static Txn writeTxn(Keys keys)
{
- return new Txn(keys, MockStore.read(keys), MockStore.QUERY, MockStore.update(keys));
+ return new Txn.InMemory(keys, MockStore.read(keys), MockStore.QUERY, MockStore.update(keys));
}
public static Txn readTxn(Keys keys)
{
- return new Txn(keys, MockStore.read(keys), MockStore.QUERY);
+ return new Txn.InMemory(keys, MockStore.read(keys), MockStore.QUERY);
}
public static Shard shard(KeyRange range, List<Node.Id> nodes, Set<Node.Id> fastPath)
diff --git a/accord-core/src/test/java/accord/burn/BurnTest.java b/accord-core/src/test/java/accord/burn/BurnTest.java
index 7be5724..59a6fd7 100644
--- a/accord-core/src/test/java/accord/burn/BurnTest.java
+++ b/accord-core/src/test/java/accord/burn/BurnTest.java
@@ -57,6 +57,8 @@ import accord.verify.StrictSerializabilityVerifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static accord.utils.Utils.toArray;
+
public class BurnTest
{
private static final Logger logger = LoggerFactory.getLogger(BurnTest.class);
@@ -93,7 +95,7 @@ public class BurnTest
requestKeys.addAll(update.keySet());
ListRead read = new ListRead(readKeys, new Keys(requestKeys));
ListQuery query = new ListQuery(client, count);
- ListRequest request = new ListRequest(new Txn(new Keys(requestKeys), read, query, update));
+ ListRequest request = new ListRequest(new Txn.InMemory(new Keys(requestKeys), read, query, update));
packets.add(new Packet(client, node, count, request));
}
@@ -162,7 +164,7 @@ public class BurnTest
StrictSerializabilityVerifier strictSerializable = new StrictSerializabilityVerifier(keyCount);
- Packet[] requests = generate(random, clients, nodes, keyCount, operations).toArray(Packet[]::new);
+ Packet[] requests = toArray(generate(random, clients, nodes, keyCount, operations), Packet[]::new);
int[] starts = new int[requests.length];
Packet[] replies = new Packet[requests.length];
@@ -232,7 +234,7 @@ public class BurnTest
try
{
- Cluster.run(nodes.toArray(Id[]::new), () -> queue,
+ Cluster.run(toArray(nodes, Id[]::new), () -> queue,
responseSink, failures::add,
() -> new Random(random.nextLong()),
() -> new AtomicLong()::incrementAndGet,
diff --git a/accord-core/src/test/java/accord/burn/TopologyUpdate.java b/accord-core/src/test/java/accord/burn/TopologyUpdate.java
index d17630e..16ab44d 100644
--- a/accord-core/src/test/java/accord/burn/TopologyUpdate.java
+++ b/accord-core/src/test/java/accord/burn/TopologyUpdate.java
@@ -21,6 +21,7 @@ package accord.burn;
import accord.api.Key;
import accord.api.Result;
import accord.api.TestableConfigurationService;
+import accord.impl.InMemoryCommandStore;
import accord.local.Command;
import accord.local.Node;
import accord.local.Status;
@@ -47,6 +48,8 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
+import static accord.impl.InMemoryCommandStores.inMemory;
+
public class TopologyUpdate
{
private static final Logger logger = LoggerFactory.getLogger(TopologyUpdate.class);
@@ -89,9 +92,9 @@ public class TopologyUpdate
return;
}
- Key progressKey = node.trySelectProgressKey(txnId, txn.keys, homeKey); // likely to be null, unless flip-flop of ownership
+ Key progressKey = node.trySelectProgressKey(txnId, txn.keys(), homeKey); // likely to be null, unless flip-flop of ownership
// TODO: can skip the homeKey if it's not a participating key in the transaction
- node.forEachLocalSince(txn.keys, epoch, commandStore -> {
+ inMemory(node).forEachLocalSince(txn.keys(), epoch, commandStore -> {
switch (status)
{
case AcceptedInvalidate:
@@ -159,9 +162,9 @@ public class TopologyUpdate
Map<TxnId, CommandSync> syncMessages = new ConcurrentHashMap<>();
Consumer<Command> commandConsumer = command -> syncMessages.put(command.txnId(), new CommandSync(command, epoch));
if (committedOnly)
- node.forEachLocal(commandStore -> commandStore.forCommittedInEpoch(ranges, epoch, commandConsumer));
+ inMemory(node).forEachLocal(commandStore -> InMemoryCommandStore.inMemory(commandStore).forCommittedInEpoch(ranges, epoch, commandConsumer));
else
- node.forEachLocal(commandStore -> commandStore.forEpochCommands(ranges, epoch, commandConsumer));
+ inMemory(node).forEachLocal(commandStore -> InMemoryCommandStore.inMemory(commandStore).forEpochCommands(ranges, epoch, commandConsumer));
return syncMessages.values().stream().map(cmd -> MessageTask.of(node, recipients.apply(cmd), "Sync:" + cmd.txnId + ':' + epoch + ':' + forEpoch, cmd::process));
}
diff --git a/accord-core/src/test/java/accord/coordinate/CoordinateTest.java b/accord-core/src/test/java/accord/coordinate/CoordinateTest.java
index b8db5d2..922c87a 100644
--- a/accord-core/src/test/java/accord/coordinate/CoordinateTest.java
... 1384 lines suppressed ...
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org