You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by be...@apache.org on 2025/01/17 21:46:37 UTC
(cassandra-accord) branch trunk updated: Protocol optimisations: - Privileged coordinator. If the coordinator is a replica we can reduce our quorum sizes by including the coordinator's vote. - with deps: if we include coordinator's preaccept deps we can reliably reduce quorum size by 1, at the expense of recovery sometimes requiring additional phases and waiting for future txns - with only vote: if we only include the vote we can avoid any additional recovery phases or waiting for future txns, but can reduce our quorum siz [...]
This is an automated email from the ASF dual-hosted git repository.
benedict 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 d5ac6b6a Protocol optimisations: - Privileged coordinator. If the coordinator is a replica we can reduce our quorum sizes by including the coordinator's vote. - with deps: if we include coordinator's preaccept deps we can reliably reduce quorum size by 1, at the expense of recovery sometimes requiring additional phases and waiting for future txns - with only vote: if we only include the vote we can avoid any additional recovery phases or waiting for future txns, but can re [...]
d5ac6b6a is described below
commit d5ac6b6ab3eb6194321d7ce6210a99adf73f10e2
Author: Benedict Elliott Smith <be...@apache.org>
AuthorDate: Mon Dec 30 00:04:44 2024 +0000
Protocol optimisations:
- Privileged coordinator. If the coordinator is a replica we can reduce our quorum sizes by including the coordinator's vote.
- with deps: if we include coordinator's preaccept deps we can reliably reduce quorum size by 1, at the expense of recovery sometimes requiring additional phases and waiting for future txns
- with only vote: if we only include the vote we can avoid any additional recovery phases or waiting for future txns, but can reduce our quorum size for only some configurations
- Medium path. If t=t0 at a simple majority we can take just two rounds.
- with additional phase: on recovery, earlier txns must wait for medium path to be disabled (or committed) so we do not accidentally recover a transaction that won't be witnessed
- with unstable deps: on slow path commit, deps not found in accept as committed as unstable and do not affect recovery decisions for earlier transactions
Also improve:
- Recovery of await conditions simply recalculates the rejects flag rather than restarting to ensure faster forward progress
- refactor Command hierarchy
- tweak: don't save Writes for non-write transactions
Also fix:
- isOutOfRange when invoked from callback for some txn < Committed
- handle loadingPruned that is pre-bootstrap on update to bootstrappedAt
- journal replay can overwrite in memory state loaded for a command not yet replayed
- subtle ordering bug in LatestDeps.mergeProposal
- fix occasional class initialisation order bug
- handle race condition on learning of topology
patch by Benedict; reviewed by Aleksey Yeschenko for CASSANDRA-20222
---
accord-core/src/main/java/accord/api/Agent.java | 2 +
.../src/main/java/accord/api/EventsListener.java | 8 +
.../src/main/java/accord/api/ProgressLog.java | 38 +-
.../main/java/accord/api/ProtocolModifiers.java | 29 +
.../coordinate/AbstractCoordinatePreAccept.java | 163 +--
.../java/accord/coordinate/AsynchronousAwait.java | 23 +-
.../src/main/java/accord/coordinate/Barrier.java | 2 +-
.../accord/coordinate/CollectCalculatedDeps.java | 11 +-
.../java/accord/coordinate/CollectLatestDeps.java | 30 +-
.../accord/coordinate/CoordinateEphemeralRead.java | 13 +-
.../coordinate/CoordinateGloballyDurable.java | 4 +-
.../accord/coordinate/CoordinatePreAccept.java | 48 +-
.../accord/coordinate/CoordinateSyncPoint.java | 22 +-
.../accord/coordinate/CoordinateTransaction.java | 230 +++-
.../accord/coordinate/CoordinationAdapter.java | 39 +-
.../accord/coordinate/ExecuteEphemeralRead.java | 3 -
.../main/java/accord/coordinate/ExecutePath.java | 2 +-
.../java/accord/coordinate/ExecuteSyncPoint.java | 18 +-
.../main/java/accord/coordinate/ExecuteTxn.java | 137 +-
.../src/main/java/accord/coordinate/FetchData.java | 15 +-
.../src/main/java/accord/coordinate/FindRoute.java | 2 +-
.../src/main/java/accord/coordinate/Infer.java | 15 +-
.../main/java/accord/coordinate/Invalidate.java | 46 +-
.../main/java/accord/coordinate/MaybeRecover.java | 9 +-
.../src/main/java/accord/coordinate/Persist.java | 9 +-
.../src/main/java/accord/coordinate/Propose.java | 122 +-
.../java/accord/coordinate/ProposeSyncPoint.java | 13 +-
.../main/java/accord/coordinate/ProposeTxn.java | 17 +-
.../java/accord/coordinate/ReadCoordinator.java | 30 +-
.../src/main/java/accord/coordinate/Recover.java | 160 ++-
.../java/accord/coordinate/RecoverWithRoute.java | 23 +-
.../src/main/java/accord/coordinate/Stabilise.java | 18 +-
.../java/accord/coordinate/SynchronousAwait.java | 99 --
.../accord/coordinate/SynchronousRecoverAwait.java | 100 ++
.../java/accord/coordinate/TopologyMismatch.java | 7 +-
.../coordinate/tracking/AbstractTracker.java | 2 +-
.../coordinate/tracking/FastPathTracker.java | 78 +-
.../coordinate/tracking/InvalidationTracker.java | 39 +-
.../PreAcceptExclusiveSyncPointTracker.java} | 24 +-
.../coordinate/tracking/PreAcceptTracker.java | 1 +
.../coordinate/tracking/QuorumIdTracker.java | 4 +-
.../accord/coordinate/tracking/QuorumTracker.java | 25 +-
.../accord/coordinate/tracking/ReadTracker.java | 37 +-
.../coordinate/tracking/RecoveryTracker.java | 27 +-
.../accord/coordinate/tracking/SimpleTracker.java | 1 +
.../java/accord/impl/AbstractFetchCoordinator.java | 26 +-
.../src/main/java/accord/impl/CommandChange.java | 86 +-
.../java/accord/impl/DurabilityScheduling.java | 2 +
.../java/accord/impl/InMemoryCommandStore.java | 23 +-
.../main/java/accord/impl/RequestCallbacks.java | 8 +-
.../impl/progresslog/DefaultProgressLog.java | 11 +-
.../java/accord/impl/progresslog/TxnState.java | 5 +-
.../java/accord/impl/progresslog/WaitingState.java | 69 +-
.../src/main/java/accord/local/Command.java | 1380 +++++++++-----------
.../src/main/java/accord/local/CommandStore.java | 11 +-
.../src/main/java/accord/local/CommandStores.java | 5 +-
.../main/java/accord/local/CommandSummaries.java | 33 +-
.../src/main/java/accord/local/Commands.java | 347 +++--
.../main/java/accord/local/CommonAttributes.java | 134 --
.../src/main/java/accord/local/ICommand.java | 242 ++++
.../src/main/java/accord/local/KeyHistory.java | 4 +-
accord-core/src/main/java/accord/local/Node.java | 69 +-
.../java/accord/local/NodeCommandStoreService.java | 2 +
.../main/java/accord/local/RedundantBefore.java | 13 +-
.../src/main/java/accord/local/SafeCommand.java | 50 +-
.../main/java/accord/local/SafeCommandStore.java | 5 +-
.../main/java/accord/local/StoreParticipants.java | 34 +-
.../main/java/accord/local/cfk/CommandsForKey.java | 132 +-
.../src/main/java/accord/local/cfk/NotifySink.java | 13 +-
.../main/java/accord/local/cfk/PostProcess.java | 8 +-
.../src/main/java/accord/local/cfk/Pruning.java | 29 +-
.../java/accord/local/cfk/SafeCommandsForKey.java | 2 +-
.../src/main/java/accord/local/cfk/Serialize.java | 10 +-
.../src/main/java/accord/local/cfk/Updating.java | 39 +-
.../src/main/java/accord/local/cfk/Utils.java | 22 +-
.../src/main/java/accord/messages/Accept.java | 87 +-
.../src/main/java/accord/messages/Apply.java | 2 +-
.../src/main/java/accord/messages/Await.java | 56 +-
.../java/accord/messages/BeginInvalidation.java | 4 +-
.../main/java/accord/messages/BeginRecovery.java | 142 +-
.../src/main/java/accord/messages/Callback.java | 3 +-
.../src/main/java/accord/messages/CheckStatus.java | 16 +-
.../src/main/java/accord/messages/Commit.java | 201 +--
.../main/java/accord/messages/GetLatestDeps.java | 18 +-
.../src/main/java/accord/messages/MessageType.java | 5 +-
.../src/main/java/accord/messages/PreAccept.java | 61 +-
.../src/main/java/accord/messages/Propagate.java | 14 +-
.../src/main/java/accord/messages/ReadData.java | 70 +-
.../main/java/accord/messages/RecoverAwait.java | 77 ++
.../main/java/accord/messages/SafeCallback.java | 5 +-
.../main/java/accord/messages/StableThenRead.java | 127 ++
.../src/main/java/accord/messages/TxnRequest.java | 6 +-
.../java/accord/primitives/AbstractRanges.java | 3 +-
.../src/main/java/accord/primitives/Deps.java | 83 +-
.../src/main/java/accord/primitives/KeyDeps.java | 42 +-
.../src/main/java/accord/primitives/Known.java | 439 +++++--
.../src/main/java/accord/primitives/KnownMap.java | 2 +-
.../main/java/accord/primitives/LatestDeps.java | 96 +-
.../main/java/accord/primitives/PartialDeps.java | 6 +-
.../java/accord/primitives/PartialKeyRoute.java | 3 +-
.../java/accord/primitives/PartialRangeRoute.java | 3 +-
.../main/java/accord/primitives/ProgressToken.java | 15 +-
.../src/main/java/accord/primitives/RangeDeps.java | 45 +-
.../main/java/accord/primitives/SaveStatus.java | 200 ++-
.../src/main/java/accord/primitives/Status.java | 92 +-
.../src/main/java/accord/primitives/Timestamp.java | 91 +-
.../src/main/java/accord/primitives/Txn.java | 82 +-
.../src/main/java/accord/primitives/TxnId.java | 169 ++-
.../src/main/java/accord/topology/Shard.java | 185 ++-
.../src/main/java/accord/topology/Topologies.java | 28 +-
.../src/main/java/accord/topology/Topology.java | 15 +-
.../main/java/accord/topology/TopologyManager.java | 330 ++++-
.../src/main/java/accord/utils/Functions.java | 19 +
.../src/main/java/accord/utils/Invariants.java | 5 +
.../utils/MergeFewDisjointSortedListsCursor.java | 43 +-
.../main/java/accord/utils/RelationMultiMap.java | 65 +
.../src/main/java/accord/utils/SortedList.java | 12 +
.../src/main/java/accord/utils/SortedListMap.java | 11 +
.../src/main/java/accord/utils/TinyEnumSet.java | 90 ++
.../ExecutePath.java => utils/UnhandledEnum.java} | 11 +-
accord-core/src/test/java/accord/Utils.java | 2 +-
.../src/test/java/accord/burn/BurnTestBase.java | 108 +-
.../accord/burn/BurnTestConfigurationService.java | 4 +-
.../accord/coordinate/CoordinateSyncPointTest.java | 2 +-
.../coordinate/CoordinateTransactionTest.java | 12 +-
.../accord/coordinate/PreAcceptTrackerTest.java | 11 +-
.../tracking/FastPathTrackerReconciler.java | 3 +-
.../tracking/InvalidationTrackerReconciler.java | 3 +-
.../impl/AbstractConfigurationServiceTest.java | 2 +-
.../test/java/accord/impl/LocalListenersTest.java | 79 +-
.../test/java/accord/impl/RemoteListenersTest.java | 13 +-
.../src/test/java/accord/impl/TestAgent.java | 7 +
.../src/test/java/accord/impl/basic/Cluster.java | 34 +-
.../accord/impl/basic/DelayedCommandStores.java | 45 +-
.../java/accord/impl/basic/InMemoryJournal.java | 40 +-
.../accord/impl/basic/TaskExecutorService.java | 13 +-
.../src/test/java/accord/impl/list/ListAgent.java | 14 +-
.../accord/impl/list/ListFetchCoordinator.java | 2 +-
.../test/java/accord/impl/list/ListRequest.java | 15 +-
.../test/java/accord/impl/mock/MockCluster.java | 2 +-
.../test/java/accord/local/CheckedCommands.java | 7 +-
.../java/accord/local/ImmutableCommandTest.java | 4 +-
.../java/accord/local/cfk/CommandsForKeyTest.java | 122 +-
.../test/java/accord/messages/PreAcceptTest.java | 6 +-
.../test/java/accord/messages/ReadDataTest.java | 6 +-
.../test/java/accord/primitives/KeyDepsTest.java | 12 +-
.../test/java/accord/primitives/RangeDepsTest.java | 2 +-
.../src/test/java/accord/topology/ShardTest.java | 75 +-
.../java/accord/topology/TopologyManagerTest.java | 4 +-
.../java/accord/topology/TopologyRandomizer.java | 18 +-
.../test/java/accord/topology/TopologyUtils.java | 2 +-
.../src/test/java/accord/utils/AccordGens.java | 4 +-
.../src/test/java/accord/utils/MessageTask.java | 4 +-
.../main/java/accord/maelstrom/MaelstromAgent.java | 7 +
.../java/accord/maelstrom/TopologyFactory.java | 2 +-
155 files changed, 5343 insertions(+), 2981 deletions(-)
diff --git a/accord-core/src/main/java/accord/api/Agent.java b/accord-core/src/main/java/accord/api/Agent.java
index 3d44358c..d02b71d8 100644
--- a/accord-core/src/main/java/accord/api/Agent.java
+++ b/accord-core/src/main/java/accord/api/Agent.java
@@ -27,6 +27,7 @@ import accord.local.SafeCommandStore;
import accord.messages.ReplyContext;
import accord.primitives.Ranges;
import accord.primitives.Routable;
+import accord.primitives.Status.Phase;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
@@ -149,6 +150,7 @@ public interface Agent extends UncaughtExceptionListener
*/
long retryAwaitTimeout(Node node, SafeCommandStore safeStore, TxnId txnId, int retryCount, BlockedUntil retrying, TimeUnit units);
+ long localExpiresAt(TxnId txnId, Phase phase, TimeUnit unit);
long expiresAt(ReplyContext replyContext, TimeUnit unit);
default void onViolation(String message) { throw illegalState(message); }
diff --git a/accord-core/src/main/java/accord/api/EventsListener.java b/accord-core/src/main/java/accord/api/EventsListener.java
index dfbad8a8..cfde3480 100644
--- a/accord-core/src/main/java/accord/api/EventsListener.java
+++ b/accord-core/src/main/java/accord/api/EventsListener.java
@@ -45,6 +45,10 @@ public interface EventsListener
{
}
+ default void onMediumPathTaken(TxnId txnId, Deps deps)
+ {
+ }
+
default void onSlowPathTaken(TxnId txnId, Deps deps)
{
}
@@ -65,6 +69,10 @@ public interface EventsListener
{
}
+ default void onRejected(TxnId txnId)
+ {
+ }
+
default void onProgressLogSizeChange(TxnId txnId, int delta)
{
}
diff --git a/accord-core/src/main/java/accord/api/ProgressLog.java b/accord-core/src/main/java/accord/api/ProgressLog.java
index bc8cdc12..66c5f7d9 100644
--- a/accord-core/src/main/java/accord/api/ProgressLog.java
+++ b/accord-core/src/main/java/accord/api/ProgressLog.java
@@ -18,6 +18,8 @@
package accord.api;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
import accord.local.Command;
@@ -38,6 +40,7 @@ import static accord.api.ProgressLog.BlockedUntil.Query.HOME;
import static accord.api.ProgressLog.BlockedUntil.Query.SHARD;
import static accord.primitives.Status.Durability.Majority;
import static accord.primitives.Status.Durability.NotDurable;
+import static accord.primitives.Status.NotAccepted;
import static accord.utils.SortedArrays.Search.FAST;
/**
@@ -81,6 +84,16 @@ public interface ProgressLog
*/
HasDecidedExecuteAt(HOME, HOME, SaveStatus.PreCommitted, NotDurable),
+ /**
+ * Wait for the transaction to be no longer pre-accepted.
+ *
+ * This is used for transactions that may have used the coordinator optimisation
+ *
+ * This BlockedUntil is useful for remote listeners performing recovery that are waiting for transactions in
+ * the Accept phase that need to reach Committed to advance the recovery machine.
+ */
+ CommittedOrNotAccepted(SHARD, SHARD, SaveStatus.Committed, NotDurable, s -> s.status == NotAccepted),
+
/**
* Wait for the transaction to be Committed.
*
@@ -111,18 +124,27 @@ public interface ProgressLog
public enum Query { HOME, SHARD }
- private static final BlockedUntil[] lookup = values();
+ private static final BlockedUntil[] lookupByOrdinal = values();
+ // avoid clashes when searching by on unblockedFrom by filtering those that may unblock by the predicate (introduced alongside NotAcceptedOrCommitted)
+ private static final BlockedUntil[] searchByUnblockedFrom = Stream.of(values()).filter(b -> b.additionallyUnblockedBy == null).toArray(BlockedUntil[]::new);
public final Query waitsOn, fetchFrom;
- public final SaveStatus minSaveStatus;
+ public final SaveStatus unblockedFrom;
+ public final @Nullable Predicate<SaveStatus> additionallyUnblockedBy;
// have remote listeners wait for the Durability before responding
// this permits us to wait for only the home shard for CanApply
public final Durability remoteDurability;
- BlockedUntil(Query waitsOn, Query fetchFrom, SaveStatus minSaveStatus, Durability remoteDurability)
+ BlockedUntil(Query waitsOn, Query fetchFrom, SaveStatus unblockedFrom, Durability remoteDurability)
+ {
+ this(waitsOn, fetchFrom, unblockedFrom, remoteDurability, null);
+ }
+
+ BlockedUntil(Query waitsOn, Query fetchFrom, SaveStatus unblockedFrom, Durability remoteDurability, @Nullable Predicate<SaveStatus> additionallyUnblockedBy)
{
this.waitsOn = waitsOn;
this.fetchFrom = fetchFrom;
- this.minSaveStatus = minSaveStatus;
+ this.unblockedFrom = unblockedFrom;
+ this.additionallyUnblockedBy = additionallyUnblockedBy;
this.remoteDurability = remoteDurability;
}
@@ -136,16 +158,16 @@ public interface ProgressLog
public static BlockedUntil forOrdinal(int ordinal)
{
- if (ordinal < 0 || ordinal > lookup.length)
+ if (ordinal < 0 || ordinal > lookupByOrdinal.length)
throw new IndexOutOfBoundsException(ordinal);
- return lookup[ordinal];
+ return lookupByOrdinal[ordinal];
}
public static BlockedUntil forSaveStatus(SaveStatus saveStatus)
{
- int i = SortedArrays.binarySearch(lookup, 0, lookup.length, saveStatus, (s, w) -> s.compareTo(w.minSaveStatus), FAST);
+ int i = SortedArrays.binarySearch(searchByUnblockedFrom, 0, searchByUnblockedFrom.length, saveStatus, (s, w) -> s.compareTo(w.unblockedFrom), FAST);
if (i < 0) i = Math.max(0, -2 - i);
- return lookup[i];
+ return searchByUnblockedFrom[i];
}
}
diff --git a/accord-core/src/main/java/accord/api/ProtocolModifiers.java b/accord-core/src/main/java/accord/api/ProtocolModifiers.java
index fc2b835c..f6f7991e 100644
--- a/accord-core/src/main/java/accord/api/ProtocolModifiers.java
+++ b/accord-core/src/main/java/accord/api/ProtocolModifiers.java
@@ -21,6 +21,10 @@ package accord.api;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import accord.primitives.TxnId;
+import accord.primitives.TxnId.FastPath;
+import accord.primitives.TxnId.FastPaths;
+import accord.primitives.TxnId.MediumPath;
import accord.utils.Invariants;
import static accord.api.ProtocolModifiers.QuorumEpochIntersections.ChaseFixedPoint.Chase;
@@ -69,6 +73,7 @@ public class ProtocolModifiers
Spec(ChaseAndInclude preaccept, Include accept, Include commit, Include stable, Include recover)
{
this.preaccept = preaccept;
+ Invariants.checkState(preaccept.chase == DoNotChase, "PreAccept chasing epoch is not implemented as not needed for current formulation and it complicated some aspects. An implementation can be found in git history.");
this.accept = accept;
this.commit = commit;
this.stable = stable;
@@ -142,6 +147,7 @@ public class ProtocolModifiers
{
static class Spec
{
+ // note that {txn,syncPoint}DiscardPreAcceptDeps faults expect filterDuplicateDependenciesFromAcceptReply to be off
final boolean txnInstability, txnDiscardPreAcceptDeps;
final boolean syncPointInstability, syncPointDiscardPreAcceptDeps;
@@ -166,10 +172,33 @@ public class ProtocolModifiers
public static final boolean txnInstability, txnDiscardPreAcceptDeps;
public static final boolean syncPointInstability, syncPointDiscardPreAcceptDeps;
+
+ public static boolean discardPreAcceptDeps(TxnId txnId)
+ {
+ return (txnDiscardPreAcceptDeps | syncPointDiscardPreAcceptDeps)
+ && (txnId.isSyncPoint() ? syncPointDiscardPreAcceptDeps : txnDiscardPreAcceptDeps);
+ }
}
public static class Toggles
{
+ private static FastPaths permittedFastPaths = new FastPaths(FastPath.values());
+ public static boolean usePrivilegedCoordinator() { return permittedFastPaths.hasPrivilegedCoordinator(); }
+ public static void setPermittedFastPaths(FastPaths newPermittedFastPaths) { permittedFastPaths = newPermittedFastPaths; }
+ public static FastPath ensurePermitted(FastPath path) { return path.toPermitted(permittedFastPaths); }
+
+ private static MediumPath defaultMediumPath = MediumPath.MEDIUM_PATH_WAIT_ON_RECOVERY;
+ public static MediumPath defaultMediumPath() { return defaultMediumPath; }
+ public static void setDefaultMediumPath(MediumPath newDefaultMediumPath) { defaultMediumPath = newDefaultMediumPath; }
+
+ private static boolean filterDuplicateDependenciesFromAcceptReply = true;
+ public static boolean filterDuplicateDependenciesFromAcceptReply() { return filterDuplicateDependenciesFromAcceptReply; }
+ public static void setFilterDuplicateDependenciesFromAcceptReply(boolean newFilterDuplicateDependenciesFromAcceptReply) { filterDuplicateDependenciesFromAcceptReply = newFilterDuplicateDependenciesFromAcceptReply; }
+
+ private static boolean permitLocalExecution = true;
+ public static boolean permitLocalExecution() { return permitLocalExecution; }
+ public static void setPermitLocalExecution(boolean newPermitLocalExecution) { permitLocalExecution = newPermitLocalExecution; }
+
public enum DependencyElision { OFF, ON, IF_DURABLE }
private static DependencyElision dependencyElision = IF_DURABLE;
public static DependencyElision dependencyElision() { return dependencyElision; }
diff --git a/accord-core/src/main/java/accord/coordinate/AbstractCoordinatePreAccept.java b/accord-core/src/main/java/accord/coordinate/AbstractCoordinatePreAccept.java
index f95d7354..49966e65 100644
--- a/accord-core/src/main/java/accord/coordinate/AbstractCoordinatePreAccept.java
+++ b/accord-core/src/main/java/accord/coordinate/AbstractCoordinatePreAccept.java
@@ -19,27 +19,20 @@
package accord.coordinate;
import java.util.Collection;
-import java.util.Map;
-import java.util.TreeMap;
import java.util.function.BiConsumer;
-import javax.annotation.Nullable;
+import javax.annotation.Nonnull;
-import accord.coordinate.tracking.QuorumTracker;
import accord.local.Node;
import accord.local.Node.Id;
import accord.messages.Callback;
import accord.primitives.FullRoute;
-import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.topology.Topologies;
import accord.utils.Invariants;
import accord.utils.WrappableException;
import accord.utils.async.AsyncResults.SettableResult;
-import static accord.api.ProtocolModifiers.QuorumEpochIntersections.ChaseFixedPoint.DoNotChase;
import static accord.api.ProtocolModifiers.QuorumEpochIntersections;
-import static accord.coordinate.tracking.RequestStatus.Failed;
-import static accord.coordinate.tracking.RequestStatus.Success;
/**
* Abstract parent class for implementing preaccept-like operations where we may need to fetch additional replies
@@ -47,71 +40,19 @@ import static accord.coordinate.tracking.RequestStatus.Success;
*/
abstract class AbstractCoordinatePreAccept<T, R> extends SettableResult<T> implements Callback<R>, BiConsumer<T, Throwable>
{
- class ExtraEpochs implements Callback<R>
- {
- final QuorumTracker tracker;
- private boolean extraRoundIsDone;
-
- ExtraEpochs(long fromEpoch, long toEpoch)
- {
- Topologies topologies = node.topology().preciseEpochs(route, fromEpoch, toEpoch);
- this.tracker = new QuorumTracker(topologies);
- }
-
- void start()
- {
- // TODO (desired, efficiency): consider sending only to electorate of most recent topology (as only these PreAccept votes matter)
- // note that we must send to all replicas of old topology, as electorate may not be reachable
- contact(tracker.topologies().nodes(), topologies, this);
- }
-
- @Override
- public void onFailure(Id from, Throwable failure)
- {
- synchronized (AbstractCoordinatePreAccept.this)
- {
- if (!extraRoundIsDone && tracker.recordFailure(from) == Failed)
- setFailure(failure);
- }
- }
-
- @Override
- public void onCallbackFailure(Id from, Throwable failure)
- {
- AbstractCoordinatePreAccept.this.onCallbackFailure(from, failure);
- }
-
- @Override
- public void onSuccess(Id from, R reply)
- {
- synchronized (AbstractCoordinatePreAccept.this)
- {
- if (!extraRoundIsDone)
- {
- if (!onExtraSuccessInternal(from, reply))
- setFailure(new Preempted(txnId, route.homeKey()));
- else if (tracker.recordSuccess(from) == Success)
- onPreAcceptedOrNewEpoch();
- }
- }
- }
- }
-
final Node node;
- @Nullable final TxnId txnId;
+ final TxnId txnId;
final FullRoute<?> route;
- private Topologies topologies;
- private boolean initialRoundIsDone;
- private ExtraEpochs extraEpochs;
- private final Map<Id, Object> debug = Invariants.debug() ? new TreeMap<>() : null;
+ final Topologies topologies;
+ private boolean isDone;
- AbstractCoordinatePreAccept(Node node, FullRoute<?> route, TxnId txnId)
+ AbstractCoordinatePreAccept(Node node, FullRoute<?> route, @Nonnull TxnId txnId)
{
this(node, route, txnId, node.topology().select(route, txnId, txnId, QuorumEpochIntersections.preaccept.include));
}
- AbstractCoordinatePreAccept(Node node, FullRoute<?> route, @Nullable TxnId txnId, Topologies topologies)
+ AbstractCoordinatePreAccept(Node node, FullRoute<?> route, @Nonnull TxnId txnId, Topologies topologies)
{
this.node = node;
this.txnId = txnId;
@@ -119,7 +60,7 @@ abstract class AbstractCoordinatePreAccept<T, R> extends SettableResult<T> imple
this.topologies = topologies;
}
- final void start()
+ void start()
{
contact(topologies.nodes(), topologies, this);
}
@@ -127,11 +68,6 @@ abstract class AbstractCoordinatePreAccept<T, R> extends SettableResult<T> imple
abstract void contact(Collection<Id> nodes, Topologies topologies, Callback<R> callback);
abstract void onSuccessInternal(Id from, R reply);
void onSlowResponseInternal(Id from) {}
- /**
- * The tracker for the extra rounds only is provided by the AbstractCoordinatePreAccept, so we expect a boolean back
- * indicating if the "success" reply was actually a good response or a failure (i.e. preempted)
- */
- abstract boolean onExtraSuccessInternal(Id from, R reply);
abstract void onFailureInternal(Id from, Throwable failure);
abstract void onNewEpochTopologyMismatch(TopologyMismatch mismatch);
abstract void onPreAccepted(Topologies topologies);
@@ -140,34 +76,30 @@ abstract class AbstractCoordinatePreAccept<T, R> extends SettableResult<T> imple
@Override
public synchronized final void onFailure(Id from, Throwable failure)
{
- if (debug != null) debug.putIfAbsent(from, failure);
- if (!initialRoundIsDone)
+ if (!isDone)
onFailureInternal(from, failure);
}
@Override
- public final synchronized void onCallbackFailure(Id from, Throwable failure)
+ public final synchronized boolean onCallbackFailure(Id from, Throwable failure)
{
- initialRoundIsDone = true;
- if (extraEpochs != null)
- extraEpochs.extraRoundIsDone = true;
-
- tryFailure(failure);
+ if (isDone) return false;
+ isDone = true;
+ return tryFailure(failure);
}
@Override
// TODO (expected): shouldn't need synchronized
public final synchronized void onSuccess(Id from, R reply)
{
- if (debug != null) debug.putIfAbsent(from, reply);
- if (!initialRoundIsDone)
+ if (!isDone)
onSuccessInternal(from, reply);
}
@Override
public final synchronized void onSlowResponse(Id from)
{
- if (!initialRoundIsDone)
+ if (!isDone)
onSlowResponseInternal(from);
}
@@ -183,10 +115,7 @@ abstract class AbstractCoordinatePreAccept<T, R> extends SettableResult<T> imple
private void onFailure(Throwable failure)
{
// we may already be complete, as we may receive a failure from a later phase; but it's fine to redundantly mark done
- initialRoundIsDone = true;
- if (extraEpochs != null)
- extraEpochs.extraRoundIsDone = true;
-
+ isDone = true;
if (failure instanceof CoordinationFailed)
{
((CoordinationFailed) failure).set(txnId, route.homeKey());
@@ -197,32 +126,15 @@ abstract class AbstractCoordinatePreAccept<T, R> extends SettableResult<T> imple
else if (failure instanceof Invalidated)
node.agent().metricsEventsListener().onInvalidated(txnId);
}
-
}
final void onPreAcceptedOrNewEpoch()
{
- Invariants.checkState(!initialRoundIsDone || (extraEpochs != null && !extraEpochs.extraRoundIsDone));
- initialRoundIsDone = true;
- if (QuorumEpochIntersections.preaccept.chase == DoNotChase)
- {
- long latestEpoch = executeAtEpoch();
- if (latestEpoch > topologies.currentEpoch()) node.withEpoch(latestEpoch, this, () -> onPreAcceptedInNewEpoch(topologies, latestEpoch));
- else onPreAccepted(topologies);
- return;
- }
-
- if (extraEpochs != null)
- extraEpochs.extraRoundIsDone = true;
-
- // if the epoch we are accepting in is later, we *must* contact the later epoch for pre-accept, as this epoch
- // could have moved ahead, and the timestamp we may propose may be stale.
- // Note that these future epochs are non-voting, they only serve to inform the timestamp we decide
+ Invariants.checkState(!isDone);
+ isDone = true;
long latestEpoch = executeAtEpoch();
- if (latestEpoch <= topologies.currentEpoch())
- onPreAccepted(topologies);
- else
- onNewEpoch(topologies, latestEpoch);
+ if (latestEpoch > topologies.currentEpoch()) node.withEpoch(latestEpoch, this, () -> onPreAcceptedInNewEpoch(topologies, latestEpoch));
+ else onPreAccepted(topologies);
}
final void onPreAcceptedInNewEpoch(Topologies topologies, long latestEpoch)
@@ -232,43 +144,6 @@ abstract class AbstractCoordinatePreAccept<T, R> extends SettableResult<T> imple
else onNewEpochTopologyMismatch(mismatch);
}
- final void onNewEpoch(Topologies prevTopologies, long latestEpoch)
- {
- // TODO (desired, efficiency): check if we have already have a valid quorum for the future epoch
- // (noting that nodes may have adopted new ranges, in which case they should be discounted, and quorums may have changed shape)
- node.withEpoch(latestEpoch, this, () -> {
- // TODO (required): this isn't synchronized, and isn't submitted back to the calling executor.
- TopologyMismatch mismatch = txnId != null && txnId.kind() == Txn.Kind.ExclusiveSyncPoint
- ? TopologyMismatch.checkForMismatch(node.topology().globalForEpoch(latestEpoch), txnId, route.homeKey(), route)
- : TopologyMismatch.checkForMismatchOrPendingRemoval(node.topology().globalForEpoch(latestEpoch), txnId, route.homeKey(), route);
- if (mismatch != null)
- {
- initialRoundIsDone = true;
- onNewEpochTopologyMismatch(mismatch);
- return;
- }
- topologies = node.topology().withUnsyncedEpochs(route, earliestEpoch(), latestEpoch);
- boolean equivalent = topologies.oldestEpoch() <= prevTopologies.currentEpoch();
- for (long epoch = topologies.currentEpoch() ; equivalent && epoch > prevTopologies.currentEpoch() ; --epoch)
- equivalent = topologies.getEpoch(epoch).shards().equals(prevTopologies.current().shards());
-
- if (equivalent)
- {
- onPreAccepted(topologies);
- }
- else
- {
- extraEpochs = new ExtraEpochs(prevTopologies.currentEpoch() + 1, latestEpoch);
- extraEpochs.start();
- }
- });
- }
-
- protected long earliestEpoch()
- {
- return txnId == null ? executeAtEpoch() : txnId.epoch();
- }
-
@Override
public final void accept(T success, Throwable failure)
{
diff --git a/accord-core/src/main/java/accord/coordinate/AsynchronousAwait.java b/accord-core/src/main/java/accord/coordinate/AsynchronousAwait.java
index f48590e7..ee21a60d 100644
--- a/accord-core/src/main/java/accord/coordinate/AsynchronousAwait.java
+++ b/accord-core/src/main/java/accord/coordinate/AsynchronousAwait.java
@@ -36,6 +36,7 @@ import accord.primitives.TxnId;
import accord.primitives.Unseekables;
import accord.topology.Topologies;
import accord.utils.Invariants;
+import accord.utils.UnhandledEnum;
import static accord.coordinate.tracking.RequestStatus.Success;
@@ -72,35 +73,42 @@ public class AsynchronousAwait implements Callback<AwaitOk>
final Participants<?> contact;
final AwaitTracker tracker;
final int asynchronousCallbackId;
+ final boolean notifyProgressLog;
final BiConsumer<SynchronousResult, Throwable> synchronousCallback;
private boolean isDone;
private Throwable failure;
- public AsynchronousAwait(TxnId txnId, Participants<?> contact, AwaitTracker tracker, int asynchronousCallbackId, BiConsumer<SynchronousResult, Throwable> synchronousCallback)
+ public AsynchronousAwait(TxnId txnId, Participants<?> contact, AwaitTracker tracker, boolean notifyProgressLog, int asynchronousCallbackId, BiConsumer<SynchronousResult, Throwable> synchronousCallback)
{
this.txnId = txnId;
this.contact = contact;
this.tracker = tracker;
this.asynchronousCallbackId = asynchronousCallbackId;
+ this.notifyProgressLog = notifyProgressLog;
this.synchronousCallback = synchronousCallback;
}
+ public static AsynchronousAwait awaitAny(Node node, Topologies topologies, TxnId txnId, Route<?> contact, BlockedUntil awaiting, int asynchronousCallbackId, BiConsumer<SynchronousResult, Throwable> synchronousCallback)
+ {
+ return awaitAny(node, topologies, txnId, contact, awaiting, true, asynchronousCallbackId, synchronousCallback);
+ }
+
/**
* we require a Route to contact so we can be sure a home shard recipient invokes {@link Commands#supplementParticipants},
* notifying the progress log of a Route to determine it is the home shard.
*/
- public static AsynchronousAwait awaitAny(Node node, Topologies topologies, TxnId txnId, Route<?> contact, BlockedUntil awaiting, int asynchronousCallbackId, BiConsumer<SynchronousResult, Throwable> synchronousCallback)
+ public static AsynchronousAwait awaitAny(Node node, Topologies topologies, TxnId txnId, Route<?> contact, BlockedUntil awaiting, boolean notifyProgressLog, int asynchronousCallbackId, BiConsumer<SynchronousResult, Throwable> synchronousCallback)
{
Invariants.checkArgument(topologies.size() == 1);
AwaitTracker tracker = new AwaitTracker(topologies);
- AsynchronousAwait result = new AsynchronousAwait(txnId, contact, tracker, asynchronousCallbackId, synchronousCallback);
+ AsynchronousAwait result = new AsynchronousAwait(txnId, contact, tracker, notifyProgressLog, asynchronousCallbackId, synchronousCallback);
result.start(node, topologies, contact, awaiting);
return result;
}
private void start(Node node, Topologies topologies, Route<?> route, BlockedUntil blockedUntil)
{
- node.send(topologies.nodes(), to -> new Await(to, topologies, txnId, route, blockedUntil, asynchronousCallbackId), this);
+ node.send(topologies.nodes(), to -> new Await(to, topologies, txnId, route, blockedUntil, asynchronousCallbackId, notifyProgressLog), this);
}
@Override
@@ -121,7 +129,7 @@ public class AsynchronousAwait implements Callback<AwaitOk>
RequestStatus status = tracker.recordFailure(from);
switch (status)
{
- default: throw new AssertionError("Unhandled RequestStatus: " + status);
+ default: throw new UnhandledEnum(status);
case NoChange: break;
case Success:
onSuccess();
@@ -143,12 +151,13 @@ public class AsynchronousAwait implements Callback<AwaitOk>
}
@Override
- public synchronized void onCallbackFailure(Id from, Throwable failure)
+ public synchronized boolean onCallbackFailure(Id from, Throwable failure)
{
- if (isDone) return;
+ if (isDone) return false;
isDone = true;
synchronousCallback.accept(null, failure);
+ return true;
}
}
diff --git a/accord-core/src/main/java/accord/coordinate/Barrier.java b/accord-core/src/main/java/accord/coordinate/Barrier.java
index 7a2bd038..9afcc383 100644
--- a/accord-core/src/main/java/accord/coordinate/Barrier.java
+++ b/accord-core/src/main/java/accord/coordinate/Barrier.java
@@ -56,7 +56,7 @@ import static accord.local.CommandSummaries.SummaryStatus.COMMITTED;
import static accord.local.CommandSummaries.SummaryStatus.INVALIDATED;
import static accord.local.CommandSummaries.ComputeIsDep.IGNORE;
import static accord.local.CommandSummaries.TestStartedAt.STARTED_AFTER;
-import static accord.primitives.Txn.Kind.Kinds.AnyGloballyVisible;
+import static accord.primitives.Txn.Kind.AnyGloballyVisible;
import static accord.utils.Invariants.checkArgument;
import static accord.utils.Invariants.checkState;
import static accord.utils.Invariants.illegalState;
diff --git a/accord-core/src/main/java/accord/coordinate/CollectCalculatedDeps.java b/accord-core/src/main/java/accord/coordinate/CollectCalculatedDeps.java
index 243cc768..e3a942b4 100644
--- a/accord-core/src/main/java/accord/coordinate/CollectCalculatedDeps.java
+++ b/accord-core/src/main/java/accord/coordinate/CollectCalculatedDeps.java
@@ -63,10 +63,7 @@ public class CollectCalculatedDeps implements Callback<CalculateDepsOk>
{
Topologies topologies = node.topology().withUnsyncedEpochs(collectFrom, txnId, executeAt);
CollectCalculatedDeps collect = new CollectCalculatedDeps(node, topologies, txnId, fullRoute.homeKey(), executeAt, callback);
- CommandStore store = CommandStore.maybeCurrent();
- if (store == null)
- store = node.commandStores().select(fullRoute);
-
+ CommandStore store = CommandStore.currentOrElseSelect(node, fullRoute);
SortedArrayList<Id> contact = collect.tracker.filterAndRecordFaulty();
if (contact == null) callback.accept(null, new Exhausted(txnId, collect.homeKey, null));
else node.send(contact, to -> new CalculateDeps(to, topologies, fullRoute, txnId, executeAt), store, collect);
@@ -97,13 +94,13 @@ public class CollectCalculatedDeps implements Callback<CalculateDepsOk>
}
@Override
- public void onCallbackFailure(Id from, Throwable failure)
+ public boolean onCallbackFailure(Id from, Throwable failure)
{
- if (isDone)
- return;
+ if (isDone) return false;
isDone = true;
callback.accept(null, failure);
+ return true;
}
private void onQuorum()
diff --git a/accord-core/src/main/java/accord/coordinate/CollectLatestDeps.java b/accord-core/src/main/java/accord/coordinate/CollectLatestDeps.java
index aa16483d..cb6d3c08 100644
--- a/accord-core/src/main/java/accord/coordinate/CollectLatestDeps.java
+++ b/accord-core/src/main/java/accord/coordinate/CollectLatestDeps.java
@@ -22,7 +22,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
-import accord.api.RoutingKey;
import accord.coordinate.tracking.QuorumTracker;
import accord.local.CommandStore;
import accord.local.Node;
@@ -32,6 +31,7 @@ import accord.messages.GetLatestDeps;
import accord.messages.GetLatestDeps.GetLatestDepsOk;
import accord.primitives.FullRoute;
import accord.primitives.LatestDeps;
+import accord.primitives.Route;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.primitives.Unseekables;
@@ -41,12 +41,13 @@ import accord.utils.SortedListMap;
import static accord.coordinate.tracking.RequestStatus.Failed;
import static accord.coordinate.tracking.RequestStatus.Success;
+import static accord.primitives.Routables.Slice.Minimal;
public class CollectLatestDeps implements Callback<GetLatestDepsOk>
{
final Node node;
final TxnId txnId;
- final RoutingKey homeKey;
+ final Route<?> route;
final Timestamp executeAt;
private final SortedListMap<Id, GetLatestDepsOk> oks;
@@ -54,11 +55,11 @@ public class CollectLatestDeps implements Callback<GetLatestDepsOk>
private final BiConsumer<List<LatestDeps>, Throwable> callback;
private boolean isDone;
- CollectLatestDeps(Node node, Topologies topologies, TxnId txnId, RoutingKey homeKey, Timestamp executeAt, BiConsumer<List<LatestDeps>, Throwable> callback)
+ CollectLatestDeps(Node node, Topologies topologies, TxnId txnId, Route<?> route, Timestamp executeAt, BiConsumer<List<LatestDeps>, Throwable> callback)
{
this.node = node;
this.txnId = txnId;
- this.homeKey = homeKey;
+ this.route = route;
this.executeAt = executeAt;
this.callback = callback;
this.oks = new SortedListMap<>(topologies.nodes(), GetLatestDepsOk[]::new);
@@ -67,15 +68,14 @@ public class CollectLatestDeps implements Callback<GetLatestDepsOk>
public static void withLatestDeps(Node node, TxnId txnId, FullRoute<?> fullRoute, Unseekables<?> collectFrom, Timestamp executeAt, BiConsumer<List<LatestDeps>, Throwable> callback)
{
- Topologies topologies = node.topology().withUnsyncedEpochs(collectFrom, txnId, executeAt);
- CollectLatestDeps collect = new CollectLatestDeps(node, topologies, txnId, fullRoute.homeKey(), executeAt, callback);
- CommandStore store = CommandStore.maybeCurrent();
- if (store == null)
- store = node.commandStores().select(fullRoute);
+ Route<?> route = fullRoute.intersecting(collectFrom, Minimal);
+ Topologies topologies = node.topology().withUnsyncedEpochs(route, txnId, executeAt);
+ CollectLatestDeps collect = new CollectLatestDeps(node, topologies, txnId, route, executeAt, callback);
+ CommandStore store = CommandStore.currentOrElseSelect(node, fullRoute);
SortedArrayList<Id> contact = collect.tracker.filterAndRecordFaulty();
- if (contact == null) callback.accept(null, new Exhausted(txnId, collect.homeKey, null));
- else node.send(contact, to -> new GetLatestDeps(to, topologies, fullRoute, txnId, executeAt), store, collect);
+ if (contact == null) callback.accept(null, new Exhausted(txnId, route.homeKey(), null));
+ else node.send(contact, to -> new GetLatestDeps(to, topologies, route, txnId, executeAt), store, collect);
}
@Override
@@ -98,18 +98,18 @@ public class CollectLatestDeps implements Callback<GetLatestDepsOk>
if (tracker.recordFailure(from) == Failed)
{
isDone = true;
- callback.accept(null, new Timeout(txnId, homeKey));
+ callback.accept(null, new Timeout(txnId, route.homeKey()));
}
}
@Override
- public void onCallbackFailure(Id from, Throwable failure)
+ public boolean onCallbackFailure(Id from, Throwable failure)
{
- if (isDone)
- return;
+ if (isDone) return false;
isDone = true;
callback.accept(null, failure);
+ return true;
}
private void onQuorum()
diff --git a/accord-core/src/main/java/accord/coordinate/CoordinateEphemeralRead.java b/accord-core/src/main/java/accord/coordinate/CoordinateEphemeralRead.java
index 565b3905..555f8a4e 100644
--- a/accord-core/src/main/java/accord/coordinate/CoordinateEphemeralRead.java
+++ b/accord-core/src/main/java/accord/coordinate/CoordinateEphemeralRead.java
@@ -86,8 +86,7 @@ public class CoordinateEphemeralRead extends AbstractCoordinatePreAccept<Result,
@Override
void contact(Collection<Node.Id> nodes, Topologies topologies, Callback<GetEphemeralReadDepsOk> callback)
{
- CommandStore commandStore = CommandStore.maybeCurrent();
- if (commandStore == null) commandStore = node.commandStores().select(route.homeKey());
+ CommandStore commandStore = CommandStore.currentOrElseSelect(node, route);
node.send(nodes, to -> new GetEphemeralReadDeps(to, topologies, route, txnId, executeAtEpoch), commandStore, callback);
}
@@ -108,16 +107,6 @@ public class CoordinateEphemeralRead extends AbstractCoordinatePreAccept<Result,
onPreAcceptedOrNewEpoch();
}
- @Override
- boolean onExtraSuccessInternal(Node.Id from, GetEphemeralReadDepsOk ok)
- {
- if (ok.latestEpoch > executeAtEpoch)
- executeAtEpoch = ok.latestEpoch;
-
- oks.put(from, ok);
- return true;
- }
-
@Override
public void onFailureInternal(Node.Id from, Throwable failure)
{
diff --git a/accord-core/src/main/java/accord/coordinate/CoordinateGloballyDurable.java b/accord-core/src/main/java/accord/coordinate/CoordinateGloballyDurable.java
index 5b0dd834..e8f313ee 100644
--- a/accord-core/src/main/java/accord/coordinate/CoordinateGloballyDurable.java
+++ b/accord-core/src/main/java/accord/coordinate/CoordinateGloballyDurable.java
@@ -81,8 +81,8 @@ public class CoordinateGloballyDurable extends SettableResult<Void> implements C
}
@Override
- public void onCallbackFailure(Node.Id from, Throwable failure)
+ public boolean onCallbackFailure(Node.Id from, Throwable failure)
{
- tryFailure(failure);
+ return tryFailure(failure);
}
}
diff --git a/accord-core/src/main/java/accord/coordinate/CoordinatePreAccept.java b/accord-core/src/main/java/accord/coordinate/CoordinatePreAccept.java
index 30699332..99d302f1 100644
--- a/accord-core/src/main/java/accord/coordinate/CoordinatePreAccept.java
+++ b/accord-core/src/main/java/accord/coordinate/CoordinatePreAccept.java
@@ -18,10 +18,8 @@
package accord.coordinate;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
-import java.util.function.Function;
+import java.util.function.BiFunction;
import accord.coordinate.tracking.FastPathTracker;
import accord.coordinate.tracking.PreAcceptTracker;
@@ -38,26 +36,28 @@ import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.topology.Topologies;
+import accord.utils.SortedListMap;
import accord.utils.WrappableException;
import static accord.api.ProtocolModifiers.QuorumEpochIntersections;
+import static accord.coordinate.Propose.NotAccept.proposeInvalidate;
import static accord.coordinate.tracking.RequestStatus.Success;
import static accord.primitives.Timestamp.mergeMax;
-import static accord.utils.Functions.foldl;
/**
* Perform initial rounds of PreAccept and Accept until we have reached agreement about when we should execute.
* If we are preempted by a recovery coordinator, we abort and let them complete (and notify us about the execution result)
*
* TODO (desired, testing): dedicated burn test to validate outcomes
+ * TODO (expected):
*/
abstract class CoordinatePreAccept<T> extends AbstractCoordinatePreAccept<T, PreAcceptReply>
{
final PreAcceptTracker<?> tracker;
// TODO (expected): this can be cleared after preaccept
- // TODO (expected): back by SortedListMap; must handle additional preaccepts (but this is no longer ordinarily enabled)
- private final List<PreAcceptOk> oks;
+ private final SortedListMap<Id, PreAcceptOk> oks;
final Txn txn;
+ boolean fastPathEnabled = true;
CoordinatePreAccept(Node node, TxnId txnId, Txn txn, FullRoute<?> route)
{
@@ -69,27 +69,24 @@ abstract class CoordinatePreAccept<T> extends AbstractCoordinatePreAccept<T, Pre
this(node, txnId, txn, route, topologies, FastPathTracker::new);
}
- CoordinatePreAccept(Node node, TxnId txnId, Txn txn, FullRoute<?> route, Topologies topologies, Function<Topologies, PreAcceptTracker<?>> trackerFactory)
+ CoordinatePreAccept(Node node, TxnId txnId, Txn txn, FullRoute<?> route, Topologies topologies, BiFunction<Topologies, TxnId, PreAcceptTracker<?>> trackerFactory)
{
super(node, route, txnId, topologies);
- this.tracker = trackerFactory.apply(topologies);
- this.oks = new ArrayList<>(topologies.nodes().size());
+ this.tracker = trackerFactory.apply(topologies, txnId);
+ this.oks = new SortedListMap<>(topologies.nodes(), PreAcceptOk[]::new);
this.txn = txn;
}
void contact(Collection<Id> nodes, Topologies topologies, Callback<PreAcceptReply> callback)
{
- // TODO (desired, efficiency): consider sending only to electorate of most recent topology (as only these PreAccept votes matter)
- // note that we must send to all replicas of old topology, as electorate may not be reachable
- CommandStore commandStore = CommandStore.maybeCurrent();
- if (commandStore == null) commandStore = node.commandStores().select(route.homeKey());
- node.send(nodes, to -> new PreAccept(to, topologies, txnId, txn, route), commandStore, callback);
+ CommandStore commandStore = CommandStore.currentOrElseSelect(node, route);
+ node.send(nodes, to -> new PreAccept(to, topologies, txnId, txn, null, false, route), commandStore, callback);
}
@Override
long executeAtEpoch()
{
- return foldl(oks, (ok, prev) -> ok.witnessedAt.epoch() > prev.epoch() ? ok.witnessedAt : prev, Timestamp.NONE).epoch();
+ return oks.foldlNonNullValues((ok, prev) -> ok.witnessedAt.epoch() > prev.epoch() ? ok.witnessedAt : prev, Timestamp.NONE).epoch();
}
@Override
@@ -119,9 +116,9 @@ abstract class CoordinatePreAccept<T> extends AbstractCoordinatePreAccept<T, Pre
else
{
PreAcceptOk ok = (PreAcceptOk) reply;
- oks.add(ok);
+ oks.put(from, ok);
- boolean fastPath = ok.witnessedAt.compareTo(txnId) == 0;
+ boolean fastPath = ok.witnessedAt.compareTo(txnId) == 0 && fastPathEnabled;
if (tracker.recordSuccess(from, fastPath) == Success)
onPreAcceptedOrNewEpoch();
}
@@ -134,17 +131,6 @@ abstract class CoordinatePreAccept<T> extends AbstractCoordinatePreAccept<T, Pre
onPreAcceptedOrNewEpoch();
}
- @Override
- boolean onExtraSuccessInternal(Id from, PreAcceptReply reply)
- {
- if (!reply.isOk())
- return false;
-
- PreAcceptOk ok = (PreAcceptOk) reply;
- oks.add(ok);
- return true;
- }
-
@Override
void onNewEpochTopologyMismatch(TopologyMismatch mismatch)
{
@@ -152,7 +138,7 @@ abstract class CoordinatePreAccept<T> extends AbstractCoordinatePreAccept<T, Pre
* We cannot execute the transaction because the execution epoch's topology no longer contains all of the
* participating keys/ranges, so we propose that the transaction is invalidated in its coordination epoch
*/
- Propose.Invalidate.proposeInvalidate(node, new Ballot(node.uniqueNow()), txnId, route.homeKey(), (outcome, failure) -> {
+ proposeInvalidate(node, new Ballot(node.uniqueNow()), txnId, route.homeKey(), (outcome, failure) -> {
if (failure != null)
mismatch.addSuppressed(failure);
accept(null, mismatch);
@@ -163,11 +149,11 @@ abstract class CoordinatePreAccept<T> extends AbstractCoordinatePreAccept<T, Pre
void onPreAccepted(Topologies topologies)
{
// TODO (expected): we do not have to take max here if we have enough fast path votes (this may unnecessarily force us onto the slow path)
- Timestamp executeAt = foldl(oks, (ok, prev) -> mergeMax(ok.witnessedAt, prev), Timestamp.NONE);
+ Timestamp executeAt = oks.foldlNonNullValues((ok, prev) -> mergeMax(ok.witnessedAt, prev), Timestamp.NONE);
node.withEpoch(executeAt.epoch(), this, t -> WrappableException.wrap(t), () -> {
onPreAccepted(topologies, executeAt, oks);
});
}
- abstract void onPreAccepted(Topologies topologies, Timestamp executeAt, List<PreAcceptOk> oks);
+ abstract void onPreAccepted(Topologies topologies, Timestamp executeAt, SortedListMap<Id, PreAcceptOk> oks);
}
diff --git a/accord-core/src/main/java/accord/coordinate/CoordinateSyncPoint.java b/accord-core/src/main/java/accord/coordinate/CoordinateSyncPoint.java
index 80e366ac..07a90917 100644
--- a/accord-core/src/main/java/accord/coordinate/CoordinateSyncPoint.java
+++ b/accord-core/src/main/java/accord/coordinate/CoordinateSyncPoint.java
@@ -29,6 +29,7 @@ import accord.api.Result;
import accord.coordinate.CoordinationAdapter.Adapters;
import accord.coordinate.CoordinationAdapter.Adapters.SyncPointAdapter;
import accord.local.Node;
+import accord.messages.Accept;
import accord.messages.Apply;
import accord.messages.Callback;
import accord.messages.PreAccept.PreAcceptOk;
@@ -42,18 +43,18 @@ import accord.primitives.Txn.Kind;
import accord.primitives.TxnId;
import accord.primitives.Unseekable;
import accord.primitives.Unseekables;
-import accord.primitives.Writes;
import accord.topology.Topologies;
+import accord.utils.SortedListMap;
import accord.utils.async.AsyncResult;
import accord.utils.async.AsyncResults;
import static accord.coordinate.ExecutePath.FAST;
-import static accord.coordinate.Propose.Invalidate.proposeAndCommitInvalidate;
+import static accord.coordinate.Propose.NotAccept.proposeAndCommitInvalidate;
import static accord.messages.Apply.Kind.Maximal;
import static accord.messages.Apply.participates;
+import static accord.primitives.Timestamp.Flag.REJECTED;
import static accord.primitives.Timestamp.mergeMax;
import static accord.primitives.Txn.Kind.ExclusiveSyncPoint;
-import static accord.utils.Functions.foldl;
import static accord.utils.Invariants.checkArgument;
/**
@@ -161,20 +162,22 @@ public class CoordinateSyncPoint<R> extends CoordinatePreAccept<R>
}
@Override
- void onPreAccepted(Topologies topologies, Timestamp executeAt, List<PreAcceptOk> oks)
+ void onPreAccepted(Topologies topologies, Timestamp executeAt, SortedListMap<Node.Id, PreAcceptOk> oks)
{
- Deps deps = Deps.merge(oks, oks.size(), List::get, ok -> ok.deps);
- Timestamp checkRejected = foldl(oks, (ok, prev) -> mergeMax(ok.witnessedAt, prev), Timestamp.NONE);
- if (checkRejected.isRejected())
+ Timestamp checkRejected = oks.foldlNonNullValues((ok, prev) -> mergeMax(ok.witnessedAt, prev), Timestamp.NONE);
+ if (checkRejected.is(REJECTED))
{
proposeAndCommitInvalidate(node, Ballot.ZERO, txnId, route.homeKey(), route, checkRejected, this);
}
else
{
+ Deps deps = Deps.merge(oks.valuesAsNullableList(), oks.domainSize(), List::get, ok -> ok.deps);
if (tracker.hasFastPathAccepted())
adapter.execute(node, topologies, route, FAST, txnId, txn, txnId, deps, this);
+ else if (tracker.hasMediumPathAccepted())
+ adapter.propose(node, topologies, route, Accept.Kind.MEDIUM, Ballot.ZERO, txnId, txn, txnId, deps, this);
else
- adapter.propose(node, topologies, route, Ballot.ZERO, txnId, txn, executeAt, deps, this);
+ adapter.propose(node, topologies, route, Accept.Kind.SLOW, Ballot.ZERO, txnId, txn, executeAt, deps, this);
}
}
@@ -198,8 +201,7 @@ public class CoordinateSyncPoint<R> extends CoordinatePreAccept<R>
Deps deps = syncPoint.waitFor;
FullRoute<?> route = syncPoint.route;
Result result = txn.result(txnId, executeAt, null);
- Writes writes = txn.execute(txnId, executeAt, null);
- Apply apply = Apply.FACTORY.create(Maximal, to, participates, txnId, route, txn, executeAt, deps, writes, result, route);
+ Apply apply = Apply.FACTORY.create(Maximal, to, participates, txnId, route, txn, executeAt, deps, null, result, route);
if (callback == null) node.send(to, apply);
else node.send(to, apply, callback);
}
diff --git a/accord-core/src/main/java/accord/coordinate/CoordinateTransaction.java b/accord-core/src/main/java/accord/coordinate/CoordinateTransaction.java
index 7baa11b1..fcaac41d 100644
--- a/accord-core/src/main/java/accord/coordinate/CoordinateTransaction.java
+++ b/accord-core/src/main/java/accord/coordinate/CoordinateTransaction.java
@@ -20,9 +20,27 @@ package accord.coordinate;
import java.util.List;
+import javax.annotation.Nullable;
+
import accord.api.Result;
+import accord.api.Timeouts;
+import accord.api.Timeouts.RegisteredTimeout;
import accord.coordinate.CoordinationAdapter.Adapters;
+import accord.local.Command;
+import accord.local.CommandStore;
+import accord.local.Commands;
+import accord.local.KeyHistory;
+import accord.local.PreLoadContext;
+import accord.local.SafeCommand;
+import accord.local.SafeCommandStore;
+import accord.local.StoreParticipants;
+import accord.messages.Accept;
import accord.messages.PreAccept;
+import accord.messages.PreAccept.PreAcceptNack;
+import accord.messages.PreAccept.PreAcceptReply;
+import accord.primitives.EpochSupplier;
+import accord.primitives.Status;
+import accord.primitives.Unseekables;
import accord.topology.Topologies;
import accord.local.Node;
import accord.messages.PreAccept.PreAcceptOk;
@@ -32,12 +50,22 @@ import accord.primitives.FullRoute;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
+import accord.utils.MapReduceConsume;
+import accord.utils.SortedListMap;
import accord.utils.async.AsyncResult;
import accord.utils.async.AsyncResults;
+import accord.utils.async.Cancellable;
+import static accord.coordinate.CoordinateTransaction.LocalExecuteState.PENDING;
+import static accord.coordinate.CoordinateTransaction.LocalExecuteState.SUCCESS;
+import static accord.coordinate.CoordinateTransaction.LocalExecuteState.TIMEOUT;
import static accord.coordinate.CoordinationAdapter.Factory.Kind.Standard;
import static accord.coordinate.ExecutePath.FAST;
-import static accord.coordinate.Propose.Invalidate.proposeAndCommitInvalidate;
+import static accord.coordinate.Propose.NotAccept.proposeAndCommitInvalidate;
+import static accord.local.Commands.AcceptOutcome.Success;
+import static accord.primitives.Timestamp.Flag.REJECTED;
+import static accord.primitives.TxnId.FastPath.PRIVILEGED_COORDINATOR_WITH_DEPS;
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
/**
* Perform initial rounds of PreAccept and Accept until we have reached agreement about when we should execute.
@@ -63,40 +91,55 @@ public class CoordinateTransaction extends CoordinatePreAccept<Result>
}
@Override
- void onPreAccepted(Topologies topologies, Timestamp executeAt, List<PreAcceptOk> oks)
+ void start()
+ {
+ if (txnId != null && txnId.hasPrivilegedCoordinator()) new LocalExecute().start();
+ else super.start();
+ }
+
+ @Override
+ void onPreAccepted(Topologies topologies, Timestamp executeAt, SortedListMap<Node.Id, PreAcceptOk> oks)
{
- // TODO (expected): this condition is too strong - we only need fast path _votes_ in the proposal epoch
- // but must have replied from a fast path quorum in earlier epochs
if (tracker.hasFastPathAccepted())
{
+ Deps deps = Deps.merge(oks.valuesAsNullableList(), oks.domainSize(), List::get, ok -> ok.deps);
// note: we merge all Deps regardless of witnessedAt. While we only need fast path votes,
// we must include Deps from fast path votes from earlier epochs that may have witnessed later transactions
// TODO (required): we might mask some bugs by merging more responses than we strictly need, so optimise this to optionally merge minimal deps
- Deps deps = Deps.merge(oks, oks.size(), List::get, ok -> ok.deps);
executeAdapter().execute(node, topologies, route, FAST, txnId, txn, txnId, deps, settingCallback());
node.agent().metricsEventsListener().onFastPathTaken(txnId, deps);
}
+ else if (tracker.hasMediumPathAccepted() && txnId.hasMediumPath())
+ {
+ Deps deps = Deps.merge(oks.valuesAsNullableList(), oks.domainSize(), List::get, ok -> ok.deps);
+ proposeAdapter().propose(node, topologies, route, Accept.Kind.MEDIUM, Ballot.ZERO, txnId, txn, txnId, deps, this);
+ node.agent().metricsEventsListener().onMediumPathTaken(txnId, deps);
+ }
else
{
- Deps deps = Deps.merge(oks, oks.size(), List::get, ok -> ok.deps);
-
// TODO (low priority, efficiency): perhaps don't submit Accept immediately if we almost have enough for fast-path,
// but by sending accept we rule out hybrid fast-path
// TODO (low priority, efficiency): if we receive an expired response, perhaps defer to permit at least one other
// node to respond before invalidating
- if (executeAt.isRejected() || executeAt.hlc() - txnId.hlc() >= node.agent().preAcceptTimeout())
+ if (executeAt.is(REJECTED) || executeAt.hlc() - txnId.hlc() >= node.agent().preAcceptTimeout())
{
proposeAndCommitInvalidate(node, Ballot.ZERO, txnId, route.homeKey(), route, executeAt,this);
+ node.agent().metricsEventsListener().onRejected(txnId);
}
else
{
if (PreAccept.rejectExecuteAt(txnId, topologies))
+ {
proposeAndCommitInvalidate(node, Ballot.ZERO, txnId, route.homeKey(), route, executeAt, this);
+ node.agent().metricsEventsListener().onRejected(txnId);
+ }
else
- proposeAdapter().propose(node, topologies, route, Ballot.ZERO, txnId, txn, executeAt, deps, this);
+ {
+ Deps deps = Deps.merge(oks.valuesAsNullableList(), oks.domainSize(), List::get, ok -> ok.deps);
+ proposeAdapter().propose(node, topologies, route, Accept.Kind.SLOW, Ballot.ZERO, txnId, txn, executeAt, deps, this);
+ node.agent().metricsEventsListener().onSlowPathTaken(txnId, deps);
+ }
}
-
- node.agent().metricsEventsListener().onSlowPathTaken(txnId, deps);
}
}
@@ -110,4 +153,169 @@ public class CoordinateTransaction extends CoordinatePreAccept<Result>
{
return node.coordinationAdapter(txnId, Standard);
}
+
+ enum LocalExecuteState { PENDING, SUCCESS, TIMEOUT}
+ class LocalExecute implements PreLoadContext, MapReduceConsume<SafeCommandStore, PreAcceptReply>, Timeouts.Timeout
+ {
+ LocalExecuteState state = LocalExecuteState.PENDING;
+ Cancellable cancel;
+ RegisteredTimeout timeout;
+
+ void start()
+ {
+ Cancellable cancel = node.mapReduceConsumeLocal(this, route, topologies.oldestEpoch(), topologies.currentEpoch(), this);
+ long expiresAt = node.agent().localExpiresAt(txnId, Status.Phase.PreAccept, MICROSECONDS);
+ RegisteredTimeout timeout = expiresAt <= 0 ? null : node.timeouts().registerAt(this, expiresAt, MICROSECONDS);
+ synchronized (this)
+ {
+ switch (state)
+ {
+ case PENDING:
+ this.cancel = cancel;
+ this.timeout = timeout;
+ return;
+ case TIMEOUT:
+ timeout = null;
+ case SUCCESS:
+ cancel = null;
+ }
+ }
+ if (cancel != null) cancel.cancel();
+ if (timeout != null) timeout.cancel();
+ }
+
+ @Override
+ public void accept(PreAcceptReply result, Throwable failure)
+ {
+ success();
+ if (failure != null)
+ {
+ CoordinateTransaction.this.accept(null, failure);
+ }
+ else
+ {
+ if (result.isOk())
+ {
+ CommandStore commandStore = CommandStore.currentOrElseSelect(node, route);
+ PreAcceptOk ok = (PreAcceptOk) result;
+ // TODO (desired): we can probably still process and record fast path votes from peers, just with different quorum requirements
+ boolean hasCoordinatorVote = txnId.equals(ok.witnessedAt);
+ if (!hasCoordinatorVote) fastPathEnabled = false;
+ Deps deps = hasCoordinatorVote && txnId.is(PRIVILEGED_COORDINATOR_WITH_DEPS) ? ok.deps : null;
+ onSuccess(node.id(), ok);
+ for (Node.Id id : topologies.nodes())
+ {
+ if (id.equals(node.id())) continue;
+ node.send(id, new PreAccept(id, topologies, txnId, txn, deps, hasCoordinatorVote, route), commandStore, CoordinateTransaction.this);
+ }
+ }
+ else
+ {
+ CoordinateTransaction.this.accept(null, new Preempted(txnId, route.homeKey()));
+ }
+ }
+
+ }
+
+ @Override
+ public PreAcceptReply apply(SafeCommandStore safeStore)
+ {
+ EpochSupplier minEpoch = topologies.size() == 1 ? txnId : EpochSupplier.constant(topologies.oldestEpoch());
+ StoreParticipants participants = StoreParticipants.update(safeStore, route, minEpoch.epoch(), txnId, txnId.epoch());
+ SafeCommand safeCommand = safeStore.get(txnId, participants);
+
+ Deps deps;
+ if (txnId.is(PRIVILEGED_COORDINATOR_WITH_DEPS))
+ {
+ deps = PreAccept.calculateDeps(safeStore, txnId, participants, minEpoch, txnId, true);
+ if (deps == null)
+ return PreAcceptNack.INSTANCE;
+
+ Commands.AcceptOutcome outcome = Commands.preaccept(safeStore, safeCommand, participants, txnId, txn, deps, true, route);
+ if (outcome != Success)
+ return PreAcceptNack.INSTANCE;
+ }
+ else
+ {
+ Commands.AcceptOutcome outcome = Commands.preaccept(safeStore, safeCommand, participants, txnId, txn, null, true, route);
+ if (outcome != Success)
+ return PreAcceptNack.INSTANCE;
+
+ deps = PreAccept.calculateDeps(safeStore, txnId, participants, minEpoch, txnId, true);
+ if (deps == null)
+ return PreAcceptNack.INSTANCE;
+ }
+
+ Command command = safeCommand.current();
+ return new PreAcceptOk(txnId, command.executeAt(), deps);
+ }
+
+ @Override
+ public PreAcceptReply reduce(PreAcceptReply r1, PreAcceptReply r2)
+ {
+ return PreAcceptReply.reduce(r1, r2);
+ }
+
+ @Override
+ public void timeout()
+ {
+ Cancellable cancel;
+ synchronized (this)
+ {
+ if (state != PENDING)
+ return;
+
+ state = TIMEOUT;
+ timeout = null;
+ if (this.cancel == null)
+ return;
+ cancel = this.cancel;
+ this.cancel = null;
+ }
+ cancel.cancel();
+ }
+
+ void success()
+ {
+ RegisteredTimeout cancel;
+ synchronized (this)
+ {
+ if (state != PENDING)
+ return;
+
+ state = SUCCESS;
+ this.cancel = null;
+ if (timeout == null)
+ return;
+ cancel = timeout;
+ timeout = null;
+ }
+ cancel.cancel();
+ }
+
+ @Override
+ public int stripe()
+ {
+ return txnId == null ? 0 : txnId.hashCode();
+ }
+
+ @Nullable
+ @Override
+ public TxnId primaryTxnId()
+ {
+ return txnId;
+ }
+
+ @Override
+ public KeyHistory keyHistory()
+ {
+ return KeyHistory.SYNC;
+ }
+
+ @Override
+ public Unseekables<?> keys()
+ {
+ return route;
+ }
+ }
}
diff --git a/accord-core/src/main/java/accord/coordinate/CoordinationAdapter.java b/accord-core/src/main/java/accord/coordinate/CoordinationAdapter.java
index 30dcbb8e..e4a90d5c 100644
--- a/accord-core/src/main/java/accord/coordinate/CoordinationAdapter.java
+++ b/accord-core/src/main/java/accord/coordinate/CoordinationAdapter.java
@@ -19,16 +19,17 @@
package accord.coordinate;
import java.util.function.BiConsumer;
-import java.util.function.Function;
+import java.util.function.BiFunction;
import javax.annotation.Nullable;
import accord.api.ProtocolModifiers;
import accord.api.Result;
import accord.coordinate.ExecuteSyncPoint.ExecuteInclusive;
import accord.coordinate.tracking.FastPathTracker;
+import accord.coordinate.tracking.PreAcceptExclusiveSyncPointTracker;
import accord.coordinate.tracking.PreAcceptTracker;
-import accord.coordinate.tracking.QuorumTracker;
import accord.local.Node;
+import accord.messages.Accept;
import accord.messages.Apply;
import accord.primitives.Ballot;
import accord.primitives.Deps;
@@ -43,6 +44,7 @@ import accord.primitives.Unseekable;
import accord.primitives.Writes;
import accord.topology.Topologies;
import accord.utils.Invariants;
+import accord.utils.UnhandledEnum;
import static accord.api.ProtocolModifiers.QuorumEpochIntersections;
import static accord.coordinate.CoordinationAdapter.Factory.Kind.Recovery;
@@ -59,7 +61,7 @@ public interface CoordinationAdapter<R>
<R> CoordinationAdapter<R> get(TxnId txnId, Kind kind);
}
- void propose(Node node, @Nullable Topologies preaccept, FullRoute<?> route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super R, Throwable> callback);
+ void propose(Node node, @Nullable Topologies preaccept, FullRoute<?> route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super R, Throwable> callback);
void stabilise(Node node, @Nullable Topologies any, FullRoute<?> route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super R, Throwable> callback);
void execute(Node node, @Nullable Topologies any, FullRoute<?> route, ExecutePath path, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super R, Throwable> callback);
void persist(Node node, @Nullable Topologies any, FullRoute<?> route, Route<?> sendTo, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer<? super R, Throwable> callback);
@@ -85,7 +87,7 @@ public interface CoordinationAdapter<R>
}
switch (kind)
{
- default: throw new AssertionError("Unhandled kind: " + kind);
+ default: throw new UnhandledEnum(kind);
case Standard: return (CoordinationAdapter<R>) Adapters.standard();
case Recovery: return (CoordinationAdapter<R>) Adapters.recover();
}
@@ -134,11 +136,11 @@ public interface CoordinationAdapter<R>
public static abstract class AbstractTxnAdapter implements CoordinationAdapter<Result>
{
@Override
- public void propose(Node node, @Nullable Topologies preacceptOrRecovery, FullRoute<?> route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super Result, Throwable> callback)
+ public void propose(Node node, @Nullable Topologies preacceptOrRecovery, FullRoute<?> route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super Result, Throwable> callback)
{
Topologies accept = node.topology().reselect(preacceptOrRecovery, QuorumEpochIntersections.preacceptOrRecover,
route, txnId, executeAt, QuorumEpochIntersections.accept);
- new ProposeTxn(node, accept, route, ballot, txnId, txn, executeAt, deps, callback).start();
+ new ProposeTxn(node, accept, route, kind, ballot, txnId, txn, executeAt, deps, callback).start();
}
@Override
@@ -166,8 +168,16 @@ public interface CoordinationAdapter<R>
{
Topologies all = execution(node, any, route, txnId, executeAt);
- if (txn.read().keys().isEmpty()) persist(node, all, route, txnId, txn, executeAt, deps, txn.execute(txnId, executeAt, null), txn.result(txnId, executeAt, null), callback);
- else new ExecuteTxn(node, all, route, path, txnId, txn, txn.read().keys().toParticipants(), executeAt, deps, callback).start();
+ if (txn.read().keys().isEmpty())
+ {
+ Writes writes = txnId.is(Txn.Kind.Write) ? txn.execute(txnId, executeAt, null) : null;
+ Result result = txn.result(txnId, executeAt, null);
+ persist(node, all, route, txnId, txn, executeAt, deps, writes, result, callback);
+ }
+ else
+ {
+ new ExecuteTxn(node, all, route, path, txnId, txn, executeAt, deps, callback).start();
+ }
}
Topologies execution(Node node, Topologies preacceptOrCommit, Participants<?> participants, TxnId txnId, Timestamp executeAt)
@@ -208,9 +218,9 @@ public interface CoordinationAdapter<R>
public static abstract class SyncPointAdapter<R> implements CoordinationAdapter<R>
{
- final Function<Topologies, PreAcceptTracker<?>> preacceptTrackerFactory;
+ final BiFunction<Topologies, TxnId, PreAcceptTracker<?>> preacceptTrackerFactory;
- protected SyncPointAdapter(Function<Topologies, PreAcceptTracker<?>> preacceptTrackerFactory)
+ protected SyncPointAdapter(BiFunction<Topologies, TxnId, PreAcceptTracker<?>> preacceptTrackerFactory)
{
this.preacceptTrackerFactory = preacceptTrackerFactory;
}
@@ -220,10 +230,10 @@ public interface CoordinationAdapter<R>
abstract void invokeSuccess(Node node, FullRoute<?> route, TxnId txnId, Timestamp executeAt, Txn txn, Deps deps, BiConsumer<? super R, Throwable> callback);
@Override
- public void propose(Node node, Topologies any, FullRoute<?> route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super R, Throwable> callback)
+ public void propose(Node node, Topologies any, FullRoute<?> route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super R, Throwable> callback)
{
Topologies all = forDecision(node, route, txnId);
- new ProposeSyncPoint<>(this, node, all, route, ballot, txnId, txn, executeAt, deps, callback).start();
+ new ProposeSyncPoint<>(this, node, all, route, kind, ballot, txnId, txn, executeAt, deps, callback).start();
}
@Override
@@ -237,7 +247,7 @@ public interface CoordinationAdapter<R>
@Override
public void execute(Node node, Topologies any, FullRoute<?> route, ExecutePath path, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super R, Throwable> callback)
{
- persist(node, null, route, txnId, txn, executeAt, deps, txn.execute(txnId, executeAt, null), txn.result(txnId, executeAt, null), callback);
+ persist(node, null, route, txnId, txn, executeAt, deps, null, txn.result(txnId, executeAt, null), callback);
}
@Override
@@ -255,7 +265,7 @@ public interface CoordinationAdapter<R>
{
public AbstractExclusiveSyncPointAdapter()
{
- super(QuorumTracker::new);
+ super(PreAcceptExclusiveSyncPointTracker::new);
}
@Override
@@ -305,6 +315,7 @@ public interface CoordinationAdapter<R>
{
protected AbstractInclusiveSyncPointAdapter()
{
+ // TODO (required): this is only safe if SyncPoint is visible to other transactions
super(FastPathTracker::new);
}
diff --git a/accord-core/src/main/java/accord/coordinate/ExecuteEphemeralRead.java b/accord-core/src/main/java/accord/coordinate/ExecuteEphemeralRead.java
index cfd5b083..ba5cfbbf 100644
--- a/accord-core/src/main/java/accord/coordinate/ExecuteEphemeralRead.java
+++ b/accord-core/src/main/java/accord/coordinate/ExecuteEphemeralRead.java
@@ -126,9 +126,6 @@ public class ExecuteEphemeralRead extends ReadCoordinator<ReadReply>
// also try sending a read command to another replica, in case they're ready to serve a response
callback.accept(null, illegalState("Received Insufficient response to ephemeral read request"));
return Action.Aborted;
- case Invalid:
- callback.accept(null, illegalState("Submitted a read command to a replica that did not own the range"));
- return Action.Aborted;
}
}
diff --git a/accord-core/src/main/java/accord/coordinate/ExecutePath.java b/accord-core/src/main/java/accord/coordinate/ExecutePath.java
index 15bce6b1..95ceaa51 100644
--- a/accord-core/src/main/java/accord/coordinate/ExecutePath.java
+++ b/accord-core/src/main/java/accord/coordinate/ExecutePath.java
@@ -20,5 +20,5 @@ package accord.coordinate;
public enum ExecutePath
{
- FAST, SLOW, RECOVER
+ FAST, MEDIUM, SLOW, RECOVER
}
diff --git a/accord-core/src/main/java/accord/coordinate/ExecuteSyncPoint.java b/accord-core/src/main/java/accord/coordinate/ExecuteSyncPoint.java
index f7d4427c..cef23d33 100644
--- a/accord-core/src/main/java/accord/coordinate/ExecuteSyncPoint.java
+++ b/accord-core/src/main/java/accord/coordinate/ExecuteSyncPoint.java
@@ -43,15 +43,14 @@ import accord.primitives.SyncPoint;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.Unseekable;
-import accord.primitives.Writes;
import accord.topology.Topologies;
import accord.utils.Invariants;
import accord.utils.SortedArrays.SortedArrayList;
+import accord.utils.UnhandledEnum;
import accord.utils.WrappableException;
import accord.utils.async.AsyncResults.SettableResult;
import static accord.messages.Apply.ApplyReply.Insufficient;
-import static accord.messages.ReadData.CommitOrReadNack.Waiting;
import static accord.primitives.Status.Durability.Majority;
import static accord.primitives.Txn.Kind.ExclusiveSyncPoint;
@@ -107,7 +106,6 @@ public abstract class ExecuteSyncPoint<U extends Unseekable> extends SettableRes
switch ((CommitOrReadNack) reply)
{
case Waiting:
- case Invalid:
case Redundant:
return true;
case Insufficient:
@@ -130,7 +128,7 @@ public abstract class ExecuteSyncPoint<U extends Unseekable> extends SettableRes
onDurableSuccess(from);
}
@Override public void onFailure(Node.Id from, Throwable failure) {}
- @Override public void onCallbackFailure(Node.Id from, Throwable failure) {}
+ @Override public boolean onCallbackFailure(Node.Id from, Throwable failure) { return false; }
};
}
CoordinateSyncPoint.sendApply(node, to, syncPoint, tracker.topologies(), insufficientCallback);
@@ -140,11 +138,10 @@ public abstract class ExecuteSyncPoint<U extends Unseekable> extends SettableRes
public void start()
{
Txn txn = node.agent().emptySystemTxn(syncPoint.syncId.kind(), syncPoint.syncId.domain());
- Writes writes = txn.execute(syncPoint.syncId, syncPoint.syncId, null);
Result result = txn.result(syncPoint.syncId, syncPoint.syncId, null);
node.send(tracker.topologies().nodes(), to -> {
Participants<?> participants = syncPoint.route.participants();
- return new ApplyThenWaitUntilApplied(to, tracker.topologies(), executeAt, syncPoint.route(), syncPoint.syncId, txn, syncPoint.waitFor, participants, writes, result);
+ return new ApplyThenWaitUntilApplied(to, tracker.topologies(), executeAt, syncPoint.route(), syncPoint.syncId, txn, syncPoint.waitFor, participants, null, result);
}, this);
}
}
@@ -253,7 +250,7 @@ public abstract class ExecuteSyncPoint<U extends Unseekable> extends SettableRes
{
switch ((CommitOrReadNack)reply)
{
- default: throw new AssertionError("Unhandled: " + reply);
+ default: throw new UnhandledEnum((CommitOrReadNack)reply);
case Insufficient:
sendApply(from);
@@ -263,9 +260,6 @@ public abstract class ExecuteSyncPoint<U extends Unseekable> extends SettableRes
tryFailure(new SyncPointErased());
return;
- case Invalid:
- tryFailure(new Invalidated(syncPoint.syncId, syncPoint.route.homeKey()));
-
case Waiting:
}
}
@@ -299,8 +293,8 @@ public abstract class ExecuteSyncPoint<U extends Unseekable> extends SettableRes
}
@Override
- public void onCallbackFailure(Node.Id from, Throwable failure)
+ public boolean onCallbackFailure(Node.Id from, Throwable failure)
{
- tryFailure(failure);
+ return tryFailure(failure);
}
}
diff --git a/accord-core/src/main/java/accord/coordinate/ExecuteTxn.java b/accord-core/src/main/java/accord/coordinate/ExecuteTxn.java
index 3ede9cc1..8ec1d43b 100644
--- a/accord-core/src/main/java/accord/coordinate/ExecuteTxn.java
+++ b/accord-core/src/main/java/accord/coordinate/ExecuteTxn.java
@@ -25,13 +25,24 @@ import org.slf4j.LoggerFactory;
import accord.api.Data;
import accord.api.Result;
+import accord.local.CommandStore;
+import accord.local.Commands;
import accord.local.Node;
import accord.local.Node.Id;
+import accord.local.SafeCommand;
+import accord.local.SafeCommandStore;
+import accord.local.StoreParticipants;
+import accord.messages.Accept;
import accord.messages.Commit;
+import accord.messages.MessageType;
+import accord.messages.ReadData;
import accord.messages.ReadData.CommitOrReadNack;
import accord.messages.ReadData.ReadOk;
import accord.messages.ReadData.ReadReply;
import accord.messages.ReadTxnData;
+import accord.messages.Request;
+import accord.messages.StableThenRead;
+import accord.primitives.Ballot;
import accord.primitives.Deps;
import accord.primitives.FullRoute;
import accord.primitives.Participants;
@@ -39,19 +50,29 @@ import accord.primitives.Ranges;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
+import accord.primitives.Writes;
import accord.topology.Topologies;
import accord.utils.Invariants;
+import accord.utils.UnhandledEnum;
import org.agrona.collections.IntHashSet;
+import static accord.api.ProtocolModifiers.Toggles.permitLocalExecution;
import static accord.coordinate.CoordinationAdapter.Factory.Kind.Standard;
+import static accord.coordinate.ExecutePath.FAST;
import static accord.coordinate.ExecutePath.RECOVER;
import static accord.coordinate.ReadCoordinator.Action.Approve;
import static accord.coordinate.ReadCoordinator.Action.ApprovePartial;
import static accord.messages.Commit.Kind.StableFastPath;
+import static accord.messages.Commit.Kind.StableMediumPath;
import static accord.messages.Commit.Kind.StableSlowPath;
import static accord.messages.Commit.Kind.StableWithTxnAndDeps;
-import static accord.utils.Invariants.illegalState;
+import static accord.primitives.SaveStatus.Stable;
+import static accord.primitives.Status.Phase.Execute;
+import static accord.primitives.Status.PreAccepted;
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+// TODO (expected): return Waiting from ReadData if not ready to execute, and do not submit more than one speculative retry in this case
+// TODO (expected): by default, if we can execute locally, never contact a remote replica regardless of local outcome
public class ExecuteTxn extends ReadCoordinator<ReadReply>
{
@SuppressWarnings("unused")
@@ -61,27 +82,46 @@ public class ExecuteTxn extends ReadCoordinator<ReadReply>
final ExecutePath path;
final Txn txn;
- final Participants<?> readScope;
final FullRoute<?> route;
final Timestamp executeAt;
final Deps stableDeps;
final Topologies allTopologies;
final BiConsumer<? super Result, Throwable> callback;
+ private Participants<?> readScope;
private Data data;
- ExecuteTxn(Node node, Topologies topologies, FullRoute<?> route, ExecutePath path, TxnId txnId, Txn txn, Participants<?> readScope, Timestamp executeAt, Deps stableDeps, BiConsumer<? super Result, Throwable> callback)
+ ExecuteTxn(Node node, Topologies topologies, FullRoute<?> route, ExecutePath path, TxnId txnId, Txn txn, Timestamp executeAt, Deps stableDeps, BiConsumer<? super Result, Throwable> callback)
{
super(node, topologies.forEpoch(executeAt.epoch()), txnId);
- Invariants.checkState(!txnId.awaitsOnlyDeps());
- Invariants.checkState(!txnId.awaitsPreviouslyOwned());
this.path = path;
this.txn = txn;
this.route = route;
this.allTopologies = topologies;
- this.readScope = readScope;
this.executeAt = executeAt;
this.stableDeps = stableDeps;
this.callback = callback;
+ Invariants.checkState(!txnId.awaitsOnlyDeps());
+ Invariants.checkState(!txnId.awaitsPreviouslyOwned());
+ }
+
+ @Override
+ protected void startOnceInitialised()
+ {
+ if (permitLocalExecution() && tryIfUniversal(node.id()))
+ {
+ new LocalExecute(txnId).process(node, node.agent().localExpiresAt(txnId, Execute, MICROSECONDS));
+ }
+ else if (path == FAST && txnId.hasPrivilegedCoordinator())
+ {
+ // we can't safely take the fast path via PRIVILEGED_COORDINATOR optimisation if we aren't permitted to execute locally,
+ // so we take the MEDIUM or SLOW path
+ adapter().propose(node, null, route, txnId.hasMediumPath() ? Accept.Kind.MEDIUM : Accept.Kind.SLOW,
+ Ballot.ZERO, txnId, txn, executeAt, stableDeps, callback);
+ }
+ else
+ {
+ super.startOnceInitialised();
+ }
}
@Override
@@ -89,15 +129,16 @@ public class ExecuteTxn extends ReadCoordinator<ReadReply>
{
IntHashSet readSet = new IntHashSet();
to.forEach(i -> readSet.add(i.id));
- Commit.stableAndRead(node, allTopologies, commitKind(), txnId, txn, route, readScope, executeAt, stableDeps, readSet, this, SEND_MINIMUM_STABLE_MESSAGES && path != RECOVER);
+ Commit.stableAndRead(node, allTopologies, commitKind(), txnId, txn, route, executeAt, stableDeps, readSet, this, SEND_MINIMUM_STABLE_MESSAGES && path != RECOVER);
}
private Commit.Kind commitKind()
{
switch (path)
{
- default: throw new AssertionError("Unhandled path: " + path);
+ default: throw new UnhandledEnum(path);
case FAST: return StableFastPath;
+ case MEDIUM: return StableMediumPath;
case SLOW: return StableSlowPath;
case RECOVER: return StableWithTxnAndDeps;
}
@@ -106,14 +147,18 @@ public class ExecuteTxn extends ReadCoordinator<ReadReply>
@Override
public void contact(Id to)
{
+ CommandStore commandStore = CommandStore.currentOrElseSelect(node, route);
if (SEND_MINIMUM_STABLE_MESSAGES && path != RECOVER)
{
+ Request request = Commit.requestTo(to, true, allTopologies, commitKind(), Ballot.ZERO, txnId, txn, route, executeAt, stableDeps, false);
// we are always sending to a replica in the latest epoch and requesting a read, so onlyContactOldAndReadSet is a redundant parameter
- Commit.stableAndRead(to, node, allTopologies, commitKind(), txnId, txn, route, readScope, executeAt, stableDeps, this, false);
+ node.send(to, request, commandStore, this);
}
else
{
- node.send(to, new ReadTxnData(to, topologies(), txnId, readScope, executeAt.epoch()), this);
+ if (readScope == null)
+ readScope = txn.read().keys().toParticipants();
+ node.send(to, new ReadTxnData(to, topologies(), txnId, readScope, executeAt.epoch()), commandStore, this);
}
}
@@ -149,9 +194,6 @@ public class ExecuteTxn extends ReadCoordinator<ReadReply>
Commit.stableMaximal(node, from, txn, txnId, executeAt, route, stableDeps);
// also try sending a read command to another replica, in case they're ready to serve a response
return Action.TryAlternative;
- case Invalid:
- callback.accept(null, illegalState("Submitted a read command to a replica that did not own the range"));
- return Action.Aborted;
}
}
@@ -160,8 +202,9 @@ public class ExecuteTxn extends ReadCoordinator<ReadReply>
{
if (failure == null)
{
+ Writes writes = txnId.is(Txn.Kind.Write) ? txn.execute(txnId, executeAt, data) : null;
Result result = txn.result(txnId, executeAt, data);
- adapter().persist(node, allTopologies, route, txnId, txn, executeAt, stableDeps, txn.execute(txnId, executeAt, data), result, callback);
+ adapter().persist(node, allTopologies, route, txnId, txn, executeAt, stableDeps, writes, result, callback);
}
else
{
@@ -182,4 +225,70 @@ public class ExecuteTxn extends ReadCoordinator<ReadReply>
", route=" + route +
'}';
}
+
+ class LocalExecute extends ReadData
+ {
+ private boolean committed;
+ public LocalExecute(TxnId txnId)
+ {
+ super(txnId, route, executeAt.epoch());
+ }
+
+ @Override
+ public CommitOrReadNack apply(SafeCommandStore safeStore)
+ {
+ StoreParticipants participants = StoreParticipants.execute(safeStore, route, txnId, minEpoch(), executeAtEpoch);
+ SafeCommand safeCommand = safeStore.get(txnId, participants);
+ return apply(safeStore, safeCommand, participants);
+ }
+
+ @Override
+ protected CommitOrReadNack apply(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants)
+ {
+ if (txnId.hasPrivilegedCoordinator() && path == FAST && (!safeCommand.current().promised().equals(Ballot.ZERO) || safeCommand.current().status() != PreAccepted))
+ return CommitOrReadNack.Rejected;
+ Commands.commit(safeStore, safeCommand, participants, Stable, Ballot.ZERO, txnId, route, txn, executeAt, stableDeps, commitKind());
+ return super.apply(safeStore, safeCommand, participants);
+ }
+
+ @Override
+ public void accept(CommitOrReadNack reply, Throwable failure)
+ {
+ if (failure == null && reply == null) committed = true;
+ super.accept(reply, failure);
+ }
+
+ @Override
+ protected ExecuteOn executeOn()
+ {
+ return StableThenRead.EXECUTE_ON;
+ }
+
+ @Override
+ protected boolean cancel()
+ {
+ if (!super.cancel())
+ return false;
+
+ // TODO (desired): if we fail to commit locally we can submit a slow/medium path request
+ if (txnId.hasPrivilegedCoordinator())
+ finishOnFailure(new Timeout(txnId, route.homeKey(), "Could not promptly commit on privileged coordinator"), true);
+ else
+ onFailure(node.id(), new Timeout(txnId, route.homeKey(), "Could not promptly " + (committed ? "commit to" : "read from") + " local coordinator"));
+ return true;
+ }
+
+ @Override
+ protected void reply(ReadReply reply, Throwable fail)
+ {
+ if (fail == null) onSuccess(node.id(), reply);
+ else onFailure(node.id(), fail);
+ }
+
+ @Override
+ public ReadType kind() { throw new UnsupportedOperationException(); }
+
+ @Override
+ public MessageType type() { throw new UnsupportedOperationException(); }
+ }
}
diff --git a/accord-core/src/main/java/accord/coordinate/FetchData.java b/accord-core/src/main/java/accord/coordinate/FetchData.java
index 37537e3e..624cc54c 100644
--- a/accord-core/src/main/java/accord/coordinate/FetchData.java
+++ b/accord-core/src/main/java/accord/coordinate/FetchData.java
@@ -41,7 +41,6 @@ import javax.annotation.Nonnull;
import static accord.coordinate.Infer.InvalidIf.NotKnownToBeInvalid;
import static accord.coordinate.Infer.InvalidateAndCallback.locallyInvalidateAndCallback;
-import static accord.primitives.EpochSupplier.constant;
import static accord.primitives.Route.castToRoute;
import static accord.primitives.Route.isFullRoute;
import static accord.primitives.Route.isRoute;
@@ -67,6 +66,7 @@ public class FetchData extends CheckShards<Route<?>>
}
}
+ // TODO (expected): separate keys we fetch deps and txns for
private static class FetchRequest
{
final Known fetch;
@@ -192,11 +192,11 @@ public class FetchData extends CheckShards<Route<?>>
{
Invariants.checkState(req.executeAt == null);
TxnId txnId = req.txnId;
- switch (found.outcome)
+ switch (found.outcome())
{
- default: throw new AssertionError("Unknown outcome: " + found.outcome);
- case Invalidated:
- locallyInvalidateAndCallback(node, txnId, constant(req.lowEpoch), constant(req.highEpoch), req.fetchKeys, new FetchResult(found, req.fetchKeys, null), req.callback);
+ default: throw new AssertionError("Unknown outcome: " + found.outcome());
+ case Abort:
+ locallyInvalidateAndCallback(node, txnId, req.lowEpoch, req.highEpoch, req.fetchKeys, new FetchResult(found, req.fetchKeys, null), req.callback);
break;
case Unknown:
@@ -337,10 +337,7 @@ public class FetchData extends CheckShards<Route<?>>
else
{
if (success == ReadCoordinator.Success.Success)
- {
- if (!isSufficient(merged))
- Invariants.checkState(isSufficient(merged), "Status %s is not sufficient", merged);
- }
+ Invariants.checkState(isSufficient(merged), "Status %s is not sufficient", merged);
// TODO (expected): should we automatically trigger a new fetch if we find executeAt but did not request enough information? would be more rob ust
Propagate.propagate(node, txnId, previouslyKnownToBeInvalidIf, sourceEpoch, lowEpoch, highEpoch, success.withQuorum, query(), propagateTo, target, (CheckStatusOkFull) merged, callback);
diff --git a/accord-core/src/main/java/accord/coordinate/FindRoute.java b/accord-core/src/main/java/accord/coordinate/FindRoute.java
index af2fc7b5..49a396c7 100644
--- a/accord-core/src/main/java/accord/coordinate/FindRoute.java
+++ b/accord-core/src/main/java/accord/coordinate/FindRoute.java
@@ -47,7 +47,7 @@ public class FindRoute extends CheckShards<Route<?>>
public Result(CheckStatusOk ok)
{
this.route = Route.castToFullRoute(ok.route);
- this.executeAt = ok.maxKnown().executeAt.isDecidedAndKnownToExecute() ? ok.executeAt : null;
+ this.executeAt = ok.maxKnown().executeAt().isDecidedAndKnownToExecute() ? ok.executeAt : null;
}
}
diff --git a/accord-core/src/main/java/accord/coordinate/Infer.java b/accord-core/src/main/java/accord/coordinate/Infer.java
index a15c1a76..a2bf62c7 100644
--- a/accord-core/src/main/java/accord/coordinate/Infer.java
+++ b/accord-core/src/main/java/accord/coordinate/Infer.java
@@ -29,7 +29,6 @@ import accord.local.SafeCommandStore;
import accord.primitives.Status;
import accord.primitives.Known;
import accord.local.StoreParticipants;
-import accord.primitives.EpochSupplier;
import accord.primitives.Participants;
import accord.primitives.TxnId;
import accord.primitives.Unseekables;
@@ -86,7 +85,7 @@ public class Infer
return NotKnownToBeInvalid;
}
- if (maxKnown.executeAt.hasDecision())
+ if (maxKnown.executeAt().hasDecision())
{
// could be invalidated or committed, but we definitely know which so we don't need to infer anything
return NotKnownToBeInvalid;
@@ -111,12 +110,12 @@ public class Infer
final TxnId txnId;
// TODO (expected): more consistent handling of transactions that only MAY intersect a commandStore
// (e.g. dependencies from an earlier epoch that have not yet committed, or commands that are proposed to execute in a later epoch than eventually agreed)
- final EpochSupplier lowEpoch, highEpoch;
+ final long lowEpoch, highEpoch;
final Participants<?> participants;
final T param;
final BiConsumer<T, Throwable> callback;
- private CleanupAndCallback(Node node, TxnId txnId, EpochSupplier lowEpoch, EpochSupplier highEpoch, Participants<?> participants, T param, BiConsumer<T, Throwable> callback)
+ private CleanupAndCallback(Node node, TxnId txnId, long lowEpoch, long highEpoch, Participants<?> participants, T param, BiConsumer<T, Throwable> callback)
{
this.node = node;
this.txnId = txnId;
@@ -131,14 +130,14 @@ public class Infer
{
PreLoadContext loadContext = contextFor(txnId);
Unseekables<?> propagateTo = isRoute(participants) ? castToRoute(participants).withHomeKey() : participants;
- node.mapReduceConsumeLocal(loadContext, propagateTo, lowEpoch.epoch(), highEpoch.epoch(), this);
+ node.mapReduceConsumeLocal(loadContext, propagateTo, lowEpoch, highEpoch, this);
}
@Override
public Void apply(SafeCommandStore safeStore)
{
// we're applying an invalidation, so the record will not be cleaned up until the whole range is truncated
- StoreParticipants participants = StoreParticipants.invalidate(safeStore, this.participants, txnId);
+ StoreParticipants participants = StoreParticipants.notAccept(safeStore, this.participants, txnId);
return apply(safeStore, safeStore.get(txnId, participants));
}
@@ -160,12 +159,12 @@ public class Infer
// TODO (required, consider): low and high bounds are correct?
static class InvalidateAndCallback<T> extends CleanupAndCallback<T>
{
- private InvalidateAndCallback(Node node, TxnId txnId, EpochSupplier lowEpoch, EpochSupplier highEpoch, Participants<?> someUnseekables, T param, BiConsumer<T, Throwable> callback)
+ private InvalidateAndCallback(Node node, TxnId txnId, long lowEpoch, long highEpoch, Participants<?> someUnseekables, T param, BiConsumer<T, Throwable> callback)
{
super(node, txnId, lowEpoch, highEpoch, someUnseekables, param, callback);
}
- public static <T> void locallyInvalidateAndCallback(Node node, TxnId txnId, EpochSupplier lowEpoch, EpochSupplier highEpoch, Participants<?> someUnseekables, T param, BiConsumer<T, Throwable> callback)
+ public static <T> void locallyInvalidateAndCallback(Node node, TxnId txnId, long lowEpoch, long highEpoch, Participants<?> someUnseekables, T param, BiConsumer<T, Throwable> callback)
{
new InvalidateAndCallback<>(node, txnId, lowEpoch, highEpoch, someUnseekables, param, callback).start();
}
diff --git a/accord-core/src/main/java/accord/coordinate/Invalidate.java b/accord-core/src/main/java/accord/coordinate/Invalidate.java
index c85fbf8c..359c9443 100644
--- a/accord-core/src/main/java/accord/coordinate/Invalidate.java
+++ b/accord-core/src/main/java/accord/coordinate/Invalidate.java
@@ -33,16 +33,17 @@ import accord.api.RoutingKey;
import accord.messages.BeginInvalidation;
import accord.messages.BeginInvalidation.InvalidateReply;
import accord.messages.Callback;
-import accord.topology.Topology;
import accord.utils.Invariants;
import accord.utils.SortedArrays;
+import accord.utils.SortedListMap;
+import accord.utils.UnhandledEnum;
import javax.annotation.Nullable;
import static accord.coordinate.Infer.InvalidIf.NotKnownToBeInvalid;
-import static accord.coordinate.Propose.Invalidate.proposeInvalidate;
+import static accord.coordinate.Propose.NotAccept.proposeInvalidate;
import static accord.local.PreLoadContext.contextFor;
-import static accord.primitives.Status.Accepted;
+import static accord.primitives.Status.AcceptedMedium;
import static accord.primitives.Status.PreAccepted;
import static accord.primitives.ProgressToken.INVALIDATED;
import static accord.primitives.ProgressToken.TRUNCATED_DURABLE_OR_INVALIDATED;
@@ -59,8 +60,7 @@ public class Invalidate implements Callback<InvalidateReply>
private boolean isDone;
private boolean isPrepareDone;
private final boolean transitivelyInvokedByPriorInvalidation;
- private final InvalidateReply[] replies;
- private final Topology topology;
+ private final SortedListMap<Id, InvalidateReply> replies;
private final InvalidationTracker tracker;
private Throwable failure;
private final long reportLowEpoch, reportHighEpoch;
@@ -77,9 +77,8 @@ public class Invalidate implements Callback<InvalidateReply>
this.reportHighEpoch = reportHighEpoch;
Topologies topologies = node.topology().forEpoch(invalidateWith, txnId.epoch());
Invariants.checkState(topologies.size() == 1);
- this.tracker = new InvalidationTracker(topologies);
- this.topology = topologies.current();
- this.replies = new InvalidateReply[topology.nodes().size()];
+ this.tracker = new InvalidationTracker(topologies, txnId);
+ this.replies = new SortedListMap<>(topologies.nodes(), InvalidateReply[]::new);
}
public static Invalidate invalidate(Node node, TxnId txnId, Participants<?> invalidateWith, BiConsumer<Outcome, Throwable> callback)
@@ -92,10 +91,10 @@ public class Invalidate implements Callback<InvalidateReply>
return invalidate(node, txnId, invalidateWith, transitivelyInvokedByPriorInvalidation, txnId.epoch(), txnId.epoch(), callback);
}
- public static Invalidate invalidate(Node node, TxnId txnId, Participants<?> invalidateWith, boolean transitivelyInvokedByPriorInvalidation, long commitLocalLowEpoch, long commitLocalHighEpoch, BiConsumer<Outcome, Throwable> callback)
+ public static Invalidate invalidate(Node node, TxnId txnId, Participants<?> invalidateWith, boolean transitivelyInvokedByPriorInvalidation, long reportLowEpoch, long reportHighEpoch, BiConsumer<Outcome, Throwable> callback)
{
Ballot ballot = new Ballot(node.uniqueNow());
- Invalidate invalidate = new Invalidate(node, ballot, txnId, invalidateWith, transitivelyInvokedByPriorInvalidation, commitLocalLowEpoch, commitLocalHighEpoch, callback);
+ Invalidate invalidate = new Invalidate(node, ballot, txnId, invalidateWith, transitivelyInvokedByPriorInvalidation, reportLowEpoch, reportHighEpoch, callback);
invalidate.start();
return invalidate;
}
@@ -113,7 +112,7 @@ public class Invalidate implements Callback<InvalidateReply>
if (isDone || isPrepareDone)
return;
- replies[topology.nodes().find(from)] = reply;
+ replies.put(from, reply);
Participants<?> truncated = reply.truncated;
Participants<?> notTruncated = truncated == null ? invalidateWith : invalidateWith.without(truncated);
@@ -163,19 +162,19 @@ public class Invalidate implements Callback<InvalidateReply>
Invariants.checkState(!isPrepareDone);
isPrepareDone = true;
- FullRoute<?> fullRoute = InvalidateReply.findRoute(replies);
- Route<?> someRoute = InvalidateReply.mergeRoutes(replies);
+ FullRoute<?> fullRoute = InvalidateReply.findRoute(replies.unsafeValuesBackingArray());
+ Route<?> someRoute = InvalidateReply.mergeRoutes(replies.unsafeValuesBackingArray());
// first look to see if it has already been decided/invalidated
// check each shard independently - if we find any that can be invalidated, do so
- InvalidateReply max = InvalidateReply.max(replies);
- InvalidateReply maxNotTruncated = !max.maxKnowledgeStatus.is(Status.Truncated) ? max : InvalidateReply.maxNotTruncated(replies);
+ InvalidateReply max = InvalidateReply.max(replies.unsafeValuesBackingArray());
+ InvalidateReply maxNotTruncated = !max.maxKnowledgeStatus.is(Status.Truncated) ? max : InvalidateReply.maxNotTruncated(replies.unsafeValuesBackingArray());
if (maxNotTruncated != null)
{
switch (maxNotTruncated.maxKnowledgeStatus.status)
{
- default: throw new AssertionError("Unhandled status: " + maxNotTruncated.maxKnowledgeStatus.status);
+ default: throw new UnhandledEnum(maxNotTruncated.maxKnowledgeStatus.status);
case Truncated: throw illegalState();
case AcceptedInvalidate:
@@ -194,7 +193,8 @@ public class Invalidate implements Callback<InvalidateReply>
case PreCommitted:
Invariants.checkState(maxNotTruncated.maxKnowledgeStatus.status == PreAccepted || !invalidateWith.contains(someRoute.homeKey()) || fullRoute != null);
- case Accepted:
+ case AcceptedMedium:
+ case AcceptedSlow:
// TODO (desired, efficiency): if we see Committed or above, go straight to Execute if we have assembled enough information
Invariants.checkState(fullRoute != null, "Received a reply from a node that must have known some route, but that did not include it"); // we now require the FullRoute on all replicas to preaccept, commit or apply
// The data we see might have made it only to a minority in the event of PreAccept ONLY.
@@ -210,7 +210,7 @@ public class Invalidate implements Callback<InvalidateReply>
// Note that there's lots of scope for variations in behaviour here, but lots of care is needed.
Status witnessedByInvalidation = maxNotTruncated.maxKnowledgeStatus.status;
- if (!witnessedByInvalidation.hasBeen(Accepted))
+ if (!witnessedByInvalidation.hasBeen(AcceptedMedium))
{
Invariants.checkState(tracker.all(InvalidationShardTracker::isPromised));
if (!invalidateWith.containsAll(fullRoute))
@@ -287,7 +287,7 @@ public class Invalidate implements Callback<InvalidateReply>
@SuppressWarnings({"unchecked", "rawtypes"})
private void commitInvalidate()
{
- @Nullable Route<?> route = InvalidateReply.mergeRoutes(replies);
+ @Nullable Route<?> route = InvalidateReply.mergeRoutes(replies.unsafeValuesBackingArray());
if (route == null && Route.isRoute(invalidateWith)) route = Route.castToRoute(invalidateWith);
if (route != null) route = route.withHomeKey();
@@ -304,7 +304,7 @@ public class Invalidate implements Callback<InvalidateReply>
// TODO (desired): when sending to network, register a callback for when local application of commitInvalidate message ahs been performed, so no need to special-case
node.forEachLocal(contextFor(txnId), commitTo, lowEpoch, highEpoch, safeStore -> {
// TODO (expected): consid
- StoreParticipants participants = StoreParticipants.invalidate(safeStore, commitTo, txnId);
+ StoreParticipants participants = StoreParticipants.notAccept(safeStore, commitTo, txnId);
Commands.commitInvalidate(safeStore, safeStore.get(txnId, participants), commitTo);
}).begin((s, f) -> {
callback.accept(INVALIDATED, null);
@@ -314,12 +314,12 @@ public class Invalidate implements Callback<InvalidateReply>
}
@Override
- public void onCallbackFailure(Id from, Throwable failure)
+ public boolean onCallbackFailure(Id from, Throwable failure)
{
- if (isDone)
- return;
+ if (isDone) return false;
isDone = true;
callback.accept(null, failure);
+ return true;
}
}
diff --git a/accord-core/src/main/java/accord/coordinate/MaybeRecover.java b/accord-core/src/main/java/accord/coordinate/MaybeRecover.java
index ee455bb4..dcd880c0 100644
--- a/accord-core/src/main/java/accord/coordinate/MaybeRecover.java
+++ b/accord-core/src/main/java/accord/coordinate/MaybeRecover.java
@@ -28,6 +28,7 @@ import accord.utils.Invariants;
import accord.local.Node;
import accord.messages.CheckStatus.CheckStatusOk;
import accord.messages.CheckStatus.IncludeInfo;
+import accord.utils.UnhandledEnum;
import static accord.coordinate.Infer.InvalidateAndCallback.locallyInvalidateAndCallback;
@@ -88,9 +89,9 @@ public class MaybeRecover extends CheckShards<Route<?>>
Known known = full.maxKnown();
Route<?> someRoute = full.route;
- switch (known.outcome)
+ switch (known.outcome())
{
- default: throw new AssertionError();
+ default: throw new UnhandledEnum(known.outcome());
case Unknown:
// ErasedOrInvalidated takes Unknown, and so permits invalidation to be initiated.
// This might prima facie seem unsafe, as ErasedOrInvalidated might mean the command
@@ -132,9 +133,9 @@ public class MaybeRecover extends CheckShards<Route<?>>
callback.accept(full.toProgressToken(), null);
break;
- case Invalidated:
+ case Abort:
Commit.Invalidate.commitInvalidate(node, txnId, Route.merge(full.route, (Route)route), txnId.epoch());
- locallyInvalidateAndCallback(node, txnId, txnId, txnId, someRoute, full.toProgressToken(), callback);
+ locallyInvalidateAndCallback(node, txnId, txnId.epoch(), txnId.epoch(), someRoute, full.toProgressToken(), callback);
break;
}
}
diff --git a/accord-core/src/main/java/accord/coordinate/Persist.java b/accord-core/src/main/java/accord/coordinate/Persist.java
index 98d452b5..5f754413 100644
--- a/accord-core/src/main/java/accord/coordinate/Persist.java
+++ b/accord-core/src/main/java/accord/coordinate/Persist.java
@@ -20,6 +20,7 @@ package accord.coordinate;
import accord.api.Result;
import accord.coordinate.tracking.QuorumTracker;
+import accord.local.CommandStore;
import accord.local.Node;
import accord.local.Node.Id;
import accord.messages.Apply;
@@ -34,6 +35,7 @@ import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.primitives.Writes;
import accord.topology.Topologies;
+import accord.utils.Invariants;
import accord.utils.SortedArrays;
import static accord.coordinate.tracking.RequestStatus.Success;
@@ -68,6 +70,7 @@ public abstract class Persist implements Callback<ApplyReply>
this.route = route;
this.topologies = all;
this.tracker = new QuorumTracker(all);
+ Invariants.checkState((writes != null) == txnId.is(Txn.Kind.Write), "%s: writes %s", txnId, writes);
}
@Override
@@ -100,9 +103,10 @@ public abstract class Persist implements Callback<ApplyReply>
}
@Override
- public void onCallbackFailure(Id from, Throwable failure)
+ public boolean onCallbackFailure(Id from, Throwable failure)
{
// TODO (expected): handle exception
+ return false;
}
public void start(Apply.Factory factory, Apply.Kind kind, Topologies all, Writes writes, Result result)
@@ -116,7 +120,8 @@ public abstract class Persist implements Callback<ApplyReply>
}
else
{
- node.send(contact, to -> factory.create(kind, to, all, txnId, sendTo, txn, executeAt, stableDeps, writes, result, route), this);
+ CommandStore commandStore = CommandStore.currentOrElseSelect(node, route);
+ node.send(contact, to -> factory.create(kind, to, all, txnId, sendTo, txn, executeAt, stableDeps, writes, result, route), commandStore, this);
}
}
}
diff --git a/accord-core/src/main/java/accord/coordinate/Propose.java b/accord-core/src/main/java/accord/coordinate/Propose.java
index 74eec97e..8c4f433c 100644
--- a/accord-core/src/main/java/accord/coordinate/Propose.java
+++ b/accord-core/src/main/java/accord/coordinate/Propose.java
@@ -21,11 +21,10 @@ package accord.coordinate;
import java.util.Map;
import java.util.function.BiConsumer;
+import accord.api.ProtocolModifiers.Faults;
import accord.api.RoutingKey;
-import accord.coordinate.tracking.AbstractTracker.ShardOutcomes;
import accord.coordinate.tracking.QuorumTracker;
-import accord.coordinate.tracking.QuorumTracker.QuorumShardTracker;
-import accord.coordinate.tracking.RequestStatus;
+import accord.coordinate.tracking.SimpleTracker;
import accord.local.Node;
import accord.local.Node.Id;
import accord.messages.Accept;
@@ -34,25 +33,33 @@ import accord.messages.Callback;
import accord.primitives.Ballot;
import accord.primitives.Deps;
import accord.primitives.FullRoute;
+import accord.primitives.Participants;
import accord.primitives.Route;
+import accord.primitives.Status;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
-import accord.topology.Shard;
import accord.topology.Topologies;
import accord.utils.Invariants;
import accord.utils.SortedArrays;
import accord.utils.SortedListMap;
import accord.utils.WrappableException;
-import static accord.coordinate.tracking.AbstractTracker.ShardOutcomes.Fail;
+import static accord.api.ProtocolModifiers.Toggles.filterDuplicateDependenciesFromAcceptReply;
+import static accord.coordinate.ExecutePath.MEDIUM;
import static accord.coordinate.tracking.RequestStatus.Failed;
+import static accord.coordinate.tracking.RequestStatus.Success;
import static accord.messages.Commit.Invalidate.commitInvalidate;
+import static accord.primitives.Status.AcceptedInvalidate;
+import static accord.primitives.Status.NotAccepted;
+import static accord.primitives.Status.PreNotAccepted;
+import static accord.primitives.TxnId.MediumPath.MEDIUM_PATH_TRACK_STABLE;
import static accord.utils.Invariants.debug;
abstract class Propose<R> implements Callback<AcceptReply>
{
final Node node;
+ final Accept.Kind kind;
final Ballot ballot;
final TxnId txnId;
final Txn txn;
@@ -65,9 +72,10 @@ abstract class Propose<R> implements Callback<AcceptReply>
final BiConsumer<? super R, Throwable> callback;
private boolean isDone;
- Propose(Node node, Topologies topologies, Ballot ballot, TxnId txnId, Txn txn, FullRoute<?> route, Timestamp executeAt, Deps deps, BiConsumer<? super R, Throwable> callback)
+ Propose(Node node, Topologies topologies, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, FullRoute<?> route, Timestamp executeAt, Deps deps, BiConsumer<? super R, Throwable> callback)
{
this.node = node;
+ this.kind = kind;
this.ballot = ballot;
this.txnId = txnId;
this.txn = txn;
@@ -85,7 +93,7 @@ abstract class Propose<R> implements Callback<AcceptReply>
{
SortedArrays.SortedArrayList<Node.Id> contact = acceptTracker.filterAndRecordFaulty();
if (contact == null) callback.accept(null, new Timeout(null, null));
- else node.send(contact, to -> new Accept(to, acceptTracker.topologies(), ballot, txnId, route, executeAt, deps), this);
+ else node.send(contact, to -> new Accept(to, acceptTracker.topologies(), kind, ballot, txnId, route, executeAt, deps), this);
}
@Override
@@ -124,7 +132,7 @@ abstract class Propose<R> implements Callback<AcceptReply>
case Retired:
case Success:
acceptOks.put(from, reply);
- if (acceptTracker.recordSuccess(from) == RequestStatus.Success)
+ if (acceptTracker.recordSuccess(from) == Success)
{
isDone = true;
onAccepted();
@@ -147,50 +155,88 @@ abstract class Propose<R> implements Callback<AcceptReply>
}
@Override
- public void onCallbackFailure(Id from, Throwable failure)
+ public boolean onCallbackFailure(Id from, Throwable failure)
{
if (isDone)
- return;
+ return false;
isDone = true;
callback.accept(null, failure);
+ return true;
+ }
+
+ void onAccepted()
+ {
+ if (kind == Accept.Kind.MEDIUM)
+ {
+ adapter().execute(node, acceptTracker.topologies(), route, MEDIUM, txnId, txn, executeAt, deps, callback);
+ }
+ else
+ {
+ adapter().stabilise(node, acceptTracker.topologies(), route, ballot, txnId, txn, executeAt, mergeDeps(), callback);
+ }
+ }
+
+ Deps mergeDeps()
+ {
+ Deps deps = Deps.merge(acceptOks, acceptOks.domainSize(), SortedListMap::getValue, ok -> ok.deps);
+ if (Faults.discardPreAcceptDeps(txnId))
+ return deps;
+
+ if (txnId.is(MEDIUM_PATH_TRACK_STABLE))
+ {
+ if (!filterDuplicateDependenciesFromAcceptReply())
+ deps = deps.without(this.deps);
+
+ deps = deps.markUnstableBefore(txnId);
+ }
+
+ return deps.with(this.deps);
}
- abstract void onAccepted();
+ abstract CoordinationAdapter<R> adapter();
// A special version for proposing the invalidation of a transaction; only needs to succeed on one shard
- static class Invalidate implements Callback<AcceptReply>
+ static class NotAccept implements Callback<AcceptReply>
{
final Node node;
+ final Status status;
final Ballot ballot;
final TxnId txnId;
- final RoutingKey someParticipant;
+ final Participants<?> someParticipants;
final BiConsumer<Void, Throwable> callback;
final Map<Id, AcceptReply> debug;
- private final QuorumShardTracker acceptTracker;
+ private final SimpleTracker<?> acceptTracker;
private boolean isDone;
- Invalidate(Node node, Shard shard, Ballot ballot, TxnId txnId, RoutingKey someParticipant, BiConsumer<Void, Throwable> callback)
+ NotAccept(Node node, Status status, Topologies topologies, Ballot ballot, TxnId txnId, Participants<?> someParticipants, BiConsumer<Void, Throwable> callback)
{
this.node = node;
- this.acceptTracker = new QuorumShardTracker(shard);
+ this.status = status;
+ this.acceptTracker = new QuorumTracker(topologies);
this.ballot = ballot;
this.txnId = txnId;
- this.someParticipant = someParticipant;
+ this.someParticipants = someParticipants;
this.callback = callback;
- this.debug = debug() ? new SortedListMap<>(shard.nodes, AcceptReply[]::new) : null;
+ this.debug = debug() ? new SortedListMap<>(topologies.nodes(), AcceptReply[]::new) : null;
}
- public static Invalidate proposeInvalidate(Node node, Ballot ballot, TxnId txnId, RoutingKey invalidateWithParticipant, BiConsumer<Void, Throwable> callback)
+ public static NotAccept proposeInvalidate(Node node, Ballot ballot, TxnId txnId, RoutingKey invalidateWithParticipant, BiConsumer<Void, Throwable> callback)
{
- Shard shard = node.topology().forEpochIfKnown(invalidateWithParticipant, txnId.epoch());
- Invalidate invalidate = new Invalidate(node, shard, ballot, txnId, invalidateWithParticipant, callback);
- node.send(shard.nodes, to -> new Accept.Invalidate(ballot, txnId, invalidateWithParticipant), invalidate);
- return invalidate;
+ return proposeNotAccept(node, AcceptedInvalidate, ballot, txnId, invalidateWithParticipant, callback);
}
- public static Invalidate proposeAndCommitInvalidate(Node node, Ballot ballot, TxnId txnId, RoutingKey invalidateWithParticipant, Route<?> commitInvalidationTo, Timestamp invalidateUntil, BiConsumer<?, Throwable> callback)
+ public static NotAccept proposeNotAccept(Node node, Status status, Ballot ballot, TxnId txnId, RoutingKey participatingKey, BiConsumer<Void, Throwable> callback)
+ {
+ Participants<?> participants = Participants.singleton(txnId.domain(), participatingKey);
+ Topologies topologies = node.topology().forEpoch(participants, txnId.epoch());
+ NotAccept notAccept = new NotAccept(node, status, topologies, ballot, txnId, participants, callback);
+ node.send(topologies.nodes(), to -> new Accept.NotAccept(status, ballot, txnId, participants), notAccept);
+ return notAccept;
+ }
+
+ public static NotAccept proposeAndCommitInvalidate(Node node, Ballot ballot, TxnId txnId, RoutingKey invalidateWithParticipant, Route<?> commitInvalidationTo, Timestamp invalidateUntil, BiConsumer<?, Throwable> callback)
{
return proposeInvalidate(node, ballot, txnId, invalidateWithParticipant, (success, fail) -> {
if (fail != null)
@@ -207,6 +253,24 @@ abstract class Propose<R> implements Callback<AcceptReply>
});
}
+ public static NotAccept proposeAndPersistNotAccept(Node node, Ballot ballot, TxnId txnId, RoutingKey participatingKey, Route<?> commitNotAcceptTo, BiConsumer<?, Throwable> callback)
+ {
+ return proposeNotAccept(node, PreNotAccepted, ballot, txnId, participatingKey, (success, fail) -> {
+ if (fail != null)
+ {
+ if (callback != null)
+ callback.accept(null, fail);
+ }
+ else
+ {
+ Topologies topologies = node.topology().forEpoch(commitNotAcceptTo, txnId.epoch());
+ node.send(topologies.nodes(), to -> new Accept.NotAccept(NotAccepted, ballot, txnId, commitNotAcceptTo));
+ if (callback != null)
+ callback.accept(null, null);
+ }
+ });
+ }
+
@Override
public void onSuccess(Id from, AcceptReply reply)
{
@@ -222,7 +286,7 @@ abstract class Propose<R> implements Callback<AcceptReply>
return;
}
- if (acceptTracker.onSuccess(from) == ShardOutcomes.Success)
+ if (acceptTracker.recordSuccess(from) == Success)
{
isDone = true;
callback.accept(null, null);
@@ -235,7 +299,7 @@ abstract class Propose<R> implements Callback<AcceptReply>
if (isDone)
return;
- if (acceptTracker.onFailure(from) == Fail)
+ if (acceptTracker.recordFailure(from) == Failed)
{
isDone = true;
callback.accept(null, new Timeout(txnId, null));
@@ -243,13 +307,13 @@ abstract class Propose<R> implements Callback<AcceptReply>
}
@Override
- public void onCallbackFailure(Id from, Throwable failure)
+ public boolean onCallbackFailure(Id from, Throwable failure)
{
- if (isDone)
- return;
+ if (isDone) return false;
isDone = true;
callback.accept(null, failure);
+ return true;
}
}
}
diff --git a/accord-core/src/main/java/accord/coordinate/ProposeSyncPoint.java b/accord-core/src/main/java/accord/coordinate/ProposeSyncPoint.java
index 368531cb..54c99265 100644
--- a/accord-core/src/main/java/accord/coordinate/ProposeSyncPoint.java
+++ b/accord-core/src/main/java/accord/coordinate/ProposeSyncPoint.java
@@ -24,6 +24,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import accord.local.Node;
+import accord.messages.Accept;
import accord.primitives.Ballot;
import accord.primitives.Deps;
import accord.primitives.FullRoute;
@@ -31,9 +32,6 @@ import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.topology.Topologies;
-import accord.utils.SortedListMap;
-
-import static accord.api.ProtocolModifiers.Faults;
public class ProposeSyncPoint<R> extends Propose<R>
{
@@ -41,16 +39,15 @@ public class ProposeSyncPoint<R> extends Propose<R>
private static final Logger logger = LoggerFactory.getLogger(ProposeSyncPoint.class);
private final CoordinationAdapter<R> adapter;
- ProposeSyncPoint(CoordinationAdapter<R> adapter, Node node, Topologies topologies, FullRoute<?> route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super R, Throwable> callback)
+ ProposeSyncPoint(CoordinationAdapter<R> adapter, Node node, Topologies topologies, FullRoute<?> route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super R, Throwable> callback)
{
- super(node, topologies, ballot, txnId, txn, route, executeAt, deps, callback);
+ super(node, topologies, kind, ballot, txnId, txn, route, executeAt, deps, callback);
this.adapter = adapter;
}
@Override
- void onAccepted()
+ CoordinationAdapter<R> adapter()
{
- Deps deps = Faults.syncPointDiscardPreAcceptDeps ? this.deps : this.deps.with(Deps.merge(acceptOks, acceptOks.domainSize(), SortedListMap::getValue, ok -> ok.deps));
- adapter.stabilise(node, acceptTracker.topologies(), route, ballot, txnId, txn, executeAt, deps, callback);
+ return adapter;
}
}
diff --git a/accord-core/src/main/java/accord/coordinate/ProposeTxn.java b/accord-core/src/main/java/accord/coordinate/ProposeTxn.java
index 05eaf86c..999ba413 100644
--- a/accord-core/src/main/java/accord/coordinate/ProposeTxn.java
+++ b/accord-core/src/main/java/accord/coordinate/ProposeTxn.java
@@ -22,6 +22,7 @@ import java.util.function.BiConsumer;
import accord.api.Result;
import accord.local.Node;
+import accord.messages.Accept;
import accord.primitives.Ballot;
import accord.primitives.Deps;
import accord.primitives.FullRoute;
@@ -29,26 +30,14 @@ import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.topology.Topologies;
-import accord.utils.SortedListMap;
-import static accord.api.ProtocolModifiers.Faults;
import static accord.coordinate.CoordinationAdapter.Factory.Kind.Standard;
class ProposeTxn extends Propose<Result>
{
- ProposeTxn(Node node, Topologies topologies, FullRoute<?> route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super Result, Throwable> callback)
+ ProposeTxn(Node node, Topologies topologies, FullRoute<?> route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super Result, Throwable> callback)
{
- super(node, topologies, ballot, txnId, txn, route, executeAt, deps, callback);
- }
-
- @Override
- void onAccepted()
- {
- Deps deps = Deps.merge(acceptOks, acceptOks.domainSize(), SortedListMap::getValue, ok -> ok.deps);
- if (!Faults.txnDiscardPreAcceptDeps)
- deps = deps.with(this.deps);
-
- adapter().stabilise(node, acceptTracker.topologies(), route, ballot, txnId, txn, executeAt, deps, callback);
+ super(node, topologies, kind, ballot, txnId, txn, route, executeAt, deps, callback);
}
protected CoordinationAdapter<Result> adapter()
diff --git a/accord-core/src/main/java/accord/coordinate/ReadCoordinator.java b/accord-core/src/main/java/accord/coordinate/ReadCoordinator.java
index 99ee9b8f..2a5d199c 100644
--- a/accord-core/src/main/java/accord/coordinate/ReadCoordinator.java
+++ b/accord-core/src/main/java/accord/coordinate/ReadCoordinator.java
@@ -177,16 +177,15 @@ public abstract class ReadCoordinator<Reply extends accord.messages.Reply> exten
}
@Override
- public void onCallbackFailure(Id from, Throwable failure)
+ public boolean onCallbackFailure(Id from, Throwable failure)
{
- node.agent().onUncaughtException(failure);
- if (isDone)
- return;
+ if (isDone) return false;
if (this.failure != null)
failure.addSuppressed(this.failure);
this.failure = failure;
finishOnFailure();
+ return true;
}
protected void finishOnFailure(Throwable failure, boolean overrideExistingFailure)
@@ -261,19 +260,18 @@ public abstract class ReadCoordinator<Reply extends accord.messages.Reply> exten
to.forEach(this::contact);
}
- public void start()
+ public final void start()
{
- if (!initialise())
- {
- finishOnExhaustion();
- }
- else
- {
- List<Id> contact = new ArrayList<>(maxShardsPerEpoch());
- if (trySendMore(List::add, contact) != RequestStatus.NoChange)
- throw new IllegalStateException();
- start(contact);
- }
+ if (initialise()) startOnceInitialised();
+ else finishOnExhaustion();
+ }
+
+ protected void startOnceInitialised()
+ {
+ List<Id> contact = new ArrayList<>(maxShardsPerEpoch());
+ if (trySendMore(List::add, contact) != RequestStatus.NoChange)
+ throw new IllegalStateException();
+ start(contact);
}
@Override
diff --git a/accord-core/src/main/java/accord/coordinate/Recover.java b/accord-core/src/main/java/accord/coordinate/Recover.java
index 3cf0a90f..1a0f3fac 100644
--- a/accord-core/src/main/java/accord/coordinate/Recover.java
+++ b/accord-core/src/main/java/accord/coordinate/Recover.java
@@ -18,9 +18,9 @@
package accord.coordinate;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -31,6 +31,7 @@ import accord.api.Result;
import accord.coordinate.tracking.RecoveryTracker;
import accord.local.Node;
import accord.local.Node.Id;
+import accord.messages.Accept;
import accord.primitives.Known.KnownDeps;
import accord.primitives.Status;
import accord.messages.BeginRecovery;
@@ -53,51 +54,54 @@ import accord.topology.Topologies;
import accord.topology.Topology;
import accord.utils.Invariants;
import accord.utils.SortedListMap;
+import accord.utils.UnhandledEnum;
import accord.utils.WrappableException;
+import accord.utils.async.AsyncChainCombiner;
import accord.utils.async.AsyncResult;
import accord.utils.async.AsyncResults;
+import static accord.api.ProgressLog.BlockedUntil.CommittedOrNotAccepted;
+import static accord.api.ProgressLog.BlockedUntil.HasCommittedDeps;
import static accord.api.ProtocolModifiers.QuorumEpochIntersections;
import static accord.coordinate.CoordinationAdapter.Factory.Kind.Recovery;
import static accord.coordinate.ExecutePath.RECOVER;
import static accord.coordinate.Infer.InvalidateAndCallback.locallyInvalidateAndCallback;
-import static accord.coordinate.Propose.Invalidate.proposeInvalidate;
+import static accord.coordinate.Propose.NotAccept.proposeInvalidate;
import static accord.coordinate.tracking.RequestStatus.Failed;
import static accord.coordinate.tracking.RequestStatus.Success;
+import static accord.messages.Accept.Kind.MEDIUM;
+import static accord.messages.Accept.Kind.SLOW;
import static accord.messages.BeginRecovery.RecoverOk.maxAccepted;
import static accord.messages.BeginRecovery.RecoverOk.maxAcceptedNotTruncated;
import static accord.primitives.Known.KnownDeps.DepsCommitted;
import static accord.primitives.Known.KnownDeps.DepsKnown;
import static accord.primitives.Known.KnownDeps.DepsUnknown;
import static accord.primitives.ProgressToken.TRUNCATED_DURABLE_OR_INVALIDATED;
+import static accord.primitives.Status.AcceptedMedium;
+import static accord.primitives.TxnId.MediumPath.MEDIUM_PATH_WAIT_ON_RECOVERY;
import static accord.utils.Invariants.illegalState;
-// TODO (low priority, cleanup): rename to Recover (verb); rename Recover message to not clash
-// TODO (expected): do not wait for a quorum if we find enough information to execute
public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throwable>
{
- // TODO (required): make sure we contact and wait (at least until timeout)
- // for an ACK from any replica that witnessed a txn as something to wait for
- AsyncResult<Object> awaitCommits(Node node, Deps waitOn)
+ AsyncResult<Boolean> await(Node node, Deps waitOn, BlockedUntil blockedUntil)
{
- AtomicInteger remaining = new AtomicInteger(waitOn.txnIdCount());
- AsyncResult.Settable<Object> result = AsyncResults.settable();
- for (int i = 0 ; i < waitOn.txnIdCount() ; ++i)
- {
- TxnId txnId = waitOn.txnId(i);
- Participants<?> participants = waitOn.participants(txnId);
- long sinceEpoch = Math.min(txnId.epoch(), Recover.this.txnId.epoch());
- Topologies topologies = this.tracker.topologies().select(participants, sinceEpoch);
- SynchronousAwait.awaitQuorum(node, topologies, txnId, route.homeKey(), BlockedUntil.HasCommittedDeps, participants).addCallback((success, failure) -> {
- if (result.isDone())
- return;
- if (failure == null && remaining.decrementAndGet() == 0)
- result.setSuccess(success);
- else
- result.tryFailure(failure);
- });
- }
- return result;
+ long requireEpoch = waitOn.maxTxnId(txnId).epoch();
+ return node.withEpoch(requireEpoch, () -> {
+ TxnId recoverId = this.txnId;
+ List<AsyncResult<Boolean>> requests = new ArrayList<>();
+ for (int i = 0 ; i < waitOn.txnIdCount() ; ++i)
+ {
+ TxnId awaitId = waitOn.txnId(i);
+ Participants<?> participants = waitOn.participants(awaitId);
+ Topologies topologies;
+ if (tracker.topologies().containsEpoch(awaitId.epoch())) topologies = tracker.topologies().selectEpoch(participants, awaitId.epoch());
+ else topologies = node.topology().forEpoch(participants, awaitId.epoch());
+ requests.add(SynchronousRecoverAwait.awaitAny(node, topologies, awaitId, blockedUntil, true, participants, recoverId));
+ }
+ if (requests.isEmpty())
+ return AsyncResults.success(false);
+ return AsyncChainCombiner.reduce(requests, (a,b) -> a|b).beginAsResult();
+ }).beginAsResult();
}
private final CoordinationAdapter<Result> adapter;
@@ -107,6 +111,7 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
private final Txn txn;
private final FullRoute<?> route;
private final @Nullable Timestamp committedExecuteAt;
+ private final long reportLowEpoch, reportHighEpoch;
private final BiConsumer<Outcome, Throwable> callback;
private boolean isDone;
@@ -114,9 +119,13 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
private final RecoveryTracker tracker;
private boolean isBallotPromised;
- private Recover(Node node, Topologies topologies, Ballot ballot, TxnId txnId, Txn txn, FullRoute<?> route, @Nullable Timestamp committedExecuteAt, BiConsumer<Outcome, Throwable> callback)
+ private Recover(Node node, Topologies topologies, Ballot ballot, TxnId txnId, Txn txn, FullRoute<?> route,
+ @Nullable Timestamp committedExecuteAt, long reportLowEpoch, long reportHighEpoch,
+ BiConsumer<Outcome, Throwable> callback)
{
this.committedExecuteAt = committedExecuteAt;
+ this.reportLowEpoch = reportLowEpoch;
+ this.reportHighEpoch = reportHighEpoch;
Invariants.checkState(txnId.isVisible());
this.adapter = node.coordinationAdapter(txnId, Recovery);
this.node = node;
@@ -158,25 +167,40 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
}
public static Recover recover(Node node, TxnId txnId, Txn txn, FullRoute<?> route, BiConsumer<Outcome, Throwable> callback)
+ {
+ return recover(node, txnId, txn, route, txnId.epoch(), txnId.epoch(), callback);
+ }
+
+ public static Recover recover(Node node, TxnId txnId, Txn txn, FullRoute<?> route, long reportLowEpoch, long reportHighEpoch, BiConsumer<Outcome, Throwable> callback)
{
Ballot ballot = new Ballot(node.uniqueNow());
- return recover(node, ballot, txnId, txn, route, callback);
+ return recover(node, ballot, txnId, txn, route, reportLowEpoch, reportHighEpoch, callback);
}
private static Recover recover(Node node, Ballot ballot, TxnId txnId, Txn txn, FullRoute<?> route, BiConsumer<Outcome, Throwable> callback)
{
- return recover(node, ballot, txnId, txn, route, null, callback);
+ return recover(node, ballot, txnId, txn, route, null, txnId.epoch(), txnId.epoch(), callback);
}
- public static Recover recover(Node node, Ballot ballot, TxnId txnId, Txn txn, FullRoute<?> route, Timestamp executeAt, BiConsumer<Outcome, Throwable> callback)
+ private static Recover recover(Node node, Ballot ballot, TxnId txnId, Txn txn, FullRoute<?> route, long reportLowEpoch, long reportHighEpoch, BiConsumer<Outcome, Throwable> callback)
+ {
+ return recover(node, ballot, txnId, txn, route, null, reportLowEpoch, reportHighEpoch, callback);
+ }
+
+ public static Recover recover(Node node, Ballot ballot, TxnId txnId, Txn txn, FullRoute<?> route, @Nullable Timestamp executeAt, BiConsumer<Outcome, Throwable> callback)
+ {
+ return recover(node, ballot, txnId, txn, route, executeAt, txnId.epoch(), (executeAt == null ? txnId : executeAt).epoch(), callback);
+ }
+
+ public static Recover recover(Node node, Ballot ballot, TxnId txnId, Txn txn, FullRoute<?> route, @Nullable Timestamp executeAt, long reportLowEpoch, long reportHighEpoch, BiConsumer<Outcome, Throwable> callback)
{
Topologies topologies = node.topology().select(route, txnId, executeAt == null ? txnId : executeAt, QuorumEpochIntersections.recover);
- return recover(node, topologies, ballot, txnId, txn, route, executeAt, callback);
+ return recover(node, topologies, ballot, txnId, txn, route, executeAt, reportLowEpoch, reportHighEpoch, callback);
}
- private static Recover recover(Node node, Topologies topologies, Ballot ballot, TxnId txnId, Txn txn, FullRoute<?> route, Timestamp executeAt, BiConsumer<Outcome, Throwable> callback)
+ private static Recover recover(Node node, Topologies topologies, Ballot ballot, TxnId txnId, Txn txn, FullRoute<?> route, Timestamp executeAt, long reportLowEpoch, long reportHighEpoch, BiConsumer<Outcome, Throwable> callback)
{
- Recover recover = new Recover(node, topologies, ballot, txnId, txn, route, executeAt, callback);
+ Recover recover = new Recover(node, topologies, ballot, txnId, txn, route, executeAt, reportLowEpoch, reportHighEpoch, callback);
recover.start(topologies.nodes());
return recover;
}
@@ -236,12 +260,12 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
{
Invariants.checkState(acceptOrCommitNotTruncated.status.compareTo(Status.PreCommitted) < 0 || executeAt.equals(committedExecuteAt));
// if we know from a prior Accept attempt that this is committed we can go straight to the commit phase
- if (status == Status.Accepted)
+ if (status == AcceptedMedium)
status = Status.Committed;
}
switch (status)
{
- default: throw new AssertionError("Unhandled Status: " + status);
+ default: throw new UnhandledEnum(status);
case Truncated: throw illegalState("Truncate should be filtered");
case Invalidated:
{
@@ -253,9 +277,6 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
case PreApplied:
{
withStableDeps(recoverOkList, executeAt, (i, t) -> node.agent().acceptAndWrap(i, t), stableDeps -> {
- // TODO (required): if we have to calculate deps for any shard, should we first ensure they are stable?
- // it should only be possible for a fast path decision, but we might have Stable in only one shard.
- // TODO (future development correctness): when writes/result are partially replicated, need to confirm we have quorum of these
adapter.persist(node, tracker.topologies(), route, txnId, txn, executeAt, stableDeps, acceptOrCommitNotTruncated.writes, acceptOrCommitNotTruncated.result, (i, t) -> node.agent().acceptAndWrap(i, t));
});
accept(acceptOrCommitNotTruncated.result, null);
@@ -264,8 +285,6 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
case Stable:
{
- // TODO (required): if we have to calculate deps for any shard, should we first ensure they are stable?
- // it should only be possible for a fast path decision, but we might have Stable in only one shard.
withStableDeps(recoverOkList, executeAt, this, stableDeps -> {
adapter.execute(node, tracker.topologies(), route, RECOVER, txnId, txn, executeAt, stableDeps, this);
});
@@ -281,14 +300,15 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
return;
}
- case Accepted:
+ case AcceptedSlow:
+ case AcceptedMedium:
{
// TODO (desired): if we have a quorum of Accept with matching ballot or proposal we can go straight to Commit
- // TODO (desired, behaviour): if we didn't find Accepted in *every* shard, consider invalidating for consistency of behaviour
+ // TODO (desired): if we didn't find Accepted in *every* shard, consider invalidating for consistency of behaviour
// however, note that we may have taken the fast path and recovered, so we can only do this if acceptedOrCommitted=Ballot.ZERO
// (otherwise recovery was attempted and did not invalidate, so it must have determined it needed to complete)
Deps proposeDeps = LatestDeps.mergeProposal(recoverOkList, ok -> ok == null ? null : ok.deps);
- propose(acceptOrCommitNotTruncated.executeAt, proposeDeps);
+ propose(status == AcceptedMedium && txnId.is(MEDIUM_PATH_WAIT_ON_RECOVERY) ? MEDIUM : SLOW, acceptOrCommitNotTruncated.executeAt, proposeDeps);
return;
}
@@ -298,13 +318,17 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
return;
}
+ case PreNotAccepted:
+ case NotAccepted:
+ break;
+
case NotDefined:
case PreAccepted:
throw illegalState("Should only be possible to have Accepted or later commands");
}
}
- if (acceptOrCommit != null)
+ if (acceptOrCommit != null && acceptOrCommit != acceptOrCommitNotTruncated)
{
// TODO (required): match logic in Invalidate; we need to know which keys have been invalidated
Topologies topologies = tracker.topologies();
@@ -333,7 +357,8 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
}
}
- if (tracker.rejectsFastPath() || supersedingRejects(route, recoverOkList))
+ if (tracker.rejectsFastPath(txnId) || supersedingRejects(recoverOkList)
+ || (txnId.hasPrivilegedCoordinator() && recoverOks.get(txnId.node) != null))
{
invalidate(recoverOks);
return;
@@ -344,25 +369,29 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
Deps proposeDeps = LatestDeps.mergeProposal(recoverOkList, ok -> ok == null ? null : ok.deps);
Deps earlierWait = Deps.merge(recoverOkList, recoverOkList.size(), List::get, ok -> ok.earlierWait);
Deps earlierNoWait = Deps.merge(recoverOkList, recoverOkList.size(), List::get, ok -> ok.earlierNoWait);
+ Deps laterWait = Deps.merge(recoverOkList, recoverOkList.size(), List::get, ok -> ok.laterWait);
earlierWait = earlierWait.without(earlierNoWait);
- if (!earlierWait.isEmpty())
+ if (!earlierWait.isEmpty() || !laterWait.isEmpty())
{
- // If there exist commands that were proposed an earlier execution time than us that have not witnessed us,
+ Propose.NotAccept.proposeAndPersistNotAccept(node, ballot, txnId, route.homeKey(), route, node.agent());
+ // If there exist commands that were proposed a later execution time than us that have not witnessed us,
// we have to be certain these commands have not successfully committed without witnessing us (thereby
// ruling out a fast path decision for us and changing our recovery decision).
- // So, we wait for these commands to finish committing before retrying recovery.
- // See whitepaper for more details
- awaitCommits(node, earlierWait).addCallback((success, failure) -> {
+ // So, we wait for these commands to commit and recompute supersedingRejects for them.
+ AsyncChainCombiner.reduce(await(node, earlierWait, HasCommittedDeps),
+ await(node, laterWait, CommittedOrNotAccepted), (a, b) -> a|b)
+ .begin((rejects, failure) -> {
if (failure != null) accept(null, failure);
- else retry();
+ else if (rejects) invalidate(recoverOks);
+ else propose(SLOW, txnId, proposeDeps);
});
return;
}
- propose(txnId, proposeDeps);
+ propose(SLOW, txnId, proposeDeps);
}
- private static boolean supersedingRejects(FullRoute<?> route, List<RecoverOk> oks)
+ private static boolean supersedingRejects(List<RecoverOk> oks)
{
for (RecoverOk ok : oks)
{
@@ -384,7 +413,7 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
private void withCommittedOrStableDeps(KnownDeps atLeast, List<RecoverOk> nullableRecoverOkList, Timestamp executeAt, BiConsumer<?, Throwable> failureCallback, Consumer<Deps> withDeps)
{
- LatestDeps.MergedCommitResult merged = LatestDeps.mergeCommit(atLeast, txnId, executeAt, nullableRecoverOkList, txnId, ok -> ok == null ? null : ok.deps);
+ LatestDeps.MergedCommitResult merged = LatestDeps.mergeCommit(atLeast, executeAt, nullableRecoverOkList, txnId, ok -> ok == null ? null : ok.deps);
node.withEpoch(executeAt.epoch(), failureCallback, () -> {
Unseekables<?> missing = route.without(merged.sufficientFor);
if (missing.isEmpty())
@@ -400,7 +429,7 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
}
else
{
- withDeps.accept(merged.deps.with(LatestDeps.mergeCommit(DepsUnknown, txnId, executeAt, extraDeps, executeAt, i -> i).deps));
+ withDeps.accept(merged.deps.with(LatestDeps.mergeCommit(DepsUnknown, executeAt, extraDeps, executeAt, i -> i).deps));
}
});
}
@@ -421,37 +450,33 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
// If not accepted then the executeAt is not consistent cross the peers and likely different on every node. There is also an edge case
// when ranges are removed from the topology, during this case the executeAt won't know the ranges and the invalidate commit will fail.
return recoverOks.valuesAsNullableStream()
- .map(ok -> ok == null ? null : ok.status.hasBeen(Status.Accepted) ? ok.executeAt : ok.txnId)
+ .map(ok -> ok == null ? null : ok.status.hasBeen(AcceptedMedium) ? ok.executeAt : ok.txnId)
.reduce(txnId, Timestamp::nonNullOrMax);
}
private void commitInvalidate(Timestamp invalidateUntil)
{
- node.withEpoch(invalidateUntil.epoch(), node.agent(), () -> {
+ long highEpoch = Math.max(invalidateUntil.epoch(), reportHighEpoch);
+ node.withEpoch(highEpoch, node.agent(), () -> {
Commit.Invalidate.commitInvalidate(node, txnId, route, invalidateUntil);
});
isDone = true;
- locallyInvalidateAndCallback(node, txnId, txnId, invalidateUntil, route, ProgressToken.INVALIDATED, callback);
+ locallyInvalidateAndCallback(node, txnId, reportLowEpoch, highEpoch, route, ProgressToken.INVALIDATED, callback);
}
- private void propose(Timestamp executeAt, Deps deps)
+ private void propose(Accept.Kind kind, Timestamp executeAt, Deps deps)
{
node.withEpoch(executeAt.epoch(), this, () -> {
- adapter.propose(node, null, route, ballot, txnId, txn, executeAt, deps, this);
+ adapter.propose(node, null, route, kind, ballot, txnId, txn, executeAt, deps, this);
});
}
- private void retry()
- {
- retry(committedExecuteAt);
- }
-
private void retry(Timestamp executeAt)
{
Topologies topologies = tracker.topologies();
if (executeAt != null && executeAt.epoch() != (this.committedExecuteAt == null ? txnId : this.committedExecuteAt).epoch())
topologies = node.topology().select(route, txnId, executeAt, QuorumEpochIntersections.recover);
- Recover.recover(node, topologies, ballot, txnId, txn, route, executeAt, callback);
+ Recover.recover(node, topologies, ballot, txnId, txn, route, executeAt, reportLowEpoch, reportHighEpoch, callback);
}
@Override
@@ -465,9 +490,10 @@ public class Recover implements Callback<RecoverReply>, BiConsumer<Result, Throw
}
@Override
- public void onCallbackFailure(Id from, Throwable failure)
+ public boolean onCallbackFailure(Id from, Throwable failure)
{
+ if (isDone) return false;
accept(null, failure);
- node.agent().onUncaughtException(failure);
+ return true;
}
}
diff --git a/accord-core/src/main/java/accord/coordinate/RecoverWithRoute.java b/accord-core/src/main/java/accord/coordinate/RecoverWithRoute.java
index 90fb195f..ca899df6 100644
--- a/accord-core/src/main/java/accord/coordinate/RecoverWithRoute.java
+++ b/accord-core/src/main/java/accord/coordinate/RecoverWithRoute.java
@@ -61,6 +61,7 @@ public class RecoverWithRoute extends CheckShards<FullRoute<?>>
final @Nullable Ballot promisedBallot; // if non-null, has already been promised by some shard
final BiConsumer<Outcome, Throwable> callback;
final Status witnessedByInvalidation;
+ // TODO (required): we don't use this on all paths, and should at least make sure we don't trigger any Invariant failures e.g. "Fetch was successful for all keys, but the WaitingState has not been cleared"
final long reportLowEpoch, reportHighEpoch;
private RecoverWithRoute(Node node, Topologies topologies, @Nullable Ballot promisedBallot, TxnId txnId, Infer.InvalidIf invalidIf, FullRoute<?> route, Status witnessedByInvalidation, long reportLowEpoch, long reportHighEpoch, BiConsumer<Outcome, Throwable> callback)
@@ -132,7 +133,7 @@ public class RecoverWithRoute extends CheckShards<FullRoute<?>>
if (!sufficientTo.isDefinitionKnown())
return false;
- if (sufficientTo.outcome.isInvalidated())
+ if (sufficientTo.outcome().isInvalidated())
return true;
Invariants.checkState(full.partialTxn.covers(route));
@@ -152,21 +153,21 @@ public class RecoverWithRoute extends CheckShards<FullRoute<?>>
Known known = full.knownFor(txnId, route, route);
// TODO (required): audit this logic, and centralise with e.g. FetchData inferences
- switch (known.outcome)
+ switch (known.outcome())
{
default: throw new AssertionError();
case Unknown:
- if (known.definition.isKnown())
+ if (known.definition().isKnown())
{
Txn txn = full.partialTxn.reconstitute(route);
- Recover.recover(node, txnId, txn, route, callback);
+ Recover.recover(node, txnId, txn, route, reportLowEpoch, reportHighEpoch, callback);
}
- else if (!known.definition.isOrWasKnown())
+ else if (!known.definition().isOrWasKnown())
{
// TODO (required): this logic should be put in Infer, alongside any similar inferences in Recover
if (witnessedByInvalidation != null && witnessedByInvalidation.compareTo(Status.PreAccepted) > 0)
throw illegalState("We previously invalidated, finding a status that should be recoverable");
- Invalidate.invalidate(node, txnId, route, witnessedByInvalidation != null, callback);
+ Invalidate.invalidate(node, txnId, route, witnessedByInvalidation != null, reportLowEpoch, reportHighEpoch, callback);
}
else
{
@@ -194,9 +195,9 @@ public class RecoverWithRoute extends CheckShards<FullRoute<?>>
else
{
known = full.knownFor(txnId, trySendTo, trySendTo);
- Invariants.checkState(known.executeAt.isDecidedAndKnownToExecute() && known.outcome == Apply);
+ Invariants.checkState(known.executeAt().isDecidedAndKnownToExecute() && known.outcome() == Apply);
- if (known.deps != DepsKnown)
+ if (!known.is(DepsKnown))
{
Invariants.checkState(txnId.isSystemTxn() || full.partialTxn.covers(trySendTo));
Participants<?> collect = full.map.knownFor(Known.Nothing.with(DepsKnown), route);
@@ -208,7 +209,7 @@ public class RecoverWithRoute extends CheckShards<FullRoute<?>>
return;
}
- LatestDeps.MergedCommitResult mergedCommit = LatestDeps.mergeCommit(DepsUnknown, txnId, full.executeAt, deps, full.executeAt, i -> i);
+ LatestDeps.MergedCommitResult mergedCommit = LatestDeps.mergeCommit(DepsUnknown, full.executeAt, deps, full.executeAt, i -> i);
Route<?> canSendTo = trySendTo.without(collect).with((Participants) collect.slice(mergedCommit.sufficientFor, Minimal));
Deps stableDeps = full.stableDeps.with(mergedCommit.deps).intersecting(canSendTo);
node.coordinationAdapter(txnId, Recovery).persist(node, null, route, canSendTo, txnId, full.partialTxn, full.executeAt, stableDeps, full.writes, full.result, null);
@@ -239,7 +240,7 @@ public class RecoverWithRoute extends CheckShards<FullRoute<?>>
}
Txn txn = full.partialTxn.reconstitute(route);
- if (known.executeAt.isDecidedAndKnownToExecute() && known.deps.hasDecidedDeps() && known.outcome == Apply)
+ if (known.executeAt().isDecidedAndKnownToExecute() && known.is(DepsKnown) && known.outcome() == Apply)
{
Deps deps = full.stableDeps.reconstitute(route());
node.withEpoch(full.executeAt.epoch(), node.agent(), t -> WrappableException.wrap(t), () -> {
@@ -253,7 +254,7 @@ public class RecoverWithRoute extends CheckShards<FullRoute<?>>
}
break;
- case Invalidated:
+ case Abort:
if (witnessedByInvalidation != null && witnessedByInvalidation.hasBeen(Status.PreCommitted))
throw illegalState("We previously invalidated, finding a status that should be recoverable");
diff --git a/accord-core/src/main/java/accord/coordinate/Stabilise.java b/accord-core/src/main/java/accord/coordinate/Stabilise.java
index 3be2ebee..3855ba38 100644
--- a/accord-core/src/main/java/accord/coordinate/Stabilise.java
+++ b/accord-core/src/main/java/accord/coordinate/Stabilise.java
@@ -28,7 +28,6 @@ import accord.messages.Callback;
import accord.messages.Commit;
import accord.messages.ReadData.CommitOrReadNack;
import accord.messages.ReadData.ReadReply;
-import accord.messages.ReadTxnData;
import accord.primitives.Ballot;
import accord.primitives.Deps;
import accord.primitives.FullRoute;
@@ -38,6 +37,7 @@ import accord.primitives.TxnId;
import accord.topology.Topologies;
import accord.utils.SortedArrays.SortedArrayList;
import accord.utils.SortedListMap;
+import accord.utils.UnhandledEnum;
import static accord.coordinate.ExecutePath.RECOVER;
import static accord.coordinate.ExecutePath.SLOW;
@@ -107,19 +107,15 @@ public abstract class Stabilise<R> implements Callback<ReadReply>
{
switch ((CommitOrReadNack)reply)
{
- default: throw new AssertionError("Unhandled CommitOrReadNack: " + reply);
+ default: throw new UnhandledEnum((CommitOrReadNack)reply);
case Redundant:
isDone = true;
callback.accept(null, new Redundant(txnId, route.homeKey(), executeAt));
break;
case Rejected:
- case Invalid:
- isDone = true;
- callback.accept(null, new Preempted(txnId, route.homeKey()));
- break;
case Insufficient:
- node.send(from, new Commit(CommitWithTxn, from, allTopologies.getEpoch(txnId.epoch()), allTopologies,
- txnId, txn, route, ballot, executeAt, stabiliseDeps, (ReadTxnData) null));
+ node.send(from, new Commit(CommitWithTxn, from, allTopologies,
+ txnId, txn, route, ballot, executeAt, stabiliseDeps));
break;
}
}
@@ -141,13 +137,13 @@ public abstract class Stabilise<R> implements Callback<ReadReply>
}
@Override
- public void onCallbackFailure(Node.Id from, Throwable failure)
+ public boolean onCallbackFailure(Node.Id from, Throwable failure)
{
- if (isDone)
- return;
+ if (isDone) return false;
isDone = true;
callback.accept(null, failure);
+ return true;
}
protected void onStabilised()
diff --git a/accord-core/src/main/java/accord/coordinate/SynchronousAwait.java b/accord-core/src/main/java/accord/coordinate/SynchronousAwait.java
deleted file mode 100644
index a03f0935..00000000
--- a/accord-core/src/main/java/accord/coordinate/SynchronousAwait.java
+++ /dev/null
@@ -1,99 +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 accord.coordinate;
-
-import javax.annotation.Nullable;
-
-import accord.api.ProgressLog.BlockedUntil;
-import accord.api.RoutingKey;
-import accord.coordinate.tracking.QuorumTracker;
-import accord.coordinate.tracking.ResponseTracker;
-import accord.local.Node;
-import accord.local.Node.Id;
-import accord.messages.Await;
-import accord.messages.Await.AwaitOk;
-import accord.messages.Callback;
-import accord.primitives.Participants;
-import accord.primitives.TxnId;
-import accord.topology.Topologies;
-import accord.utils.async.AsyncResults;
-
-import static accord.coordinate.tracking.RequestStatus.Failed;
-import static accord.coordinate.tracking.RequestStatus.Success;
-
-/**
- * Synchronously await some set of replicas reaching a given wait condition.
- * This may or may not be a condition we expect to reach promptly, but we will wait only until the timeout passes
- * at which point we will report failure.
- */
-public class SynchronousAwait extends AsyncResults.SettableResult<Void> implements Callback<AwaitOk>
-{
- // TODO (desired, efficiency): this should collect the executeAt of any commit, and terminate as soon as one is found
- // that is earlier than TxnId for the Txn we are recovering; if all commits we wait for
- // are given earlier timestamps we can retry without restarting.
-
- final TxnId txnId;
- final @Nullable RoutingKey homeKey;
- final ResponseTracker tracker;
-
- public SynchronousAwait(TxnId txnId, @Nullable RoutingKey homeKey, ResponseTracker tracker)
- {
- this.txnId = txnId;
- this.homeKey = homeKey;
- this.tracker = tracker;
- }
-
- public static SynchronousAwait awaitQuorum(Node node, Topologies topologies, TxnId txnId, @Nullable RoutingKey homeKey, BlockedUntil awaiting, Participants<?> participants)
- {
- QuorumTracker tracker = new QuorumTracker(topologies);
- SynchronousAwait result = new SynchronousAwait(txnId, homeKey, tracker);
- result.start(node, topologies, participants, awaiting);
- return result;
- }
-
- private void start(Node node, Topologies topologies, Participants<?> participants, BlockedUntil blockedUntil)
- {
- node.send(topologies.nodes(), to -> new Await(to, topologies, txnId, participants, blockedUntil), this);
- }
-
- @Override
- public synchronized void onSuccess(Id from, AwaitOk reply)
- {
- if (isDone()) return;
-
- if (tracker.recordSuccess(from) == Success)
- trySuccess(null);
- }
-
- @Override
- public synchronized void onFailure(Id from, Throwable failure)
- {
- if (isDone()) return;
-
- if (tracker.recordFailure(from) == Failed)
- tryFailure(new Timeout(txnId, homeKey));
- }
-
- @Override
- public void onCallbackFailure(Id from, Throwable failure)
- {
- tryFailure(failure);
- }
-}
-
diff --git a/accord-core/src/main/java/accord/coordinate/SynchronousRecoverAwait.java b/accord-core/src/main/java/accord/coordinate/SynchronousRecoverAwait.java
new file mode 100644
index 00000000..69932268
--- /dev/null
+++ b/accord-core/src/main/java/accord/coordinate/SynchronousRecoverAwait.java
@@ -0,0 +1,100 @@
+/*
+ * 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.coordinate;
+
+import java.util.function.BiConsumer;
+
+import accord.api.ProgressLog.BlockedUntil;
+import accord.local.Node;
+import accord.local.Node.Id;
+import accord.messages.RecoverAwait;
+import accord.messages.SimpleReply;
+import accord.primitives.Participants;
+import accord.primitives.TxnId;
+import accord.topology.Topologies;
+import accord.utils.async.AsyncResult;
+import accord.utils.async.AsyncResults;
+
+import static accord.messages.SimpleReply.Nack;
+
+/**
+ * Synchronously await some set of replicas reaching a given wait condition.
+ * This may or may not be a condition we expect to reach promptly, but we will wait only until the timeout passes
+ * at which point we will report failure.
+ */
+public class SynchronousRecoverAwait extends ReadCoordinator<SimpleReply>
+{
+ final Participants<?> participants;
+ final BlockedUntil blockedUntil;
+ final boolean notifyProgressLog;
+ final TxnId recoverId;
+
+ final BiConsumer<Boolean, Throwable> callback;
+
+ private boolean rejects;
+ public SynchronousRecoverAwait(Node node, Topologies topologies, TxnId txnId, Participants<?> participants, BlockedUntil blockedUntil, boolean notifyProgressLog, TxnId recoverId, BiConsumer<Boolean, Throwable> callback)
+ {
+ super(node, topologies, txnId);
+ this.participants = participants;
+ this.blockedUntil = blockedUntil;
+ this.notifyProgressLog = notifyProgressLog;
+ this.recoverId = recoverId;
+ this.callback = callback;
+ }
+
+ public static SynchronousRecoverAwait awaitAny(Node node, Topologies topologies, TxnId txnId, BlockedUntil blockedUntil, boolean notifyProgressLog, Participants<?> participants, TxnId recoverId, BiConsumer<Boolean, Throwable> callback)
+ {
+ SynchronousRecoverAwait result = new SynchronousRecoverAwait(node, topologies, txnId, participants, blockedUntil, notifyProgressLog, recoverId, callback);
+ result.start();
+ return result;
+ }
+
+ public static AsyncResult<Boolean> awaitAny(Node node, Topologies topologies, TxnId txnId, BlockedUntil blockedUntil, boolean notifyProgressLog, Participants<?> participants, TxnId recoverId)
+ {
+ AsyncResult.Settable<Boolean> result = AsyncResults.settable();
+ awaitAny(node, topologies, txnId, blockedUntil, notifyProgressLog, participants, recoverId, result.settingCallback());
+ return result;
+ }
+
+ @Override
+ protected Action process(Id from, SimpleReply reply)
+ {
+ if (reply == Nack)
+ {
+ rejects = true;
+ onDone(null, null);
+ return Action.Aborted;
+ }
+ return Action.Approve;
+ }
+
+ @Override
+ protected void onDone(ReadCoordinator.Success success, Throwable failure)
+ {
+ if (failure == null) callback.accept(rejects, null);
+ else callback.accept(null, failure);
+ }
+
+ @Override
+ protected void contact(Id to)
+ {
+ node.send(to, new RecoverAwait(to, topologies, txnId, participants, blockedUntil, notifyProgressLog, recoverId), this);
+ }
+}
+
diff --git a/accord-core/src/main/java/accord/coordinate/TopologyMismatch.java b/accord-core/src/main/java/accord/coordinate/TopologyMismatch.java
index 18db4f01..14e99e3e 100644
--- a/accord-core/src/main/java/accord/coordinate/TopologyMismatch.java
+++ b/accord-core/src/main/java/accord/coordinate/TopologyMismatch.java
@@ -24,7 +24,6 @@ import javax.annotation.Nullable;
import accord.api.RoutingKey;
import accord.primitives.Routables;
import accord.primitives.TxnId;
-import accord.primitives.Unseekables;
import accord.topology.Topology;
import static accord.utils.Invariants.checkState;
@@ -41,7 +40,7 @@ public class TopologyMismatch extends CoordinationFailed
this.reasons = reasons;
}
- private TopologyMismatch(EnumSet<Reason> reasons, Topology t, Unseekables<?> select)
+ private TopologyMismatch(EnumSet<Reason> reasons, Topology t, Routables<?> select)
{
super(null, null, buildMessage(t, select));
this.reasons = reasons;
@@ -74,7 +73,7 @@ public class TopologyMismatch extends CoordinationFailed
return sb.toString();
}
- private static String buildMessage(Topology t, Unseekables<?> select)
+ private static String buildMessage(Topology t, Routables<?> select)
{
return String.format("Attempted to access %s that are no longer valid globally (%d -> %s)", select.without(t.ranges()), t.epoch(), t.ranges());
}
@@ -98,7 +97,7 @@ public class TopologyMismatch extends CoordinationFailed
}
@Nullable
- public static TopologyMismatch checkForMismatch(Topology t, Unseekables<?> select)
+ public static TopologyMismatch checkForMismatch(Topology t, Routables<?> select)
{
return t.ranges().containsAll(select) ? null : new TopologyMismatch(EnumSet.of(Reason.KEYS_OR_RANGES), t, select);
}
diff --git a/accord-core/src/main/java/accord/coordinate/tracking/AbstractTracker.java b/accord-core/src/main/java/accord/coordinate/tracking/AbstractTracker.java
index 846621b5..27f118e2 100644
--- a/accord-core/src/main/java/accord/coordinate/tracking/AbstractTracker.java
+++ b/accord-core/src/main/java/accord/coordinate/tracking/AbstractTracker.java
@@ -94,7 +94,7 @@ public abstract class AbstractTracker<ST extends ShardTracker>
this(topologies, arrayFactory, (ignore, shard) -> trackerFactory.apply(shard));
}
- public AbstractTracker(Topologies topologies, IntFunction<ST[]> arrayFactory, ShardFactory<ST> trackerFactory)
+ public <P> AbstractTracker(Topologies topologies, IntFunction<ST[]> arrayFactory, ShardFactory<ST> trackerFactory)
{
Invariants.checkArgument(topologies.totalShards() > 0);
int topologyCount = topologies.size();
diff --git a/accord-core/src/main/java/accord/coordinate/tracking/FastPathTracker.java b/accord-core/src/main/java/accord/coordinate/tracking/FastPathTracker.java
index 271b1395..80357ac6 100644
--- a/accord-core/src/main/java/accord/coordinate/tracking/FastPathTracker.java
+++ b/accord-core/src/main/java/accord/coordinate/tracking/FastPathTracker.java
@@ -20,9 +20,12 @@ package accord.coordinate.tracking;
import accord.coordinate.tracking.QuorumTracker.QuorumShardTracker;
import accord.local.Node;
+import accord.primitives.TxnId;
+import accord.primitives.TxnId.FastPath;
import accord.topology.Shard;
import accord.topology.Topologies;
import accord.utils.Invariants;
+import accord.utils.UnhandledEnum;
import com.google.common.annotations.VisibleForTesting;
@@ -31,25 +34,36 @@ import javax.annotation.Nonnull;
import java.util.function.BiFunction;
import static accord.coordinate.tracking.AbstractTracker.ShardOutcomes.*;
+import static accord.primitives.TxnId.FastPath.PRIVILEGED_COORDINATOR_WITHOUT_DEPS;
+import static accord.primitives.TxnId.FastPath.PRIVILEGED_COORDINATOR_WITH_DEPS;
+import static accord.primitives.TxnId.FastPath.UNOPTIMISED;
// TODO (desired, efficiency): if any shard *cannot* take the fast path, and all shards have accepted, terminate
public class FastPathTracker extends PreAcceptTracker<FastPathTracker.FastPathShardTracker>
{
private static final ShardOutcome<FastPathTracker> NewFastPathSuccess = (tracker, shardIndex) -> {
--tracker.waitingOnFastPathSuccess;
+ --tracker.waitingOnMediumPathSuccess;
+ return --tracker.waitingOnShards == 0 ? Success : NoChange;
+ };
+
+ private static final ShardOutcome<FastPathTracker> NewMediumPathSuccess = (tracker, shardIndex) -> {
+ --tracker.waitingOnMediumPathSuccess;
return --tracker.waitingOnShards == 0 ? Success : NoChange;
};
public static abstract class FastPathShardTracker extends QuorumShardTracker
{
+ final int fastQuorumSize;
protected int fastPathAccepts;
protected int fastPathFailures;
protected int fastPathDelayed;
protected boolean complete;
- public FastPathShardTracker(Shard shard)
+ public FastPathShardTracker(FastPath fastPath, Shard shard)
{
super(shard);
+ this.fastQuorumSize = shard.fastQuorumSize(fastPath);
}
// return NewQuorumSuccess ONLY once fast path is rejected
@@ -67,7 +81,7 @@ public class FastPathTracker extends PreAcceptTracker<FastPathTracker.FastPathSh
return NoChange;
++successes;
- if (shard.fastPathElectorate.contains(node))
+ if (shard.isInFastPath(node))
{
++fastPathAccepts;
if (hasMetFastPathCriteria())
@@ -85,7 +99,7 @@ public class FastPathTracker extends PreAcceptTracker<FastPathTracker.FastPathSh
if (++failures > shard.maxFailures)
return complete(Fail);
- if (shard.fastPathElectorate.contains(from))
+ if (shard.isInFastPath(from))
{
++fastPathFailures;
@@ -101,7 +115,7 @@ public class FastPathTracker extends PreAcceptTracker<FastPathTracker.FastPathSh
if (complete)
return NoChange;
- if (shard.fastPathElectorate.contains(from))
+ if (shard.isInFastPath(from))
{
++fastPathDelayed;
@@ -114,32 +128,40 @@ public class FastPathTracker extends PreAcceptTracker<FastPathTracker.FastPathSh
final ShardOutcome<? super FastPathTracker> quorumIfHasRejectedFastPath()
{
- return hasReachedQuorum() && hasRejectedFastPath() ? complete(Success) : NoChange;
+ return hasReachedQuorum() && hasRejectedFastPath()
+ ? hasMetMediumPathCriteria() ? complete(NewMediumPathSuccess) : complete(Success)
+ : NoChange;
}
final boolean isFastPathDelayed()
{
- return fastPathDelayed >= shard.fastPathRejectSize;
+ return shard.rejectsFastPath(fastQuorumSize, fastPathDelayed);
}
@VisibleForTesting
public final boolean hasMetFastPathCriteria()
{
- return fastPathAccepts >= shard.fastPathQuorumSize;
+ return fastPathAccepts >= fastQuorumSize;
+ }
+
+ @VisibleForTesting
+ public final boolean hasMetMediumPathCriteria()
+ {
+ return fastPathAccepts >= shard.slowQuorumSize;
}
@VisibleForTesting
public final boolean hasRejectedFastPath()
{
- return fastPathFailures >= shard.fastPathRejectSize;
+ return shard.rejectsFastPath(fastQuorumSize, fastPathFailures);
}
}
public static final class PriorFastPathShardTracker extends FastPathShardTracker
{
- public PriorFastPathShardTracker(Shard shard)
+ public PriorFastPathShardTracker(FastPath fastPath, Shard shard)
{
- super(shard);
+ super(fastPath, shard);
}
public ShardOutcome<? super FastPathTracker> onQuorumSuccess(Node.Id node)
@@ -150,9 +172,9 @@ public class FastPathTracker extends PreAcceptTracker<FastPathTracker.FastPathSh
public static final class CurrentFastPathShardTracker extends FastPathShardTracker
{
- public CurrentFastPathShardTracker(Shard shard)
+ public CurrentFastPathShardTracker(FastPath fastPath, Shard shard)
{
- super(shard);
+ super(fastPath, shard);
}
public ShardOutcome<? super FastPathTracker> onQuorumSuccess(Node.Id node)
@@ -161,18 +183,19 @@ public class FastPathTracker extends PreAcceptTracker<FastPathTracker.FastPathSh
return NoChange;
++successes;
- if (shard.fastPathElectorate.contains(node))
+ if (shard.isInFastPath(node))
++fastPathFailures; // Quorum success can not count towards fast path success
return quorumIfHasRejectedFastPath();
}
}
- int waitingOnFastPathSuccess; // if we reach zero, we have succeeded on the fast path outcome for every shard
- public FastPathTracker(Topologies topologies)
+ int waitingOnFastPathSuccess; // if we reach zero, we have succeeded on the fast path for every shard
+ int waitingOnMediumPathSuccess; // if we reach zero, we have succeeded on the medium path for every shard
+ public FastPathTracker(Topologies topologies, TxnId txnId)
{
- super(topologies, FastPathShardTracker[]::new, (i, shard) -> i == 0 ? new CurrentFastPathShardTracker(shard) : new PriorFastPathShardTracker(shard));
- this.waitingOnFastPathSuccess = super.waitingOnShards;
+ super(topologies, FastPathShardTracker[]::new, factory(txnId));
+ this.waitingOnFastPathSuccess = this.waitingOnMediumPathSuccess = super.waitingOnShards;
}
public RequestStatus recordSuccess(Node.Id from, boolean withFastPathTimestamp)
@@ -203,6 +226,11 @@ public class FastPathTracker extends PreAcceptTracker<FastPathTracker.FastPathSh
return waitingOnFastPathSuccess == 0;
}
+ public boolean hasMediumPathAccepted()
+ {
+ return waitingOnMediumPathSuccess == 0;
+ }
+
public boolean hasFailed()
{
return any(FastPathShardTracker::hasFailed);
@@ -212,4 +240,20 @@ public class FastPathTracker extends PreAcceptTracker<FastPathTracker.FastPathSh
{
return all(FastPathShardTracker::hasReachedQuorum);
}
+
+ private static ShardFactory<FastPathShardTracker> factory(TxnId txnId)
+ {
+ switch (txnId.fastPath())
+ {
+ default: throw new UnhandledEnum(txnId.fastPath());
+ case UNOPTIMISED: return (i, s) -> create(UNOPTIMISED, i, s);
+ case PRIVILEGED_COORDINATOR_WITHOUT_DEPS: return (i, s) -> create(PRIVILEGED_COORDINATOR_WITHOUT_DEPS, i, s);
+ case PRIVILEGED_COORDINATOR_WITH_DEPS: return (i, s) -> create(PRIVILEGED_COORDINATOR_WITH_DEPS, i, s);
+ }
+ }
+
+ private static FastPathShardTracker create(FastPath fastPath, int index, Shard shard)
+ {
+ return index == 0 ? new CurrentFastPathShardTracker(fastPath, shard) : new PriorFastPathShardTracker(fastPath, shard);
+ }
}
diff --git a/accord-core/src/main/java/accord/coordinate/tracking/InvalidationTracker.java b/accord-core/src/main/java/accord/coordinate/tracking/InvalidationTracker.java
index 5f79455b..3b470594 100644
--- a/accord-core/src/main/java/accord/coordinate/tracking/InvalidationTracker.java
+++ b/accord-core/src/main/java/accord/coordinate/tracking/InvalidationTracker.java
@@ -23,15 +23,22 @@ import javax.annotation.Nullable;
import accord.local.Node;
import accord.primitives.Participants;
+import accord.primitives.TxnId;
+import accord.primitives.TxnId.FastPath;
import accord.topology.Shard;
import accord.topology.Topologies;
+import accord.utils.UnhandledEnum;
import static accord.coordinate.tracking.AbstractTracker.ShardOutcomes.*;
+import static accord.primitives.TxnId.FastPath.PRIVILEGED_COORDINATOR_WITHOUT_DEPS;
+import static accord.primitives.TxnId.FastPath.PRIVILEGED_COORDINATOR_WITH_DEPS;
+import static accord.primitives.TxnId.FastPath.UNOPTIMISED;
public class InvalidationTracker extends AbstractTracker<InvalidationTracker.InvalidationShardTracker>
{
public static class InvalidationShardTracker extends ShardTracker implements ShardOutcome<InvalidationTracker>
{
+ private final int fastQuorumSize;
private int fastPathRejects;
private int fastPathInflight;
private int promisesOrPartPromises;
@@ -40,16 +47,17 @@ public class InvalidationTracker extends AbstractTracker<InvalidationTracker.Inv
private boolean isFinal;
private @Nullable Participants<?> neverTruncated;
- private InvalidationShardTracker(Shard shard)
+ private InvalidationShardTracker(FastPath fastPath, Shard shard)
{
super(shard);
inflight = shard.rf();
- fastPathInflight = shard.fastPathElectorate.size();
+ fastPathInflight = shard.fastPathElectorateSize;
+ fastQuorumSize = shard.fastQuorumSize(fastPath);
}
public InvalidationShardTracker onSuccess(Node.Id from, @Nullable Participants<?> promised, @Nonnull Participants<?> notTruncated, @Nullable Participants<?> truncated, boolean isDecided, boolean withFastPath)
{
- if (shard.fastPathElectorate.contains(from))
+ if (shard.isInFastPath(from))
{
--fastPathInflight;
if (!withFastPath) ++fastPathRejects;
@@ -67,7 +75,7 @@ public class InvalidationTracker extends AbstractTracker<InvalidationTracker.Inv
public ShardOutcome<? super InvalidationTracker> onFailure(Node.Id from)
{
- if (shard.fastPathElectorate.contains(from))
+ if (shard.isInFastPath(from))
--fastPathInflight;
--inflight;
return this;
@@ -85,12 +93,12 @@ public class InvalidationTracker extends AbstractTracker<InvalidationTracker.Inv
public boolean isFastPathRejected()
{
- return fastPathRejects > shard.fastPathElectorate.size() - shard.fastPathQuorumSize;
+ return shard.rejectsFastPath(fastQuorumSize, fastPathRejects);
}
public boolean canFastPathBeRejected()
{
- return fastPathRejects + fastPathInflight > shard.fastPathElectorate.size() - shard.fastPathQuorumSize;
+ return shard.rejectsFastPath(fastQuorumSize, fastPathRejects + fastPathInflight);
}
private boolean isPromiseDecided()
@@ -100,12 +108,12 @@ public class InvalidationTracker extends AbstractTracker<InvalidationTracker.Inv
public boolean isPromiseRejected()
{
- return promisesOrPartPromises + inflight < shard.slowPathQuorumSize;
+ return promisesOrPartPromises + inflight < shard.slowQuorumSize;
}
public boolean isPromised()
{
- return promisesOrPartPromises >= shard.slowPathQuorumSize;
+ return promisesOrPartPromises >= shard.slowQuorumSize;
}
public boolean isAnyNotTruncated()
@@ -148,9 +156,9 @@ public class InvalidationTracker extends AbstractTracker<InvalidationTracker.Inv
private int promisedShard = -1;
private boolean rejectsFastPath;
- public InvalidationTracker(Topologies topologies)
+ public InvalidationTracker(Topologies topologies, TxnId txnId)
{
- super(topologies, InvalidationShardTracker[]::new, InvalidationShardTracker::new);
+ super(topologies, InvalidationShardTracker[]::new, factory(txnId));
}
public Shard promisedShard()
@@ -183,4 +191,15 @@ public class InvalidationTracker extends AbstractTracker<InvalidationTracker.Inv
{
return recordResponse(this, from, InvalidationShardTracker::onFailure, from);
}
+
+ private static ShardFactory<InvalidationShardTracker> factory(TxnId txnId)
+ {
+ switch (txnId.fastPath())
+ {
+ default: throw new UnhandledEnum(txnId.fastPath());
+ case UNOPTIMISED: return (i, s) -> new InvalidationShardTracker(UNOPTIMISED, s);
+ case PRIVILEGED_COORDINATOR_WITHOUT_DEPS: return (i, s) -> new InvalidationShardTracker(PRIVILEGED_COORDINATOR_WITHOUT_DEPS, s);
+ case PRIVILEGED_COORDINATOR_WITH_DEPS: return (i, s) -> new InvalidationShardTracker(PRIVILEGED_COORDINATOR_WITH_DEPS, s);
+ }
+ }
}
diff --git a/accord-core/src/main/java/accord/local/NodeCommandStoreService.java b/accord-core/src/main/java/accord/coordinate/tracking/PreAcceptExclusiveSyncPointTracker.java
similarity index 67%
copy from accord-core/src/main/java/accord/local/NodeCommandStoreService.java
copy to accord-core/src/main/java/accord/coordinate/tracking/PreAcceptExclusiveSyncPointTracker.java
index 4ee7686e..d0148828 100644
--- a/accord-core/src/main/java/accord/local/NodeCommandStoreService.java
+++ b/accord-core/src/main/java/accord/coordinate/tracking/PreAcceptExclusiveSyncPointTracker.java
@@ -16,17 +16,21 @@
* limitations under the License.
*/
-package accord.local;
+package accord.coordinate.tracking;
-import accord.api.Timeouts;
-import accord.primitives.Timestamp;
+import accord.primitives.TxnId;
+import accord.topology.Topologies;
-public interface NodeCommandStoreService extends TimeService
+public class PreAcceptExclusiveSyncPointTracker extends QuorumTracker
{
- long epoch();
- Node.Id id();
- Timeouts timeouts();
- DurableBefore durableBefore();
- Timestamp uniqueNow();
- Timestamp uniqueNow(Timestamp atLeast);
+ public PreAcceptExclusiveSyncPointTracker(Topologies topologies, TxnId txnId)
+ {
+ super(topologies);
+ }
+
+ @Override
+ public boolean hasMediumPathAccepted()
+ {
+ return hasReachedQuorum();
+ }
}
diff --git a/accord-core/src/main/java/accord/coordinate/tracking/PreAcceptTracker.java b/accord-core/src/main/java/accord/coordinate/tracking/PreAcceptTracker.java
index 6aa89ad5..c782bf26 100644
--- a/accord-core/src/main/java/accord/coordinate/tracking/PreAcceptTracker.java
+++ b/accord-core/src/main/java/accord/coordinate/tracking/PreAcceptTracker.java
@@ -40,4 +40,5 @@ public abstract class PreAcceptTracker<ST extends ShardTracker> extends Abstract
public abstract RequestStatus recordSuccess(Node.Id from, boolean withFastPathTimestamp);
public abstract RequestStatus recordDelayed(Node.Id from);
public abstract boolean hasFastPathAccepted();
+ public abstract boolean hasMediumPathAccepted();
}
diff --git a/accord-core/src/main/java/accord/coordinate/tracking/QuorumIdTracker.java b/accord-core/src/main/java/accord/coordinate/tracking/QuorumIdTracker.java
index c8e9be55..e846f8cd 100644
--- a/accord-core/src/main/java/accord/coordinate/tracking/QuorumIdTracker.java
+++ b/accord-core/src/main/java/accord/coordinate/tracking/QuorumIdTracker.java
@@ -43,7 +43,7 @@ public class QuorumIdTracker extends SimpleTracker<QuorumIdTracker.QuorumIdShard
public ShardOutcomes onSuccess(Node.Id from)
{
- return successes.add(from) && successes.size() == shard.slowPathQuorumSize ? Success : NoChange;
+ return successes.add(from) && successes.size() == shard.slowQuorumSize ? Success : NoChange;
}
// return true iff hasFailed()
@@ -56,7 +56,7 @@ public class QuorumIdTracker extends SimpleTracker<QuorumIdTracker.QuorumIdShard
public boolean hasReachedQuorum()
{
- return successes.size() >= shard.slowPathQuorumSize;
+ return successes.size() >= shard.slowQuorumSize;
}
boolean hasInFlight()
diff --git a/accord-core/src/main/java/accord/coordinate/tracking/QuorumTracker.java b/accord-core/src/main/java/accord/coordinate/tracking/QuorumTracker.java
index 6f3faf4b..4015c8b9 100644
--- a/accord-core/src/main/java/accord/coordinate/tracking/QuorumTracker.java
+++ b/accord-core/src/main/java/accord/coordinate/tracking/QuorumTracker.java
@@ -26,19 +26,33 @@ import static accord.coordinate.tracking.AbstractTracker.ShardOutcomes.*;
public class QuorumTracker extends SimpleTracker<QuorumTracker.QuorumShardTracker> implements ResponseTracker
{
- public static class QuorumShardTracker extends ShardTracker
+ public static class QuorumShardTracker extends AbstractQuorumShardTracker
+ {
+ public QuorumShardTracker(Shard shard)
+ {
+ super(shard);
+ }
+ public boolean hasReachedQuorum()
+ {
+ return successes >= shard.slowQuorumSize;
+ }
+ }
+
+ public static abstract class AbstractQuorumShardTracker extends ShardTracker
{
protected int successes;
protected int failures;
- public QuorumShardTracker(Shard shard)
+ public abstract boolean hasReachedQuorum();
+
+ public AbstractQuorumShardTracker(Shard shard)
{
super(shard);
}
public ShardOutcomes onSuccess(Object ignore)
{
- return ++successes == shard.slowPathQuorumSize ? Success : NoChange;
+ return ++successes == shard.slowQuorumSize ? Success : NoChange;
}
// return true iff hasFailed()
@@ -47,11 +61,6 @@ public class QuorumTracker extends SimpleTracker<QuorumTracker.QuorumShardTracke
return failures++ == shard.maxFailures ? Fail : NoChange;
}
- public boolean hasReachedQuorum()
- {
- return successes >= shard.slowPathQuorumSize;
- }
-
boolean hasInFlight()
{
return successes + failures < shard.rf();
diff --git a/accord-core/src/main/java/accord/coordinate/tracking/ReadTracker.java b/accord-core/src/main/java/accord/coordinate/tracking/ReadTracker.java
index 3dee3651..371fff8e 100644
--- a/accord-core/src/main/java/accord/coordinate/tracking/ReadTracker.java
+++ b/accord-core/src/main/java/accord/coordinate/tracking/ReadTracker.java
@@ -27,6 +27,8 @@ import accord.primitives.Ranges;
import accord.topology.Shard;
import accord.topology.ShardSelection;
import com.google.common.annotations.VisibleForTesting;
+
+import accord.topology.Topology;
import accord.utils.Invariants;
import accord.local.Node.Id;
@@ -135,7 +137,7 @@ public class ReadTracker extends AbstractTracker<ReadTracker.ReadShardTracker>
if (hadSucceeded)
return NoChange;
- if (quorum == shard.slowPathQuorumSize)
+ if (quorum == shard.slowQuorumSize)
return Success;
return ensureProgressOrFail();
@@ -169,7 +171,7 @@ public class ReadTracker extends AbstractTracker<ReadTracker.ReadShardTracker>
public boolean hasReachedQuorum()
{
- return quorum >= shard.slowPathQuorumSize;
+ return quorum >= shard.slowQuorumSize;
}
public boolean hasSucceeded()
@@ -309,6 +311,37 @@ public class ReadTracker extends AbstractTracker<ReadTracker.ReadShardTracker>
return recordResponse(this, from, function, isSlow);
}
+ protected final boolean tryIfUniversal(Id id)
+ {
+ if (candidates.isEmpty())
+ return false;
+
+ for (int i = 0 ; i < topologies.size() ; ++i)
+ {
+ Topology topology = topologies.get(i);
+ for (int j = 0 ; j < topology.size() ; ++j)
+ {
+ if (!topology.get(j).nodes.contains(id))
+ return false;
+ }
+ }
+
+ {
+ int index = 0, last = candidates.size() - 1;
+ while (index <= last)
+ {
+ if (id.equals(candidates.get(index)))
+ break;
+ ++index;
+ }
+ candidates.set(index, candidates.get(last));
+ candidates.remove(last);
+ }
+ recordInFlightRead(id);
+
+ return true;
+ }
+
public <T1> RequestStatus trySendMore(BiConsumer<T1, Id> contact, T1 with)
{
ShardSelection toRead;
diff --git a/accord-core/src/main/java/accord/coordinate/tracking/RecoveryTracker.java b/accord-core/src/main/java/accord/coordinate/tracking/RecoveryTracker.java
index e7c6f5b2..d8495e73 100644
--- a/accord-core/src/main/java/accord/coordinate/tracking/RecoveryTracker.java
+++ b/accord-core/src/main/java/accord/coordinate/tracking/RecoveryTracker.java
@@ -18,18 +18,25 @@
package accord.coordinate.tracking;
-import accord.coordinate.tracking.QuorumTracker.QuorumShardTracker;
+import accord.coordinate.tracking.QuorumTracker.AbstractQuorumShardTracker;
import accord.local.Node;
+import accord.primitives.TxnId;
import accord.topology.Shard;
import accord.topology.Topologies;
import accord.topology.Topology;
public class RecoveryTracker extends AbstractTracker<RecoveryTracker.RecoveryShardTracker>
{
- public static class RecoveryShardTracker extends QuorumShardTracker
+ public static class RecoveryShardTracker extends AbstractQuorumShardTracker
{
protected int fastPathRejects = 0;
+ @Override
+ public boolean hasReachedQuorum()
+ {
+ return successes >= shard.recoveryQuorumSize;
+ }
+
private RecoveryShardTracker(Shard shard)
{
super(shard);
@@ -37,14 +44,14 @@ public class RecoveryTracker extends AbstractTracker<RecoveryTracker.RecoverySha
private ShardOutcomes onSuccessRejectFastPath(Node.Id from)
{
- if (shard.fastPathElectorate.contains(from))
+ if (shard.isInFastPath(from))
++fastPathRejects;
return onSuccess(from);
}
- private boolean rejectsFastPath()
+ private boolean rejectsFastPath(TxnId txnId)
{
- return fastPathRejects > shard.fastPathElectorate.size() - shard.fastPathQuorumSize;
+ return shard.rejectsFastPath(txnId, fastPathRejects);
}
}
@@ -67,7 +74,7 @@ public class RecoveryTracker extends AbstractTracker<RecoveryTracker.RecoverySha
return recordResponse(this, from, RecoveryShardTracker::onFailure, from);
}
- public boolean rejectsFastPath()
+ public boolean rejectsFastPath(TxnId txnId)
{
// a fast path decision must have recorded itself to a fast quorum in an earlier epoch
// but the fast path votes may be taken from the proposal epoch only.
@@ -76,9 +83,15 @@ public class RecoveryTracker extends AbstractTracker<RecoveryTracker.RecoverySha
Topology current = topologies.current();
for (int i = 0 ; i < current.size() ; ++i)
{
- if (trackers[i].rejectsFastPath())
+ if (trackers[i].rejectsFastPath(txnId))
return true;
}
return false;
}
+
+ public boolean mustSupersedingCoordinatorHaveIntersectedFastQuorum(TxnId self, TxnId superseding)
+ {
+ // TODO (required): in follow up
+ return false;
+ }
}
diff --git a/accord-core/src/main/java/accord/coordinate/tracking/SimpleTracker.java b/accord-core/src/main/java/accord/coordinate/tracking/SimpleTracker.java
index 05556235..d2e5cb7e 100644
--- a/accord-core/src/main/java/accord/coordinate/tracking/SimpleTracker.java
+++ b/accord-core/src/main/java/accord/coordinate/tracking/SimpleTracker.java
@@ -37,6 +37,7 @@ public abstract class SimpleTracker<ST extends ShardTracker> extends PreAcceptTr
public RequestStatus recordSuccess(Id from, boolean withFastPathTimestamp) { return recordSuccess(from); }
public RequestStatus recordDelayed(Id from) { return NoChange; }
public boolean hasFastPathAccepted() { return false; }
+ public boolean hasMediumPathAccepted() { return false; }
public abstract RequestStatus recordSuccess(Id from);
public abstract RequestStatus recordFailure(Id from);
diff --git a/accord-core/src/main/java/accord/impl/AbstractFetchCoordinator.java b/accord-core/src/main/java/accord/impl/AbstractFetchCoordinator.java
index 92d61dc0..739eb369 100644
--- a/accord-core/src/main/java/accord/impl/AbstractFetchCoordinator.java
+++ b/accord-core/src/main/java/accord/impl/AbstractFetchCoordinator.java
@@ -26,6 +26,7 @@ import java.util.Map;
import accord.local.Command;
import accord.local.SafeCommandStore;
import accord.messages.ReadData;
+import accord.utils.UnhandledEnum;
import accord.utils.async.AsyncChain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -55,6 +56,7 @@ import accord.utils.async.AsyncResults;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import static accord.messages.ReadData.CommitOrReadNack.Redundant;
import static accord.messages.ReadEphemeralTxnData.retryInLaterEpoch;
import static accord.primitives.SaveStatus.Applied;
import static accord.primitives.SaveStatus.TruncatedApply;
@@ -153,17 +155,10 @@ public abstract class AbstractFetchCoordinator extends FetchCoordinator
{
fail(to, new RuntimeException(reply.toString()));
inflight.remove(key).cancel();
- switch ((CommitOrReadNack) reply)
- {
- default: throw new AssertionError("Unhandled enum: " + reply);
- case Redundant:
- // TODO (expected): stop fetch sync points from garbage collecting too quickly
- // too late, sync point has been erased
- break;
- case Invalid:
- case Rejected:
- throw new AssertionError(String.format("Unexpected reply: %s", reply));
- }
+ if (reply != Redundant)
+ throw new UnhandledEnum((CommitOrReadNack)reply);
+ // too late, sync point has been erased
+ // TODO (expected): stop fetch sync points from garbage collecting too quickly
}
return;
}
@@ -185,7 +180,7 @@ public abstract class AbstractFetchCoordinator extends FetchCoordinator
received = ranges;
}
- // TODO (now): make sure it works if invoked in either order
+ // TODO (required): make sure it works if invoked in either order
inflight.remove(key).started(ok.safeToReadAfter);
onReadOk(to, commandStore, ok.data, received);
// received must be invoked after submitting the persistence future, as it triggers onDone
@@ -200,10 +195,11 @@ public abstract class AbstractFetchCoordinator extends FetchCoordinator
}
@Override
- public void onCallbackFailure(Node.Id from, Throwable failure)
+ public boolean onCallbackFailure(Node.Id from, Throwable failure)
{
// TODO (soon)
logger.error("Fetch coordination failure from " + from, failure);
+ return true;
}
});
}
@@ -286,7 +282,7 @@ public abstract class AbstractFetchCoordinator extends FetchCoordinator
@Override
protected void readComplete(CommandStore commandStore, Data result, Ranges unavailable)
{
- Ranges reportUnavailable = unavailable == null ? null : unavailable.slice((Ranges)this.readScope, Minimal);
+ Ranges reportUnavailable = unavailable == null ? null : unavailable.slice((Ranges)this.scope, Minimal);
super.readComplete(commandStore, result, reportUnavailable);
}
@@ -304,7 +300,7 @@ public abstract class AbstractFetchCoordinator extends FetchCoordinator
long retryInLaterEpoch = retryInLaterEpoch(executeAtEpoch, safeStore, command);
if (retryInLaterEpoch > 0)
{
- Ranges unavailable = ((Ranges)readScope).slice(safeStore.ranges().allAt(executeAtEpoch), Minimal);
+ Ranges unavailable = ((Ranges) scope).slice(safeStore.ranges().allAt(executeAtEpoch), Minimal);
readComplete(safeStore.commandStore(), null, unavailable);
}
else
diff --git a/accord-core/src/main/java/accord/impl/CommandChange.java b/accord-core/src/main/java/accord/impl/CommandChange.java
index 5d0bc37d..2a68b1aa 100644
--- a/accord-core/src/main/java/accord/impl/CommandChange.java
+++ b/accord-core/src/main/java/accord/impl/CommandChange.java
@@ -26,7 +26,6 @@ import accord.api.Agent;
import accord.api.Result;
import accord.local.Cleanup;
import accord.local.Command;
-import accord.local.CommonAttributes;
import accord.local.DurableBefore;
import accord.local.RedundantBefore;
import accord.local.StoreParticipants;
@@ -39,6 +38,7 @@ import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.primitives.Writes;
import accord.utils.Invariants;
+import accord.utils.UnhandledEnum;
import static accord.api.Journal.Load;
import static accord.api.Journal.Load.ALL;
@@ -58,9 +58,8 @@ import static accord.impl.CommandChange.Field.WAITING_ON;
import static accord.impl.CommandChange.Field.WRITES;
import static accord.local.Cleanup.NO;
import static accord.local.Cleanup.TRUNCATE_WITH_OUTCOME;
-import static accord.primitives.SaveStatus.TruncatedApplyWithOutcome;
+import static accord.local.StoreParticipants.Filter.LOAD;
import static accord.primitives.Status.Durability.NotDurable;
-import static accord.utils.Invariants.illegalState;
public class CommandChange
{
@@ -252,7 +251,7 @@ public class CommandChange
return count;
}
- public Cleanup shouldCleanup(Agent agent, RedundantBefore redundantBefore, DurableBefore durableBefore)
+ public Cleanup shouldCleanup(Agent agent, RedundantBefore redundantBefore, DurableBefore durableBefore, boolean isPartial)
{
if (!nextCalled)
return NO;
@@ -260,7 +259,8 @@ public class CommandChange
if (saveStatus == null || participants == null)
return Cleanup.NO;
- Cleanup cleanup = Cleanup.shouldCleanupPartial(agent, txnId, saveStatus, durability, participants, redundantBefore, durableBefore);
+ Cleanup cleanup = isPartial ? Cleanup.shouldCleanupPartial(agent, txnId, saveStatus, durability, participants, redundantBefore, durableBefore)
+ : Cleanup.shouldCleanup(agent, txnId, saveStatus, durability, participants, redundantBefore, durableBefore);
if (this.cleanup != null && this.cleanup.compareTo(cleanup) > 0)
cleanup = this.cleanup;
return cleanup;
@@ -291,7 +291,8 @@ public class CommandChange
case NO:
return this;
default:
- throw new IllegalStateException("Unknown cleanup: " + cleanup);}
+ throw new UnhandledEnum(cleanup);
+ }
}
public Builder expungePartial(Cleanup cleanup, SaveStatus saveStatus, boolean includeOutcome)
@@ -350,7 +351,7 @@ public class CommandChange
public Command.Minimal asMinimal()
{
- return new Command.Minimal(txnId, saveStatus, participants, durability, executeAt, writes);
+ return new Command.Minimal(txnId, saveStatus, participants, durability, executeAt);
}
public void forceResult(Result newValue)
@@ -364,26 +365,11 @@ public class CommandChange
return null;
Invariants.checkState(txnId != null);
- CommonAttributes.Mutable attrs = new CommonAttributes.Mutable(txnId);
- if (partialTxn != null)
- attrs.partialTxn(partialTxn);
- if (durability != null)
- attrs.durability(durability);
- if (participants != null)
- attrs.setParticipants(participants.filter(StoreParticipants.Filter.LOAD, redundantBefore, txnId, saveStatus.known.executeAt.isDecidedAndKnownToExecute() ? executeAt : null));
- if (participants != null)
- attrs.setParticipants(participants);
- if (partialDeps != null && saveStatus.known.deps.hasProposedOrDecidedDeps())
- attrs.partialDeps(partialDeps);
+ if (participants == null) participants = StoreParticipants.empty(txnId);
+ else participants = participants.filter(LOAD, redundantBefore, txnId, saveStatus.known.executeAt().isDecidedAndKnownToExecute() ? executeAt : null);
- switch (saveStatus.known.outcome)
- {
- case Erased:
- case WasApply:
- writes = null;
- result = null;
- break;
- }
+ if (durability == null)
+ durability = NotDurable;
Command.WaitingOn waitingOn = null;
if (this.waitingOn != null)
@@ -392,52 +378,50 @@ public class CommandChange
switch (saveStatus.status)
{
case NotDefined:
- return saveStatus == SaveStatus.Uninitialised ? Command.NotDefined.uninitialised(attrs.txnId())
- : Command.NotDefined.notDefined(attrs, promised);
+ return saveStatus == SaveStatus.Uninitialised ? Command.NotDefined.uninitialised(txnId)
+ : Command.NotDefined.notDefined(txnId, saveStatus, durability, participants, promised);
case PreAccepted:
- return Command.PreAccepted.preAccepted(attrs, executeAt, promised);
+ return Command.PreAccepted.preaccepted(txnId, saveStatus, durability, participants, promised, executeAt, partialTxn, partialDeps);
case AcceptedInvalidate:
+ case PreNotAccepted:
+ case NotAccepted:
if (!saveStatus.known.isDefinitionKnown())
- return Command.AcceptedInvalidateWithoutDefinition.acceptedInvalidate(attrs, promised, acceptedOrCommitted);
- case Accepted:
+ return Command.NotAcceptedWithoutDefinition.notAccepted(txnId, saveStatus, durability, participants, promised, acceptedOrCommitted, partialDeps);
+ case AcceptedMedium:
+ case AcceptedSlow:
case PreCommitted:
- return Command.Accepted.accepted(attrs, saveStatus, executeAt, promised, acceptedOrCommitted);
+ return Command.Accepted.accepted(txnId, saveStatus, durability, participants, promised, executeAt, partialTxn, partialDeps, acceptedOrCommitted);
case Committed:
case Stable:
- return Command.Committed.committed(attrs, saveStatus, executeAt, promised, acceptedOrCommitted, waitingOn);
+ return Command.Committed.committed(txnId, saveStatus, durability, participants, promised, executeAt, partialTxn, partialDeps, acceptedOrCommitted, waitingOn);
case PreApplied:
case Applied:
- return Command.Executed.executed(attrs, saveStatus, executeAt, promised, acceptedOrCommitted, waitingOn, writes, result);
+ return Command.Executed.executed(txnId, saveStatus, durability, participants, promised, executeAt, partialTxn, partialDeps, acceptedOrCommitted, waitingOn, writes, result);
case Truncated:
case Invalidated:
- return truncated(attrs, saveStatus, executeAt, executeAtLeast, writes, result);
+ return truncated(txnId, saveStatus, durability, participants, executeAt, executeAtLeast, writes, result);
default:
- throw new IllegalStateException();
+ throw new UnhandledEnum(saveStatus.status);
}
}
- private static Command.Truncated truncated(CommonAttributes.Mutable attrs, SaveStatus status, Timestamp executeAt, Timestamp executesAtLeast, Writes writes, Result result)
+ private static Command.Truncated truncated(TxnId txnId, SaveStatus status, Status.Durability durability, StoreParticipants participants, Timestamp executeAt, Timestamp executesAtLeast, Writes writes, Result result)
{
switch (status)
{
- default:
- throw illegalState("Unhandled SaveStatus: " + status);
+ default: throw new UnhandledEnum(status);
case TruncatedApplyWithOutcome:
case TruncatedApplyWithDeps:
case TruncatedApply:
- if (status != TruncatedApplyWithOutcome)
- result = null;
- if (attrs.txnId().kind().awaitsOnlyDeps())
- return Command.Truncated.truncatedApply(attrs, status, executeAt, writes, result, executesAtLeast);
- return Command.Truncated.truncatedApply(attrs, status, executeAt, writes, result, null);
+ return Command.Truncated.truncatedApply(txnId, status, durability, participants, executeAt, writes, result, executesAtLeast);
case ErasedOrVestigial:
- return Command.Truncated.erasedOrVestigial(attrs.txnId(), attrs.participants());
+ return Command.Truncated.erasedOrVestigial(txnId, participants);
case Erased:
// TODO (expected): why are we saving Durability here for erased commands?
- return Command.Truncated.erased(attrs.txnId(), attrs.durability(), attrs.participants());
+ return Command.Truncated.erased(txnId, durability, participants);
case Invalidated:
- return Command.Truncated.invalidated(attrs.txnId(), attrs.participants());
+ return Command.Truncated.invalidated(txnId, participants);
}
}
@@ -524,13 +508,7 @@ public class CommandChange
flags = collectFlags(before, after, CommandChange::getWaitingOn, true, WAITING_ON, flags);
flags = collectFlags(before, after, Command::writes, false, WRITES, flags);
-
- // Special-cased for Journal BurnTest integration
- if ((before != null && after.result() != before.result()) ||
- (before == null && after.result() != null))
- {
- flags = collectFlags(before, after, Command::writes, false, RESULT, flags);
- }
+ flags = collectFlags(before, after, Command::result, false, RESULT, flags);
return flags;
}
diff --git a/accord-core/src/main/java/accord/impl/DurabilityScheduling.java b/accord-core/src/main/java/accord/impl/DurabilityScheduling.java
index a3347026..278d4139 100644
--- a/accord-core/src/main/java/accord/impl/DurabilityScheduling.java
+++ b/accord-core/src/main/java/accord/impl/DurabilityScheduling.java
@@ -224,6 +224,8 @@ public class DurabilityScheduling implements ConfigurationService.Listener
if (numberOfSplits > targetShardSplits && index % 4 == 0)
{
index /= 4;
+ if (index > 0 && numberOfSplits % 4 != 0)
+ --index;
numberOfSplits /= 4;
}
scheduleAt(nowMicros, scheduleAt);
diff --git a/accord-core/src/main/java/accord/impl/InMemoryCommandStore.java b/accord-core/src/main/java/accord/impl/InMemoryCommandStore.java
index 71df36f6..d63fb6d8 100644
--- a/accord-core/src/main/java/accord/impl/InMemoryCommandStore.java
+++ b/accord-core/src/main/java/accord/impl/InMemoryCommandStore.java
@@ -88,7 +88,7 @@ import accord.utils.async.Cancellable;
import org.agrona.collections.ObjectHashSet;
import static accord.local.KeyHistory.ASYNC;
-import static accord.primitives.Known.KnownRoute.Maybe;
+import static accord.primitives.Known.KnownRoute.MaybeRoute;
import static accord.primitives.Routable.Domain.Range;
import static accord.primitives.Routables.Slice.Minimal;
import static accord.local.KeyHistory.SYNC;
@@ -212,7 +212,7 @@ public abstract class InMemoryCommandStore extends CommandStore
if (intersectingParticipants.isEmpty())
continue;
- if (!cur.txnId().witnesses().test(prev.txnId()) && !cur.partialDeps().contains(prev.txnId()))
+ if (!cur.txnId().witnesses(prev.txnId()) && !cur.partialDeps().contains(prev.txnId()))
continue;
Participants<?> depParticipants = cur.partialDeps().participants(prev.txnId());
@@ -329,6 +329,7 @@ public abstract class InMemoryCommandStore extends CommandStore
}
protected void onRead(Command current) {}
+ protected void onWrite(Command current) {}
protected void onRead(CommandsForKey current) {}
protected final InMemorySafeStore createSafeStore(PreLoadContext context, RangesForEpoch ranges)
@@ -1182,14 +1183,16 @@ public abstract class InMemoryCommandStore extends CommandStore
try
{
PreLoadContext context = context(command, SYNC);
- commandStore.executeInContext(commandStore,
- context,
- safeStore -> {
- applyWrites(command.txnId(), safeStore, (safeCommand, cmd) -> {
- unsafeApplyWrites(safeStore, safeCommand, cmd);
+ commandStore.unsafeRunIn(() -> {
+ commandStore.executeInContext(commandStore,
+ context,
+ safeStore -> {
+ applyWrites(command.txnId(), safeStore, (safeCommand, cmd) -> {
+ unsafeApplyWrites(safeStore, safeCommand, cmd);
+ });
+ return null;
});
- return null;
- });
+ });
}
catch (Throwable t)
{
@@ -1222,7 +1225,7 @@ public abstract class InMemoryCommandStore extends CommandStore
TreeMap<TxnId, RangeCommand> rangeCommands = this.rangeCommands;
rangeDeps.forEachUniqueTxnId(allRanges, null, (ignore, txnId) -> {
GlobalCommand global = commands.get(txnId);
- if (global != null && global.value().known().route != Maybe)
+ if (global != null && global.value().known().has(MaybeRoute))
return;
Ranges ranges = rangeDeps.ranges(txnId);
diff --git a/accord-core/src/main/java/accord/impl/RequestCallbacks.java b/accord-core/src/main/java/accord/impl/RequestCallbacks.java
index 7f35d637..9309bd08 100644
--- a/accord-core/src/main/java/accord/impl/RequestCallbacks.java
+++ b/accord-core/src/main/java/accord/impl/RequestCallbacks.java
@@ -141,15 +141,19 @@ public class RequestCallbacks extends AbstractTimeouts<RequestCallbacks.Callback
}
catch (Throwable t)
{
+ Throwable log = null;
try
{
- callback.onCallbackFailure(to, t);
+ if (!callback.onCallbackFailure(to, t))
+ log = t;
}
catch (Throwable t2)
{
+ log = t;
t.addSuppressed(t2);
- executor.agent().onUncaughtException(t);
}
+ if (log != null)
+ executor.agent().onUncaughtException(t);
}
});
}
diff --git a/accord-core/src/main/java/accord/impl/progresslog/DefaultProgressLog.java b/accord-core/src/main/java/accord/impl/progresslog/DefaultProgressLog.java
index 78c61601..077fc597 100644
--- a/accord-core/src/main/java/accord/impl/progresslog/DefaultProgressLog.java
+++ b/accord-core/src/main/java/accord/impl/progresslog/DefaultProgressLog.java
@@ -35,7 +35,6 @@ import accord.api.ProgressLog;
import accord.api.RoutingKey;
import accord.local.Command;
import accord.local.CommandStore;
-import accord.local.CommonAttributes;
import accord.local.Node;
import accord.local.SafeCommand;
import accord.local.SafeCommandStore;
@@ -366,12 +365,10 @@ public class DefaultProgressLog implements ProgressLog, Runnable
if (!blockedBy.txnId().isVisible())
return;
- // ensure we have a record to work with later; otherwise may think has been truncated
- // TODO (expected): we shouldn't rely on this anymore
blockedBy.initialise();
Command command = blockedBy.current();
SaveStatus saveStatus = command.saveStatus();
- Invariants.checkState(saveStatus.compareTo(blockedUntil.minSaveStatus) < 0);
+ Invariants.checkState(saveStatus.compareTo(blockedUntil.unblockedFrom) < 0);
StoreParticipants blockedOnStoreParticipants2 = null;
if (blockedOnParticipants != null || blockedOnRoute != null)
@@ -393,16 +390,16 @@ public class DefaultProgressLog implements ProgressLog, Runnable
// first save the route/participant info into the Command if it isn't already there
- CommonAttributes update = blockedBy.current();
+ Command update = blockedBy.current();
StoreParticipants participants = update.participants();
StoreParticipants updatedParticipants = participants;
if (blockedOnStoreParticipants != null) updatedParticipants = updatedParticipants.supplementOrMerge(saveStatus, blockedOnStoreParticipants);
if (blockedOnStoreParticipants2 != null) updatedParticipants = updatedParticipants.supplementOrMerge(saveStatus, blockedOnStoreParticipants2);
if (participants != updatedParticipants)
- update = update.mutable().setParticipants(updatedParticipants);
+ update = update.updateParticipants(updatedParticipants);
if (update != command)
- command = blockedBy.updateAttributes(safeStore, update);
+ command = blockedBy.incidentalUpdate(update);
// TODO (required): tighten up ExclusiveSyncPoint range bounds
Invariants.checkState((command.txnId().is(ExclusiveSyncPoint) ? safeStore.ranges().all()
diff --git a/accord-core/src/main/java/accord/impl/progresslog/TxnState.java b/accord-core/src/main/java/accord/impl/progresslog/TxnState.java
index 18384534..28d23feb 100644
--- a/accord-core/src/main/java/accord/impl/progresslog/TxnState.java
+++ b/accord-core/src/main/java/accord/impl/progresslog/TxnState.java
@@ -26,6 +26,7 @@ import accord.api.ProgressLog.BlockedUntil;
import accord.local.SafeCommandStore;
import accord.primitives.TxnId;
import accord.utils.Invariants;
+import accord.utils.UnhandledEnum;
import static java.util.concurrent.TimeUnit.MICROSECONDS;
@@ -42,7 +43,7 @@ final class TxnState extends HomeState
switch (newProgress)
{
default:
- throw new AssertionError("Unhandled Progress: " + newProgress);
+ throw new UnhandledEnum(newProgress);
case NoneExpected:
case Querying:
newDelay = 0;
@@ -50,7 +51,7 @@ final class TxnState extends HomeState
case Queued:
switch (updated)
{
- default: throw new AssertionError("Unhandled TxnStateKind: " + updated);
+ default: throw new UnhandledEnum(updated);
case Waiting:
newDelay = instance.commandStore.agent().seekProgressDelay(instance.node, safeStore, txnId, waitingRetryCounter(), blockedUntil, MICROSECONDS);
break;
diff --git a/accord-core/src/main/java/accord/impl/progresslog/WaitingState.java b/accord-core/src/main/java/accord/impl/progresslog/WaitingState.java
index 69269611..c6e92482 100644
--- a/accord-core/src/main/java/accord/impl/progresslog/WaitingState.java
+++ b/accord-core/src/main/java/accord/impl/progresslog/WaitingState.java
@@ -42,6 +42,7 @@ import accord.primitives.TxnId;
import accord.primitives.Unseekables;
import accord.topology.Topologies;
import accord.utils.Invariants;
+import accord.utils.UnhandledEnum;
import static accord.api.ProgressLog.BlockedUntil.CanApply;
import static accord.api.ProgressLog.BlockedUntil.Query.HOME;
@@ -66,6 +67,7 @@ import static accord.impl.progresslog.WaitingState.CallbackKind.AwaitSlice;
import static accord.impl.progresslog.WaitingState.CallbackKind.Fetch;
import static accord.impl.progresslog.WaitingState.CallbackKind.FetchRoute;
import static accord.primitives.EpochSupplier.constant;
+import static accord.primitives.Txn.Kind.ExclusiveSyncPoint;
/**
* This represents a simple state machine encoded in a small number of bits for efficiently gathering
@@ -234,12 +236,30 @@ abstract class WaitingState extends BaseTxnState
*/
static EpochSupplier lowEpoch(SafeCommandStore safeStore, TxnId txnId, Command command)
{
- EpochSupplier result = txnId;
StoreParticipants participants = command.participants();
- long newEpoch = safeStore.ranges().latestEarlierEpochThatFullyCovers(txnId.epoch(), participants.hasTouched());
- if (newEpoch < txnId.epoch())
- result = constant(newEpoch);
- return result;
+ if (txnId.is(ExclusiveSyncPoint))
+ {
+ long newEpoch = safeStore.ranges().latestEarlierEpochThatFullyCovers(txnId.epoch(), participants.hasTouched());
+ if (newEpoch < txnId.epoch())
+ return constant(newEpoch);
+ return txnId;
+ }
+
+ if (command.known().deps().hasPreAcceptedOrProposedOrDecidedDeps() && participants.touchesOnlyOwned())
+ return txnId;
+
+ if (participants.route() == null)
+ return txnId;
+
+ Participants<?> unsynced = safeStore.node().topology().unsyncedOnly(participants.route(), txnId.epoch());
+ if (unsynced == null || unsynced.isEmpty())
+ return txnId;
+
+ long lowEpoch = safeStore.ranges().latestEarlierEpochThatFullyCovers(txnId.epoch(), unsynced);
+ if (lowEpoch >= txnId.epoch())
+ return txnId;
+
+ return constant(lowEpoch);
}
static EpochSupplier highEpoch(SafeCommandStore safeStore, TxnId txnId, BlockedUntil blockedUntil, Command command, Timestamp executeAt)
@@ -258,12 +278,12 @@ abstract class WaitingState extends BaseTxnState
return command.route().slice(ranges);
}
- private static Route<?> slicedRoute(SafeCommandStore safeStore, TxnId txnId, Route<?> route, EpochSupplier toLocalEpoch)
+ private static Route<?> slicedRoute(SafeCommandStore safeStore, TxnId txnId, Route<?> route, EpochSupplier fromLocalEpoch, EpochSupplier toLocalEpoch)
{
- route = StoreParticipants.touches(safeStore, txnId, toLocalEpoch, route);
- if (route.isEmpty())
- route = route.homeKeyOnlyRoute();
- return route;
+ Route<?> result = StoreParticipants.touches(safeStore, fromLocalEpoch, txnId, toLocalEpoch, route);
+ if (result.isEmpty())
+ result = result.homeKeyOnlyRoute();
+ return result;
}
private static Route<?> awaitRoute(Route<?> slicedRoute, BlockedUntil blockedUntil)
@@ -271,9 +291,13 @@ abstract class WaitingState extends BaseTxnState
return blockedUntil.waitsOn == HOME ? slicedRoute.homeKeyOnlyRoute() : slicedRoute;
}
- private static Route<?> fetchRoute(Route<?> slicedRoute, Route<?> awaitRoute, BlockedUntil blockedUntil)
+ private static Route<?> fetchRoute(Route<?> slicedRoute, Route<?> awaitRoute, BlockedUntil blockedUntil, SafeCommandStore safeStore, EpochSupplier lowEpoch, TxnId txnId, EpochSupplier highEpoch, Route<?> route)
{
- return blockedUntil.waitsOn == blockedUntil.fetchFrom ? awaitRoute : slicedRoute;
+ if (lowEpoch.epoch() < txnId.epoch())
+ return StoreParticipants.touches(safeStore, lowEpoch, txnId, highEpoch, route);
+ if (blockedUntil.waitsOn == blockedUntil.fetchFrom)
+ return awaitRoute;
+ return slicedRoute;
}
void setWaitingDone(DefaultProgressLog owner)
@@ -298,7 +322,7 @@ abstract class WaitingState extends BaseTxnState
void record(DefaultProgressLog owner, SaveStatus newSaveStatus)
{
BlockedUntil currentlyBlockedUntil = blockedUntil();
- if (currentlyBlockedUntil.minSaveStatus.compareTo(newSaveStatus) <= 0)
+ if (currentlyBlockedUntil.unblockedFrom.compareTo(newSaveStatus) <= 0)
{
boolean isDone = newSaveStatus.hasBeen(Status.PreApplied);
set(null, owner, isDone ? CanApply : currentlyBlockedUntil, NoneExpected);
@@ -318,8 +342,8 @@ abstract class WaitingState extends BaseTxnState
BlockedUntil blockedUntil = blockedUntil();
Command command = safeCommand.current();
Invariants.checkState(!owner.hasActive(Waiting, txnId));
- Invariants.checkState(command.saveStatus().compareTo(blockedUntil.minSaveStatus) < 0,
- "Command has met desired criteria (%s) but progress log entry has not been cancelled: %s", blockedUntil.minSaveStatus, command);
+ Invariants.checkState(command.saveStatus().compareTo(blockedUntil.unblockedFrom) < 0,
+ "Command has met desired criteria (%s) but progress log entry has not been cancelled: %s", blockedUntil.unblockedFrom, command);
set(safeStore, owner, blockedUntil, Querying);
TxnId txnId = safeCommand.txnId();
@@ -343,7 +367,8 @@ abstract class WaitingState extends BaseTxnState
return;
}
- Route<?> slicedRoute = slicedRoute(safeStore, txnId, route, highEpoch);
+ // TODO (expected): split into txn and deps sources
+ Route<?> slicedRoute = slicedRoute(safeStore, txnId, route, txnId, highEpoch);
Invariants.checkState(!slicedRoute.isEmpty());
if (!command.hasBeen(Status.PreCommitted))
{
@@ -356,7 +381,7 @@ abstract class WaitingState extends BaseTxnState
// the awaitRoute may be only the home shard, if that is sufficient to indicate the fetchRoute will be able to answer our query;
// the fetchRoute may also be only the home shard, if that is sufficient to answer our query (e.g. for executeAt)
Route<?> awaitRoute = awaitRoute(slicedRoute, blockedUntil);
- Route<?> fetchRoute = fetchRoute(slicedRoute, awaitRoute, blockedUntil);
+ Route<?> fetchRoute = fetchRoute(slicedRoute, awaitRoute, blockedUntil, safeStore, lowEpoch, txnId, highEpoch, route);
if (awaitRoute.isHomeKeyOnlyRoute())
{
@@ -415,7 +440,7 @@ abstract class WaitingState extends BaseTxnState
}
EpochSupplier toLocalEpoch = highEpoch(safeStore, txnId, blockedUntil, command, command.executeAtOrTxnId());
- Route<?> slicedRoute = slicedRoute(safeStore, txnId, route, toLocalEpoch); // the actual local keys we care about
+ Route<?> slicedRoute = slicedRoute(safeStore, txnId, route, txnId, toLocalEpoch); // the actual local keys we care about
Route<?> awaitRoute = awaitRoute(slicedRoute, blockedUntil); // either slicedRoute or just the home key
int roundSize = awaitRoundSize(awaitRoute);
@@ -424,7 +449,7 @@ abstract class WaitingState extends BaseTxnState
switch (kind)
{
- default: throw new AssertionError("Unhandled CallbackKind: " + kind);
+ default: throw new UnhandledEnum(kind);
case AwaitHome:
if (notReady == null)
@@ -612,7 +637,7 @@ abstract class WaitingState extends BaseTxnState
// TODO (desired): fetch only the route
// we MUSt allocate before calling withEpoch to register cancellation, as async
BiConsumer<FetchData.FetchResult, Throwable> invoker = invokeWaitingCallback(owner, txnId, blockedUntil, WaitingState::fetchRouteCallback);
- FetchData.fetch(blockedUntil.minSaveStatus.known, owner.node(), txnId, executeAt, fetchKeys, lowEpoch, highEpoch, invoker);
+ FetchData.fetch(blockedUntil.unblockedFrom.known, owner.node(), txnId, executeAt, fetchKeys, lowEpoch, highEpoch, invoker);
}
static void fetch(DefaultProgressLog owner, BlockedUntil blockedUntil, TxnId txnId, Timestamp executeAt, EpochSupplier lowEpoch, EpochSupplier highEpoch, Route<?> slicedRoute, Route<?> fetchRoute, Route<?> maxRoute)
@@ -620,7 +645,7 @@ abstract class WaitingState extends BaseTxnState
Invariants.checkState(!slicedRoute.isEmpty());
// we MUSt allocate before calling withEpoch to register cancellation, as async
BiConsumer<FetchData.FetchResult, Throwable> invoker = invokeWaitingCallback(owner, txnId, blockedUntil, WaitingState::fetchCallback);
- FetchData.fetchSpecific(blockedUntil.minSaveStatus.known, owner.node(), txnId, fetchRoute, maxRoute, slicedRoute, lowEpoch, highEpoch, executeAt, invoker);
+ FetchData.fetchSpecific(blockedUntil.unblockedFrom.known, owner.node(), txnId, fetchRoute, maxRoute, slicedRoute, lowEpoch, highEpoch, executeAt, invoker);
}
void awaitHomeKey(DefaultProgressLog owner, BlockedUntil blockedUntil, TxnId txnId, Timestamp executeAt, Route<?> route)
@@ -653,7 +678,7 @@ abstract class WaitingState extends BaseTxnState
switch (progress)
{
default:
- throw new AssertionError("Unhandled Progress: " + progress);
+ throw new UnhandledEnum(progress);
case NoneExpected:
return blockedUntil == CanApply ? "Done" : "NotWaiting";
case Queued:
diff --git a/accord-core/src/main/java/accord/local/Command.java b/accord-core/src/main/java/accord/local/Command.java
index 72cf30e8..7b9eb1db 100644
--- a/accord-core/src/main/java/accord/local/Command.java
+++ b/accord-core/src/main/java/accord/local/Command.java
@@ -27,7 +27,6 @@ import javax.annotation.Nullable;
import accord.api.Result;
import accord.api.RoutingKey;
-import accord.api.VisibleForImplementation;
import accord.local.cfk.CommandsForKey;
import accord.primitives.Ballot;
import accord.primitives.Deps;
@@ -44,6 +43,7 @@ import accord.primitives.RoutingKeys;
import accord.primitives.SaveStatus;
import accord.primitives.Status;
import accord.primitives.Status.Durability;
+import accord.primitives.Status.Phase;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
@@ -54,23 +54,30 @@ import accord.utils.IndexedQuadConsumer;
import accord.utils.IndexedTriConsumer;
import accord.utils.Invariants;
import accord.utils.SimpleBitSet;
+import accord.utils.UnhandledEnum;
import com.google.common.annotations.VisibleForTesting;
-import static accord.local.Command.AbstractCommand.validate;
+import static accord.local.Command.Committed.committed;
+import static accord.local.Command.Executed.executed;
+import static accord.local.Command.NotAcceptedWithoutDefinition.notAccepted;
import static accord.primitives.Known.KnownExecuteAt.ExecuteAtKnown;
+import static accord.primitives.Known.KnownDeps.DepsUnknown;
import static accord.primitives.Routable.Domain.Key;
import static accord.primitives.Routable.Domain.Range;
import static accord.primitives.Routables.Slice;
import static accord.primitives.SaveStatus.AcceptedInvalidate;
import static accord.primitives.SaveStatus.ErasedOrVestigial;
+import static accord.primitives.SaveStatus.NotAccepted;
+import static accord.primitives.SaveStatus.PreNotAccepted;
+import static accord.primitives.SaveStatus.ReadyToExecute;
import static accord.primitives.SaveStatus.Uninitialised;
import static accord.primitives.Status.Durability.Local;
import static accord.primitives.Status.Durability.NotDurable;
import static accord.primitives.Status.Durability.ShardUniversal;
import static accord.primitives.Status.Durability.UniversalOrInvalidated;
-import static accord.primitives.Status.Invalidated;
import static accord.primitives.Status.Stable;
+import static accord.primitives.Txn.Kind.Write;
import static accord.utils.Invariants.Paranoia.LINEAR;
import static accord.utils.Invariants.Paranoia.NONE;
import static accord.utils.Invariants.ParanoiaCostFactor.LOW;
@@ -81,296 +88,114 @@ import static accord.utils.Utils.ensureImmutable;
import static accord.utils.Utils.ensureMutable;
import static java.lang.String.format;
-public abstract class Command implements CommonAttributes
+public abstract class Command implements ICommand
{
- private static Durability durability(Durability durability, SaveStatus status)
- {
- if (durability == NotDurable && status.compareTo(SaveStatus.PreApplied) >= 0 && status.compareTo(ErasedOrVestigial) < 0)
- return Local; // not necessary anywhere, but helps for logical consistency
- return durability;
- }
-
- @VisibleForImplementation
- public static class SerializerSupport
+ public static class Minimal
{
- public static NotDefined notDefined(CommonAttributes attributes, Ballot promised)
- {
- return NotDefined.notDefined(attributes, promised);
- }
-
- public static PreAccepted preaccepted(CommonAttributes common, Timestamp executeAt, Ballot promised)
- {
- return PreAccepted.preAccepted(common, executeAt, promised);
- }
-
- public static Accepted accepted(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted)
- {
- return Accepted.accepted(common, status, executeAt, promised, accepted);
- }
-
- public static AcceptedInvalidateWithoutDefinition acceptedInvalidateWithoutDefinition(CommonAttributes common, Ballot promised, Ballot accepted)
- {
- return AcceptedInvalidateWithoutDefinition.acceptedInvalidate(common, promised, accepted);
- }
-
- public static Committed committed(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted, WaitingOn waitingOn)
- {
- return Committed.committed(common, status, executeAt, promised, accepted, waitingOn);
- }
-
- public static Executed executed(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted, WaitingOn waitingOn, Writes writes, Result result)
- {
- return Executed.executed(common, status, executeAt, promised, accepted, waitingOn, writes, result);
- }
+ public final TxnId txnId;
+ public final SaveStatus saveStatus;
+ public final StoreParticipants participants;
+ public final Status.Durability durability;
+ public final Timestamp executeAt;
- public static Truncated invalidated(TxnId txnId, StoreParticipants participants)
+ public Minimal(TxnId txnId, SaveStatus saveStatus, StoreParticipants participants, Status.Durability durability, Timestamp executeAt)
{
- return Truncated.invalidated(txnId, participants);
+ this.txnId = txnId;
+ this.saveStatus = saveStatus;
+ this.participants = participants;
+ this.durability = durability;
+ this.executeAt = executeAt;
}
- public static Truncated truncatedApply(CommonAttributes common, SaveStatus saveStatus, Timestamp executeAt, Writes writes, Result result)
+ @Override
+ public boolean equals(Object object)
{
- return Truncated.truncatedApply(common, saveStatus, executeAt, writes, result);
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ Minimal minimal = (Minimal) object;
+ return Objects.equals(txnId, minimal.txnId) && saveStatus == minimal.saveStatus && Objects.equals(participants, minimal.participants) && durability == minimal.durability && Objects.equals(executeAt, minimal.executeAt);
}
- public static Truncated truncatedApply(CommonAttributes common, SaveStatus saveStatus, Timestamp executeAt, Writes writes, Result result, Timestamp executesAtLeast)
+ @Override
+ public final int hashCode()
{
- return Truncated.truncatedApply(common, saveStatus, executeAt, writes, result, executesAtLeast);
+ throw new UnsupportedOperationException();
}
}
- private static SaveStatus validateCommandClass(SaveStatus status, Class<?> expected, Class<?> actual)
+ private final TxnId txnId;
+ private final SaveStatus saveStatus;
+ private final Durability durability;
+ private final @Nonnull StoreParticipants participants;
+ private final Ballot promised;
+ private final Timestamp executeAt;
+ private final @Nullable PartialTxn partialTxn;
+ private final @Nullable PartialDeps partialDeps;
+ private final @Nonnull Ballot acceptedOrCommitted;
+
+ protected Command(TxnId txnId, SaveStatus saveStatus, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, @Nullable PartialTxn partialTxn, @Nullable PartialDeps partialDeps, Ballot acceptedOrCommitted)
{
- if (actual != expected)
- {
- throw illegalState(format("Cannot instantiate %s for status %s. %s expected",
- actual.getSimpleName(), status, expected.getSimpleName()));
- }
- return status;
+ this.txnId = txnId;
+ this.saveStatus = validateCommandClass(txnId, saveStatus, getClass());
+ this.durability = Invariants.nonNull(durability);
+ this.participants = Invariants.nonNull(participants);
+ this.promised = Invariants.nonNull(promised);
+ this.partialTxn = partialTxn;
+ this.partialDeps = partialDeps;
+ this.executeAt = executeAt;
+ this.acceptedOrCommitted = Invariants.nonNull(acceptedOrCommitted);
}
- private static SaveStatus validateCommandClass(TxnId txnId, SaveStatus status, Class<?> klass)
+ protected Command(TxnId txnId, SaveStatus saveStatus, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, @Nullable PartialTxn partialTxn, @Nullable PartialDeps partialDeps, Ballot acceptedOrCommitted, boolean unsafeInitialisation)
{
- switch (status)
- {
- case Uninitialised:
- case NotDefined:
- return validateCommandClass(status, NotDefined.class, klass);
- case PreAccepted:
- return validateCommandClass(status, PreAccepted.class, klass);
- case AcceptedInvalidate:
- return validateCommandClass(status, AcceptedInvalidateWithoutDefinition.class, klass);
- case Accepted:
- case AcceptedWithDefinition:
- case AcceptedInvalidateWithDefinition:
- case PreCommitted:
- case PreCommittedWithAcceptedDeps:
- case PreCommittedWithDefinition:
- case PreCommittedWithDefinitionAndAcceptedDeps:
- return validateCommandClass(status, Accepted.class, klass);
- case Committed:
- case ReadyToExecute:
- case Stable:
- return validateCommandClass(status, Committed.class, klass);
- case PreApplied:
- case Applying:
- case Applied:
- return validateCommandClass(status, Executed.class, klass);
- case TruncatedApply:
- case TruncatedApplyWithDeps:
- case TruncatedApplyWithOutcome:
- if (txnId.awaitsOnlyDeps())
- return validateCommandClass(status, TruncatedAwaitsOnlyDeps.class, klass);
- case Erased:
- case ErasedOrVestigial:
- case Invalidated:
- return validateCommandClass(status, Truncated.class, klass);
- default:
- throw illegalState("Unhandled status " + status);
- }
+ Invariants.checkState(unsafeInitialisation);
+ this.txnId = txnId;
+ this.saveStatus = saveStatus;
+ this.durability = durability;
+ this.participants = participants;
+ this.promised = promised;
+ this.partialTxn = partialTxn;
+ this.partialDeps = partialDeps;
+ this.executeAt = executeAt;
+ this.acceptedOrCommitted = acceptedOrCommitted;
}
- public abstract static class AbstractCommand extends Command
+ protected Command(ICommand copy, SaveStatus saveStatus)
{
- private final TxnId txnId;
- private final SaveStatus status;
- private final Durability durability;
- private final @Nonnull StoreParticipants participants;
- private final Ballot promised;
-
- private AbstractCommand(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised)
- {
- this.txnId = txnId;
- this.status = validateCommandClass(txnId, status, getClass());
- this.durability = Invariants.nonNull(durability);
- this.participants = Invariants.nonNull(participants);
- this.promised = Invariants.nonNull(promised);
- }
-
- private AbstractCommand(CommonAttributes common, SaveStatus status, Ballot promised)
- {
- this.txnId = common.txnId();
- this.status = validateCommandClass(txnId, status, getClass());
- this.durability = Invariants.nonNull(common.durability());
- this.participants = Invariants.nonNull(common.participants());
- this.promised = Invariants.nonNull(promised);
- }
-
- @Override
- public boolean equals(Object o)
- {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Command command = (Command) o;
- return txnId().equals(command.txnId())
- && saveStatus() == command.saveStatus()
- && durability() == command.durability()
- && Objects.equals(participants(), command.participants())
- && Objects.equals(promised(), command.promised());
- }
-
- @Override
- public String toString()
- {
- return "Command@" + System.identityHashCode(this) + '{' + txnId + ':' + status + '}';
- }
-
- @Override
- public TxnId txnId()
- {
- return txnId;
- }
-
- @Override
- public final StoreParticipants participants()
- {
- return participants;
- }
-
- @Override
- public Ballot promised()
- {
- return promised;
- }
-
- @Override
- public Durability durability()
- {
- return Command.durability(durability, saveStatus());
- }
+ this.txnId = copy.txnId();
+ this.saveStatus = validateCommandClass(txnId, saveStatus, getClass());
+ this.durability = Invariants.nonNull(copy.durability());
+ this.participants = Invariants.nonNull(copy.participants());
+ this.promised = copy.promised();
+ this.partialTxn = copy.partialTxn();
+ this.partialDeps = copy.partialDeps();
+ this.executeAt = copy.executeAt();
+ this.acceptedOrCommitted = copy.acceptedOrCommitted();
+ }
- @Override
- public final SaveStatus saveStatus()
- {
- return status;
- }
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Command command = (Command) o;
+ return txnId().equals(command.txnId())
+ && saveStatus() == command.saveStatus()
+ && durability() == command.durability()
+ && Objects.equals(participants(), command.participants())
+ && Objects.equals(promised(), command.promised());
+ }
- public static <T extends AbstractCommand> T validate(T validate)
- {
- Known known = validate.known();
- switch (known.route)
- {
- default: throw new AssertionError("Unhandled KnownRoute: " + known.route);
- case Maybe: break;
- case Full: Invariants.checkState(Route.isFullRoute(validate.route())); break;
- case Covering: Invariants.checkState(Route.isRoute(validate.route())); break;
- }
- {
- PartialTxn partialTxn = validate.partialTxn();
- switch (known.definition)
- {
- default: throw new AssertionError("Unhandled Definition: " + known.definition);
- case DefinitionErased:
- case DefinitionUnknown:
- case NoOp:
- Invariants.checkState(partialTxn == null, "partialTxn is defined %s", validate);
- break;
- case DefinitionKnown:
- Invariants.checkState(partialTxn != null || validate.participants().owns().isEmpty(), "partialTxn is null");
- break;
- }
- }
- {
- Timestamp executeAt = validate.executeAt();
- switch (known.executeAt)
- {
- default: throw new AssertionError("Unhandled KnownExecuteAt: " + known.executeAt);
- case ExecuteAtErased:
- case ExecuteAtUnknown:
- Invariants.checkState(executeAt == null || executeAt.compareTo(validate.txnId()) >= 0);
- break;
- case ExecuteAtKnown:
- case ExecuteAtProposed:
- Invariants.checkState(executeAt != null && executeAt.compareTo(validate.txnId()) >= 0);
- break;
- case NoExecuteAt:
- Invariants.checkState(executeAt.equals(Timestamp.NONE));
- break;
- }
- }
- {
- PartialDeps deps = validate.partialDeps();
- switch (known.deps)
- {
- default: throw new AssertionError("Unhandled KnownDeps: " + known.deps);
- case DepsUnknown:
- case DepsErased:
- case NoDeps:
- Invariants.checkState(deps == null);
- break;
- case DepsProposed:
- case DepsCommitted:
- case DepsKnown:
- Invariants.checkState(deps != null);
- break;
- }
- }
- {
- Writes writes = validate.writes();
- Result result = validate.result();
- switch (known.outcome)
- {
- default: throw new AssertionError("Unhandled Outcome: " + known.outcome);
- case Apply:
- Invariants.checkState(writes != null, "Writes is null %s", validate);
- Invariants.checkState(result != null, "Result is null %s", validate);
- break;
- case Invalidated:
- Invariants.checkState(validate.durability().isMaybeInvalidated(), "%s is not invalidated", validate.durability());
- case Unknown:
- Invariants.checkState(validate.durability() != Local);
- case Erased:
- case WasApply:
- Invariants.checkState(writes == null, "Writes exist for %s", validate);
- Invariants.checkState(result == null, "Results exist %s", validate);
- break;
- }
- }
- switch (validate.saveStatus().execution)
- {
- case NotReady:
- case CleaningUp:
- break;
- case ReadyToExclude:
- Invariants.checkState(validate.saveStatus() != SaveStatus.Committed || validate.asCommitted().waitingOn == null);
- break;
- case WaitingToExecute:
- case ReadyToExecute:
- case Applied:
- case Applying:
- case WaitingToApply:
- Invariants.checkState(validate.participants().executes() != null);
- Invariants.checkState(validate.asCommitted().waitingOn != null);
- break;
- }
- return validate;
- }
+ @Override
+ public String toString()
+ {
+ return "Command@" + System.identityHashCode(this) + '{' + txnId + ':' + saveStatus + '}';
}
@Override
- public final int hashCode()
+ public final TxnId txnId()
{
- throw new UnsupportedOperationException();
+ return txnId;
}
/**
@@ -385,8 +210,33 @@ public abstract class Command implements CommonAttributes
* or any route will do
*/
@Override
- @Nullable
- public final Route<?> route() { return participants().route(); }
+ public final StoreParticipants participants()
+ {
+ return participants;
+ }
+
+ @Override
+ public final Ballot promised()
+ {
+ return promised;
+ }
+
+ @Override
+ public final Durability durability()
+ {
+ return Command.durability(durability, saveStatus());
+ }
+
+ public final SaveStatus saveStatus()
+ {
+ return saveStatus;
+ }
+
+ @Override
+ public final int hashCode()
+ {
+ throw new UnsupportedOperationException();
+ }
/**
* We require that this is a FullRoute for all states where isDefinitionKnown().
@@ -399,8 +249,8 @@ public abstract class Command implements CommonAttributes
* preferably introduce two variations so callers can declare whether they need the full shard's route
* or any route will do
*/
- @Override
- public abstract StoreParticipants participants();
+ @Nullable
+ public final Route<?> route() { return participants().route(); }
public Participants<?> maxContactable()
{
@@ -423,59 +273,28 @@ public abstract class Command implements CommonAttributes
}
@Override
- public abstract TxnId txnId();
- public abstract Ballot promised();
- @Override
- public abstract Durability durability();
- public abstract SaveStatus saveStatus();
+ public final Timestamp executeAt() { return executeAt; }
- /**
- * Only meaningful when txnId.kind().awaitsOnlyDeps()
- */
- public Timestamp executesAtLeast() { return executeAt(); }
-
- static boolean isSameClass(Command command, Class<? extends Command> klass)
- {
- return command.getClass() == klass;
- }
-
- private static void checkNewBallot(Ballot current, Ballot next, String name)
- {
- if (next.compareTo(current) < 0)
- throw new IllegalArgumentException(format("Cannot update %s ballot from %s to %s. New ballot is less than current", name, current, next));
- }
-
- private static void checkPromised(Command command, Ballot ballot)
- {
- checkNewBallot(command.promised(), ballot, "promised");
- }
-
- private static void checkAccepted(Command command, Ballot ballot)
- {
- checkNewBallot(command.acceptedOrCommitted(), ballot, "accepted");
- }
-
- private static void checkSameClass(Command command, Class<? extends Command> klass, String errorMsg)
- {
- if (!isSameClass(command, klass))
- throw illegalArgument(errorMsg + format(" expected %s got %s", klass.getSimpleName(), command.getClass().getSimpleName()));
- }
-
- public abstract Timestamp executeAt();
- public abstract Ballot acceptedOrCommitted();
+ @Override
+ public final Ballot acceptedOrCommitted() { return acceptedOrCommitted; }
@Override
- public abstract PartialTxn partialTxn();
+ public final @Nullable PartialTxn partialTxn() { return partialTxn; }
@Override
- public abstract @Nullable PartialDeps partialDeps();
+ public final @Nullable PartialDeps partialDeps() { return partialDeps; }
public @Nullable Writes writes() { return null; }
public @Nullable Result result() { return null; }
+ public @Nullable WaitingOn waitingOn() { return null; }
+ /**
+ * Only meaningful when txnId.kind().awaitsOnlyDeps()
+ */
+ public @Nullable Timestamp executesAtLeast() { return null; }
public final Timestamp executeAtIfKnownElseTxnId()
{
- if (known().executeAt == ExecuteAtKnown)
+ if (known().is(ExecuteAtKnown))
return executeAt();
return txnId();
}
@@ -487,16 +306,11 @@ public abstract class Command implements CommonAttributes
public final Timestamp executeAtIfKnown(Timestamp orElse)
{
- if (known().executeAt == ExecuteAtKnown)
+ if (known().is(ExecuteAtKnown))
return executeAt();
return orElse;
}
- public final boolean executesInFutureEpoch()
- {
- return known().executeAt == ExecuteAtKnown && executeAt().epoch() > txnId().epoch();
- }
-
public final Timestamp executeAtOrTxnId()
{
Timestamp executeAt = executeAt();
@@ -524,103 +338,44 @@ public abstract class Command implements CommonAttributes
return status().compareTo(status) >= 0;
}
- public boolean has(Known known)
- {
- return known.isSatisfiedBy(saveStatus().known);
- }
-
public boolean is(Status status)
{
return status() == status;
}
- public final boolean isDefined()
- {
- if (status().hasBeen(Status.Truncated))
- return false;
- return status().hasBeen(Status.PreAccepted);
- }
-
- public final PreAccepted asWitnessed()
- {
- return Invariants.cast(this, PreAccepted.class);
- }
-
- public final boolean isAccepted()
- {
- return status().hasBeen(Status.AcceptedInvalidate);
- }
-
public final Accepted asAccepted()
{
return Invariants.cast(this, Accepted.class);
}
-
- public final boolean isCommitted()
- {
- SaveStatus saveStatus = saveStatus();
- return saveStatus.hasBeen(Status.Committed) && !saveStatus.is(Invalidated);
- }
-
- public final boolean isStable()
- {
- SaveStatus saveStatus = saveStatus();
- return isStable(saveStatus);
- }
-
- public static boolean isStable(SaveStatus saveStatus)
- {
- return saveStatus.hasBeen(Status.Stable) && !saveStatus.is(Invalidated);
- }
-
public final Committed asCommitted()
{
return Invariants.cast(this, Committed.class);
}
-
public final Executed asExecuted()
{
return Invariants.cast(this, Executed.class);
}
- public abstract Command updateAttributes(CommonAttributes attrs, Ballot promised);
- public final Command updateAttributes(CommonAttributes attrs)
- {
- return updateAttributes(attrs, promised());
- }
- public final Command updatePromised(Ballot promised)
- {
- return updateAttributes(this, promised);
- }
- public abstract Command updateParticipants(StoreParticipants participants);
+ public final Command updatePromised(Ballot promised) { return updateAttributes(participants(), promised, durability()); }
+ public final Command updateParticipants(StoreParticipants participants) { return updateAttributes(participants, promised(), durability()); }
+ public final Command updateDurability(Durability durability) { return updateAttributes(participants(), promised(), durability); }
+ public abstract Command updateAttributes(StoreParticipants participants, Ballot promised, Durability durability);
- public static class NotDefined extends AbstractCommand
+ public static class NotDefined extends Command
{
- NotDefined(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised)
+ public static NotDefined notDefined(ICommand copy)
{
- super(txnId, status, durability, participants, promised);
+ return validate(notDefined(copy, copy.promised()));
}
- NotDefined(CommonAttributes common, SaveStatus status, Ballot promised)
+ public static NotDefined notDefined(ICommand copy, Ballot promised)
{
- super(common, status, promised);
+ return validate(new NotDefined(copy.txnId(), SaveStatus.NotDefined, copy.durability(), copy.participants(), promised));
}
- @Override
- public Command updateAttributes(CommonAttributes attrs, Ballot promised)
- {
- return validate(new NotDefined(attrs, initialise(saveStatus()), promised));
- }
-
- @Override
- public Command updateParticipants(StoreParticipants participants)
+ public static NotDefined notDefined(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised)
{
- return new NotDefined(txnId(), SaveStatus.NotDefined, durability(), participants, promised());
- }
-
- public static NotDefined notDefined(CommonAttributes common, Ballot promised)
- {
- return validate(new NotDefined(common, SaveStatus.NotDefined, promised));
+ return validate(new NotDefined(txnId, status, durability, participants, promised));
}
public static NotDefined uninitialised(TxnId txnId)
@@ -628,28 +383,15 @@ public abstract class Command implements CommonAttributes
return validate(new NotDefined(txnId, Uninitialised, NotDurable, StoreParticipants.empty(txnId), Ballot.ZERO));
}
- @Override
- public Timestamp executeAt()
- {
- return null;
- }
-
- @Override
- public Ballot acceptedOrCommitted()
- {
- return Ballot.ZERO;
- }
-
- @Override
- public PartialTxn partialTxn()
+ NotDefined(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised)
{
- return null;
+ super(txnId, status, durability, participants, promised, null, null, null, Ballot.ZERO);
}
@Override
- public @Nullable PartialDeps partialDeps()
+ public Command updateAttributes(StoreParticipants participants, Ballot promised, Durability durability)
{
- return null;
+ return validate(new NotDefined(txnId(), initialise(saveStatus()), durability, participants, promised));
}
private static SaveStatus initialise(SaveStatus saveStatus)
@@ -658,41 +400,8 @@ public abstract class Command implements CommonAttributes
}
}
- public static class Truncated extends AbstractCommand
+ public static class Truncated extends Command
{
- @Nullable final Timestamp executeAt;
- @Nullable final Writes writes;
- @Nullable final Result result;
-
- public Truncated(CommonAttributes commonAttributes, SaveStatus saveStatus, @Nullable Timestamp executeAt, @Nullable Writes writes, @Nullable Result result)
- {
- // TODO (expected): is Ballot.MAX helpful or harmful here?
- // We have to special-case partial truncation anyway as we can infinite loop if we think there's a superseding recovery.
- super(commonAttributes, saveStatus, Ballot.MAX);
- this.executeAt = executeAt;
- this.writes = writes;
- this.result = result;
- }
-
- public Truncated(TxnId txnId, SaveStatus saveStatus, Durability durability, @Nonnull StoreParticipants participants, @Nullable Timestamp executeAt, @Nullable Writes writes, @Nullable Result result)
- {
- super(txnId, saveStatus, durability, participants, Ballot.MAX);
- this.executeAt = executeAt;
- this.writes = writes;
- this.result = result;
- }
-
- @Override
- public boolean equals(Object o)
- {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- if (!super.equals(o)) return false;
- Truncated that = (Truncated) o;
- return Objects.equals(executeAt, that.executeAt)
- && Objects.equals(writes, that.writes)
- && Objects.equals(result, that.result);
- }
public static Truncated erased(Command command)
{
@@ -722,7 +431,7 @@ public abstract class Command implements CommonAttributes
public static Truncated truncatedApply(Command command, @Nonnull StoreParticipants participants)
{
- Invariants.checkArgument(command.known().executeAt.isDecidedAndKnownToExecute());
+ Invariants.checkArgument(command.known().executeAt().isDecidedAndKnownToExecute());
Durability durability = Durability.mergeAtLeast(command.durability(), ShardUniversal);
if (command.txnId().awaitsOnlyDeps())
{
@@ -740,29 +449,39 @@ public abstract class Command implements CommonAttributes
return validate(new Truncated(command.txnId(), SaveStatus.TruncatedApplyWithOutcome, durability, command.participants(), command.executeAt(), command.writes, command.result));
}
- public static Truncated truncatedApply(CommonAttributes common, SaveStatus saveStatus, Timestamp executeAt, Writes writes, Result result)
+ public static Truncated truncatedApply(ICommand common, SaveStatus saveStatus, Timestamp executeAt, Writes writes, Result result)
{
- Invariants.checkArgument(!common.txnId().awaitsOnlyDeps());
- Durability durability = checkTruncatedApplyInvariants(common, saveStatus, executeAt);
- return validate(new Truncated(common.txnId(), saveStatus, durability, common.participants(), executeAt, writes, result));
+ return truncatedApply(common.txnId(), saveStatus, common.durability(), common.participants(), executeAt, writes, result);
}
- public static Truncated truncatedApply(CommonAttributes common, SaveStatus saveStatus, Timestamp executeAt, Writes writes, Result result, @Nullable Timestamp dependencyExecutesAt)
+ public static Truncated truncatedApply(TxnId txnId, SaveStatus saveStatus, Durability durability, StoreParticipants participants, Timestamp executeAt, Writes writes, Result result)
{
- if (!common.txnId().awaitsOnlyDeps())
+ Invariants.checkArgument(!txnId.awaitsOnlyDeps());
+ durability = checkTruncatedApplyInvariants(durability, saveStatus, executeAt);
+ return validate(new Truncated(txnId, saveStatus, durability, participants, executeAt, writes, result));
+ }
+
+ public static Truncated truncatedApply(ICommand common, SaveStatus saveStatus, Timestamp executeAt, Writes writes, Result result, @Nullable Timestamp dependencyExecutesAt)
+ {
+ return truncatedApply(common.txnId(), saveStatus, common.durability(), common.participants(), executeAt, writes, result, dependencyExecutesAt);
+ }
+
+ public static Truncated truncatedApply(TxnId txnId, SaveStatus saveStatus, Durability durability, StoreParticipants participants, Timestamp executeAt, Writes writes, Result result, @Nullable Timestamp dependencyExecutesAt)
+ {
+ if (!txnId.awaitsOnlyDeps())
{
Invariants.checkState(dependencyExecutesAt == null);
- return truncatedApply(common, saveStatus, executeAt, writes, result);
+ return truncatedApply(txnId, saveStatus, durability, participants, executeAt, writes, result);
}
- Durability durability = checkTruncatedApplyInvariants(common, saveStatus, executeAt);
- return validate(new TruncatedAwaitsOnlyDeps(common.txnId(), saveStatus, durability, common.participants(), executeAt, writes, result, dependencyExecutesAt));
+ durability = checkTruncatedApplyInvariants(durability, saveStatus, executeAt);
+ return validate(new TruncatedAwaitsOnlyDeps(txnId, saveStatus, durability, participants, executeAt, writes, result, dependencyExecutesAt));
}
- private static Durability checkTruncatedApplyInvariants(CommonAttributes common, SaveStatus saveStatus, Timestamp executeAt)
+ private static Durability checkTruncatedApplyInvariants(Durability durability, SaveStatus saveStatus, Timestamp executeAt)
{
Invariants.checkArgument(executeAt != null);
Invariants.checkArgument(saveStatus == SaveStatus.TruncatedApply || saveStatus == SaveStatus.TruncatedApplyWithDeps || saveStatus == SaveStatus.TruncatedApplyWithOutcome);
- return Durability.mergeAtLeast(common.durability(), ShardUniversal);
+ return Durability.mergeAtLeast(durability, ShardUniversal);
}
public static Truncated invalidated(Command command)
@@ -775,55 +494,57 @@ public abstract class Command implements CommonAttributes
{
// TODO (expected): migrate to using null for executeAt when invalidated
// TODO (expected): is UniversalOrInvalidated correct here? Should we have a lower implication pure Invalidated?
+ // NOTE: we *must* save participants here so that on replay we properly repopulate CommandsForKey
+ // (otherwise we may see this as a transitive transaction, but never mark it invalidated)
return validate(new Truncated(txnId, SaveStatus.Invalidated, UniversalOrInvalidated, participants, Timestamp.NONE, null, null));
}
- @Override
- public Timestamp executeAt()
- {
- return executeAt;
- }
-
- @Override
- public @Nullable Writes writes()
- {
- return writes;
- }
+ @Nullable final Writes writes;
+ @Nullable final Result result;
- @Override
- public @Nullable Result result()
+ public Truncated(ICommand copy, SaveStatus saveStatus)
{
- return result;
+ // TODO (expected): is Ballot.MAX helpful or harmful here?
+ // We have to special-case partial truncation anyway as we can infinite loop if we think there's a superseding recovery.
+ super(copy, saveStatus);
+ this.writes = copy.writes();
+ this.result = copy.result();
}
- @Override
- public Ballot acceptedOrCommitted()
+ public Truncated(TxnId txnId, SaveStatus saveStatus, Durability durability, @Nonnull StoreParticipants participants, @Nullable Timestamp executeAt, @Nullable Writes writes, @Nullable Result result)
{
- return Ballot.MAX;
+ super(txnId, saveStatus, durability, participants, Ballot.MAX, executeAt, null, null, Ballot.MAX);
+ this.writes = writes;
+ this.result = result;
}
@Override
- public PartialTxn partialTxn()
+ public boolean equals(Object o)
{
- return null;
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+ Truncated that = (Truncated) o;
+ return Objects.equals(writes, that.writes)
+ && Objects.equals(result, that.result);
}
@Override
- public @Nullable PartialDeps partialDeps()
+ public @Nullable Writes writes()
{
- return null;
+ return writes;
}
@Override
- public Command updateAttributes(CommonAttributes attrs, Ballot promised)
+ public @Nullable Result result()
{
- return validate(new Truncated(txnId(), saveStatus(), attrs.durability(), attrs.participants(), executeAt, writes, result));
+ return result;
}
@Override
- public Command updateParticipants(StoreParticipants participants)
+ public Command updateAttributes(StoreParticipants participants, Ballot promised, Durability durability)
{
- return validate(new Truncated(txnId(), saveStatus(), durability(), participants, executeAt, writes, result));
+ return validate(new Truncated(txnId(), saveStatus(), durability, participants, executeAt(), writes, result));
}
}
@@ -835,9 +556,9 @@ public abstract class Command implements CommonAttributes
*/
@Nullable final Timestamp executesAtLeast;
- public TruncatedAwaitsOnlyDeps(CommonAttributes commonAttributes, SaveStatus saveStatus, @Nullable Timestamp executeAt, @Nullable Writes writes, @Nullable Result result, @Nullable Timestamp executesAtLeast)
+ public TruncatedAwaitsOnlyDeps(ICommand cmd, SaveStatus saveStatus, Timestamp executesAtLeast)
{
- super(commonAttributes, saveStatus, executeAt, writes, result);
+ super(cmd, saveStatus);
this.executesAtLeast = executesAtLeast;
}
@@ -856,183 +577,105 @@ public abstract class Command implements CommonAttributes
public boolean equals(Object o)
{
if (!super.equals(o)) return false;
- return Objects.equals(executesAtLeast, ((TruncatedAwaitsOnlyDeps)o).executesAtLeast);
- }
-
- @Override
- public Command updateAttributes(CommonAttributes attrs, Ballot promised)
- {
- return validate(new TruncatedAwaitsOnlyDeps(txnId(), saveStatus(), attrs.durability(), attrs.participants(), executeAt, writes, result, executesAtLeast));
- }
-
- @Override
- public Command updateParticipants(StoreParticipants participants)
- {
- return validate(new TruncatedAwaitsOnlyDeps(txnId(), saveStatus(), durability(), participants, executeAt, writes, result, executesAtLeast));
- }
- }
-
- public static class PreAccepted extends AbstractCommand
- {
- public static PreAccepted preAccepted(CommonAttributes common, Timestamp executeAt, Ballot promised)
- {
- return validate(new PreAccepted(common, SaveStatus.PreAccepted, promised, executeAt));
- }
-
- public static PreAccepted preAccepted(PreAccepted command, CommonAttributes common, Ballot promised)
- {
- checkPromised(command, promised);
- checkSameClass(command, PreAccepted.class, "Cannot update");
- Invariants.checkArgument(command.getClass() == PreAccepted.class);
- return preAccepted(common, command.executeAt(), promised);
- }
-
- private final @Nullable Timestamp executeAt;
- private final PartialTxn partialTxn;
- private final @Nullable PartialDeps partialDeps;
-
- private PreAccepted(CommonAttributes common, SaveStatus status, Ballot promised, Timestamp executeAt)
- {
- this(common, status, promised, executeAt, common.partialTxn(), common.partialDeps());
- }
-
- private PreAccepted(CommonAttributes common, SaveStatus status, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps)
- {
- super(common, status, promised);
- this.executeAt = executeAt;
- this.partialTxn = partialTxn;
- this.partialDeps = partialDeps;
- }
-
- private PreAccepted(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps)
- {
- super(txnId, status, durability, participants, promised);
- this.executeAt = executeAt;
- this.partialTxn = partialTxn;
- this.partialDeps = partialDeps;
+ return Objects.equals(executesAtLeast, ((TruncatedAwaitsOnlyDeps)o).executesAtLeast);
}
@Override
- public Timestamp executeAt()
+ public Command updateAttributes(StoreParticipants participants, Ballot promised, Durability durability)
{
- return executeAt;
+ return validate(new TruncatedAwaitsOnlyDeps(txnId(), saveStatus(), durability, participants, executeAt(), writes, result, executesAtLeast));
}
+ }
- @Override
- public Ballot acceptedOrCommitted()
+ public static class PreAccepted extends Command
+ {
+ public static PreAccepted preaccepted(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps)
{
- return Ballot.ZERO;
+ return validate(new PreAccepted(txnId, status, durability, participants, promised, executeAt, partialTxn, partialDeps));
}
- @Override
- public PartialTxn partialTxn()
+ public static PreAccepted preaccepted(ICommand common, SaveStatus newSaveStatus)
{
- return partialTxn;
+ return validate(new PreAccepted(common, newSaveStatus));
}
- @Override
- public @Nullable PartialDeps partialDeps()
+ private PreAccepted(ICommand copy, SaveStatus status)
{
- return partialDeps;
+ super(copy, status);
}
- @Override
- public Command updateAttributes(CommonAttributes attrs, Ballot promised)
+ private PreAccepted(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps)
{
- return new PreAccepted(attrs, saveStatus(), promised, executeAt());
+ super(txnId, status, durability, participants, promised, executeAt, partialTxn, partialDeps, Ballot.ZERO);
}
- @Override
- public Command updateParticipants(StoreParticipants participants)
+ private PreAccepted(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, Ballot acceptedOrCommitted)
{
- return new PreAccepted(txnId(), saveStatus(), durability(), participants, promised(), executeAt, partialTxn, partialDeps);
+ super(txnId, status, durability, participants, promised, executeAt, partialTxn, partialDeps, acceptedOrCommitted);
}
@Override
- public boolean equals(Object o)
+ public Command updateAttributes(StoreParticipants participants, Ballot promised, Durability durability)
{
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- if (!super.equals(o)) return false;
- PreAccepted that = (PreAccepted) o;
- return Objects.equals(executeAt, that.executeAt)
- && Objects.equals(partialTxn, that.partialTxn)
- && Objects.equals(partialDeps, that.partialDeps);
+ return new PreAccepted(txnId(), saveStatus(), durability, participants, promised, executeAt(), partialTxn(), partialDeps());
}
}
- public static class AcceptedInvalidateWithoutDefinition extends NotDefined
+ public static class NotAcceptedWithoutDefinition extends Command
{
- public static AcceptedInvalidateWithoutDefinition acceptedInvalidate(CommonAttributes common, Ballot promised, Ballot accepted)
- {
- return new AcceptedInvalidateWithoutDefinition(common, promised, accepted);
- }
-
- private final Ballot accepted;
-
- private AcceptedInvalidateWithoutDefinition(CommonAttributes common, Ballot promised, Ballot accepted)
+ public static NotAcceptedWithoutDefinition notAccepted(ICommand copy, SaveStatus saveStatus)
{
- super(common, SaveStatus.AcceptedInvalidate, promised);
- this.accepted = accepted;
+ return new NotAcceptedWithoutDefinition(copy, saveStatus);
}
- private AcceptedInvalidateWithoutDefinition(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Ballot accepted)
+ public static NotAcceptedWithoutDefinition notAccepted(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Ballot accepted, PartialDeps partialDeps)
{
- super(txnId, status, durability, participants, promised);
- this.accepted = accepted;
+ return new NotAcceptedWithoutDefinition(txnId, status, durability, participants, promised, accepted, partialDeps);
}
- @Override
- public Ballot acceptedOrCommitted()
+ public static NotAcceptedWithoutDefinition acceptedInvalidate(ICommand copy)
{
- return accepted;
+ return new NotAcceptedWithoutDefinition(copy, SaveStatus.AcceptedInvalidate);
}
- @Override
- public Command updateAttributes(CommonAttributes attrs, Ballot promised)
+ private NotAcceptedWithoutDefinition(ICommand copy, SaveStatus saveStatus)
{
- return new AcceptedInvalidateWithoutDefinition(attrs, promised, accepted);
+ super(copy, saveStatus);
}
- @Override
- public Command updateParticipants(StoreParticipants participants)
+ private NotAcceptedWithoutDefinition(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Ballot accepted, @Nullable PartialDeps partialDeps)
{
- return new AcceptedInvalidateWithoutDefinition(txnId(), saveStatus(), durability(), participants, promised(), accepted);
+ super(txnId, status, durability, participants, promised, null, null, partialDeps, accepted);
}
@Override
- public boolean equals(Object o)
+ public Command updateAttributes(StoreParticipants participants, Ballot promised, Durability durability)
{
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- if (!super.equals(o)) return false;
- AcceptedInvalidateWithoutDefinition that = (AcceptedInvalidateWithoutDefinition) o;
- return Objects.equals(accepted, that.accepted);
+ return new NotAcceptedWithoutDefinition(txnId(), saveStatus(), durability, participants, promised, acceptedOrCommitted(), partialDeps());
}
}
public static class Accepted extends PreAccepted
{
- private final Ballot acceptedOrCommitted;
+ public static Accepted accepted(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, Ballot acceptedOrCommitted)
+ {
+ return validate(new Accepted(txnId, status, durability, participants, promised, executeAt, partialTxn, partialDeps, acceptedOrCommitted));
+ }
- Accepted(CommonAttributes common, SaveStatus status, Ballot promised, Timestamp executeAt, Ballot acceptedOrCommitted)
+ public static Accepted accepted(ICommand common, SaveStatus status)
{
- super(common, status, promised, executeAt);
- this.acceptedOrCommitted = acceptedOrCommitted;
- validateDeps(participants(), partialDeps());
+ return validate(new Accepted(common, status));
}
- Accepted(CommonAttributes common, SaveStatus status, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, Ballot acceptedOrCommitted)
+ Accepted(ICommand copy, SaveStatus status)
{
- super(common, status, promised, executeAt, partialTxn, partialDeps);
- this.acceptedOrCommitted = acceptedOrCommitted;
- validateDeps(participants(), partialDeps);
+ super(copy, status);
+ validateDeps(participants(), partialDeps());
}
Accepted(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, Ballot acceptedOrCommitted)
{
- super(txnId, status, durability, participants, promised, executeAt, partialTxn, partialDeps);
- this.acceptedOrCommitted = acceptedOrCommitted;
+ super(txnId, status, durability, participants, promised, executeAt, partialTxn, partialDeps, acceptedOrCommitted);
validateDeps(participants(), partialDeps);
}
@@ -1048,62 +691,43 @@ public abstract class Command implements CommonAttributes
}
@Override
- public Command updateAttributes(CommonAttributes attrs, Ballot promised)
- {
- return validate(new Accepted(attrs, saveStatus(), promised, executeAt(), acceptedOrCommitted()));
- }
-
- @Override
- public Command updateParticipants(StoreParticipants participants)
+ public Command updateAttributes(StoreParticipants participants, Ballot promised, Durability durability)
{
- return new Accepted(txnId(), saveStatus(), durability(), participants, promised(), executeAt(), partialTxn(), partialDeps(), acceptedOrCommitted);
+ return validate(new Accepted(txnId(), saveStatus(), durability, participants, promised, executeAt(), partialTxn(), partialDeps(), acceptedOrCommitted()));
}
+ }
- @Override
- public boolean equals(Object o)
+ public static class Committed extends Accepted
+ {
+ public static Committed committed(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, Ballot acceptedOrCommitted, WaitingOn waitingOn)
{
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- if (!super.equals(o)) return false;
- Accepted that = (Accepted) o;
- return Objects.equals(acceptedOrCommitted, that.acceptedOrCommitted);
+ return validate(new Committed(txnId, status, durability, participants, promised, executeAt, partialTxn, partialDeps, acceptedOrCommitted, waitingOn));
}
- public static Accepted accepted(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted)
+ public static Committed committed(ICommand copy, SaveStatus status)
{
- return validate(new Accepted(common, status, promised, executeAt, accepted));
+ return validate(new Committed(copy, status));
}
- static Accepted accepted(Accepted command, CommonAttributes common, SaveStatus status, Ballot promised)
+ public static Committed committed(ICommand copy, SaveStatus status, WaitingOn overrideWaitingOn)
{
- checkPromised(command, promised);
- checkSameClass(command, Accepted.class, "Cannot update");
- return validate(new Accepted(common, status, promised, command.executeAt(), command.acceptedOrCommitted()));
+ Invariants.checkState(status == (overrideWaitingOn.isWaiting() ? SaveStatus.Stable : ReadyToExecute));
+ return validate(new Committed(copy, status, overrideWaitingOn));
}
- static Accepted accepted(Accepted command, CommonAttributes common, Ballot promised)
- {
- return accepted(command, common, command.saveStatus(), promised);
- }
+ public final WaitingOn waitingOn;
- @Override
- public Ballot acceptedOrCommitted()
+ Committed(ICommand copy, SaveStatus status)
{
- return acceptedOrCommitted;
+ this(copy, status, copy.waitingOn());
}
- }
- public static class Committed extends Accepted
- {
- public final WaitingOn waitingOn;
-
- Committed(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted, WaitingOn waitingOn)
+ Committed(ICommand copy, SaveStatus status, WaitingOn overrideWaitingOn)
{
- super(common, status, promised, executeAt, accepted);
- this.waitingOn = waitingOn;
- Invariants.nonNull(common.participants());
- Invariants.nonNull(common.route());
- Invariants.checkState(common.route().kind().isFullRoute(), "Expected a full route but given %s", common.route().kind());
+ super(copy, status);
+ this.waitingOn = overrideWaitingOn;
+ Invariants.nonNull(copy.participants());
+ Invariants.checkState(Route.isFullRoute(copy.participants().route()), "Expected a full route but given %s", copy.participants().route());
}
Committed(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, Ballot acceptedOrCommitted, WaitingOn waitingOn)
@@ -1113,24 +737,18 @@ public abstract class Command implements CommonAttributes
}
@Override
- public Timestamp executesAtLeast()
+ public final Timestamp executesAtLeast()
{
- if (!txnId().awaitsOnlyDeps()) return executeAt();
+ if (!txnId().awaitsOnlyDeps()) return null;
if (status().hasBeen(Stable)) return waitingOn.executeAtLeast(executeAt());
return null;
}
@Override
- public Command updateAttributes(CommonAttributes attrs, Ballot promised)
- {
- return validate(new Committed(attrs, saveStatus(), executeAt(), promised, acceptedOrCommitted(), waitingOn()));
- }
-
- @Override
- public Command updateParticipants(StoreParticipants participants)
+ public Command updateAttributes(StoreParticipants participants, Ballot promised, Durability durability)
{
Invariants.checkState(partialDeps().covers(participants.stillTouches()));
- return new Committed(txnId(), saveStatus(), durability(), participants, promised(), executeAt(), partialTxn(), partialDeps(), acceptedOrCommitted(), waitingOn);
+ return validate(new Committed(txnId(), saveStatus(), durability, participants, promised, executeAt(), partialTxn(), partialDeps(), acceptedOrCommitted(), waitingOn));
}
@Override
@@ -1143,75 +761,63 @@ public abstract class Command implements CommonAttributes
return Objects.equals(waitingOn, committed.waitingOn);
}
- private static Committed committed(Committed command, CommonAttributes common, Ballot promised, SaveStatus status, WaitingOn waitingOn)
+ public final WaitingOn waitingOn()
{
- checkPromised(command, promised);
- checkSameClass(command, Committed.class, "Cannot update");
- return validate(new Committed(common, status, command.executeAt(), promised, command.acceptedOrCommitted(), waitingOn));
+ return waitingOn;
}
- static Committed committed(Committed command, CommonAttributes common, Ballot promised)
+ public boolean isWaitingOnDependency()
{
- return committed(command, common, promised, command.saveStatus(), command.waitingOn());
+ return waitingOn.isWaiting();
}
+ }
- static Committed committed(Committed command, CommonAttributes common, SaveStatus status)
+ public static class Executed extends Committed
+ {
+ public static Executed executed(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, Ballot acceptedOrCommitted, WaitingOn waitingOn, Writes writes, Result result)
{
- return committed(command, common, command.promised(), status, command.waitingOn());
+ return validate(new Executed(txnId, status, durability, participants, promised, executeAt, partialTxn, partialDeps, acceptedOrCommitted, waitingOn, writes, result));
}
- public static Committed committed(Committed command, CommonAttributes common, WaitingOn waitingOn)
+ public static Executed executed(ICommand common, SaveStatus status)
{
- return committed(command, common, command.promised(), command.saveStatus(), waitingOn);
+ return validate(new Executed(common, status));
}
- public static Committed committed(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted, WaitingOn waitingOn)
+ public static Executed executed(ICommand common, SaveStatus status, WaitingOn waitingOn)
{
- return validate(new Committed(common, status, executeAt, promised, accepted, waitingOn));
+ return validate(new Executed(common, status, waitingOn));
}
- public WaitingOn waitingOn()
- {
- return waitingOn;
- }
+ private final Writes writes;
+ private final Result result;
- public boolean isWaitingOnDependency()
+ private Executed(ICommand copy, SaveStatus status)
{
- return waitingOn.isWaiting();
+ this(copy, status, copy.waitingOn());
}
- }
- public static class Executed extends Committed
- {
- private final Writes writes;
- private final Result result;
-
- public Executed(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted, WaitingOn waitingOn, Writes writes, Result result)
+ private Executed(ICommand copy, SaveStatus status, WaitingOn overrideWaitingOn)
{
- super(common, status, executeAt, promised, accepted, waitingOn);
- Invariants.checkState(txnId().kind() != Txn.Kind.Write || writes != null);
- this.writes = writes;
- this.result = result;
+ super(copy, status, overrideWaitingOn);
+ this.writes = copy.writes();
+ this.result = copy.result();
+ Invariants.checkState(txnId().kind() != Write || writes != null);
}
private Executed(TxnId txnId, SaveStatus status, Durability durability, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, Ballot acceptedOrCommitted, WaitingOn waitingOn, Writes writes, Result result)
{
super(txnId, status, durability, participants, promised, executeAt, partialTxn, partialDeps, acceptedOrCommitted, waitingOn);
- Invariants.checkState(txnId().kind() != Txn.Kind.Write || writes != null);
+ Invariants.checkState(txnId().kind() != Write || writes != null);
this.writes = writes;
this.result = result;
}
@Override
- public Command updateAttributes(CommonAttributes attrs, Ballot promised)
- {
- return validate(new Executed(attrs, saveStatus(), executeAt(), promised, acceptedOrCommitted(), waitingOn(), writes, result));
- }
-
- @Override
- public Command updateParticipants(StoreParticipants participants)
+ public Command updateAttributes(StoreParticipants participants, Ballot promised, Durability durability)
{
- return new Executed(txnId(), saveStatus(), durability(), participants, promised(), executeAt(), partialTxn(), partialDeps(), acceptedOrCommitted(), waitingOn, writes, result);
+ Invariants.checkState(partialDeps().covers(participants.stillTouches()));
+ return validate(new Executed(txnId(), saveStatus(), durability, participants, promised, executeAt(), partialTxn(), partialDeps(), acceptedOrCommitted(), waitingOn, writes, result));
}
@Override
@@ -1225,32 +831,6 @@ public abstract class Command implements CommonAttributes
&& Objects.equals(result, executed.result);
}
- public static Executed executed(Executed command, CommonAttributes common, SaveStatus status, Ballot promised, WaitingOn waitingOn)
- {
- checkSameClass(command, Executed.class, "Cannot update");
- return validate(new Executed(common, status, command.executeAt(), promised, command.acceptedOrCommitted(), waitingOn, command.writes(), command.result()));
- }
-
- public static Executed executed(Executed command, CommonAttributes common, SaveStatus status)
- {
- return executed(command, common, status, command.promised(), command.waitingOn());
- }
-
- public static Executed executed(Executed command, CommonAttributes common, WaitingOn waitingOn)
- {
- return executed(command, common, command.saveStatus(), command.promised(), waitingOn);
- }
-
- public static Executed executed(Executed command, CommonAttributes common, Ballot promised)
- {
- return executed(command, common, command.saveStatus(), promised, command.waitingOn());
- }
-
- public static Executed executed(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted, WaitingOn waitingOn, Writes writes, Result result)
- {
- return validate(new Executed(common, status, executeAt, promised, accepted, waitingOn, writes, result));
- }
-
public Writes writes()
{
return writes;
@@ -1262,40 +842,6 @@ public abstract class Command implements CommonAttributes
}
}
- public static class Minimal
- {
- public final TxnId txnId;
- public final SaveStatus saveStatus;
- public final StoreParticipants participants;
- public final Status.Durability durability;
- public final Timestamp executeAt;
- public final Writes writes;
-
- public Minimal(TxnId txnId, SaveStatus saveStatus, StoreParticipants participants, Status.Durability durability, Timestamp executeAt, Writes writes)
- {
- this.txnId = txnId;
- this.saveStatus = saveStatus;
- this.participants = participants;
- this.durability = durability;
- this.executeAt = executeAt;
- this.writes = writes;
- }
-
- @Override
- public boolean equals(Object object)
- {
- if (this == object) return true;
- if (object == null || getClass() != object.getClass()) return false;
- Minimal minimal = (Minimal) object;
- return Objects.equals(txnId, minimal.txnId) && saveStatus == minimal.saveStatus && Objects.equals(participants, minimal.participants) && durability == minimal.durability && Objects.equals(executeAt, minimal.executeAt) && Objects.equals(writes, minimal.writes);
- }
-
- @Override
- public final int hashCode()
- {
- throw new UnsupportedOperationException();
- }
- }
public static class WaitingOn
{
@@ -1480,6 +1026,35 @@ public abstract class Command implements CommonAttributes
&& Objects.equals(this.appliedOrInvalidated, other.appliedOrInvalidated);
}
+ public static class Initialise extends Update implements ICommand
+ {
+ final TxnId txnId;
+ final StoreParticipants participants;
+ final Timestamp executeAt;
+ final PartialDeps deps;
+
+ private Initialise(TxnId txnId, StoreParticipants participants, Timestamp executeAt, PartialDeps deps)
+ {
+ super(txnId, deps.keyDeps.keys(), deps.rangeDeps, deps.directKeyDeps);
+ this.txnId = txnId;
+ this.participants = participants;
+ this.executeAt = executeAt;
+ this.deps = deps;
+ }
+
+ @Override public TxnId txnId() { return txnId; }
+ @Override public Status.Durability durability() { return null; }
+ @Override public StoreParticipants participants() { return participants; }
+ @Override public Ballot promised() { return null; }
+ @Override public PartialTxn partialTxn() { return null; }
+ @Override public PartialDeps partialDeps() { return deps; }
+ @Override public Timestamp executeAt() { return executeAt; }
+ @Override public Ballot acceptedOrCommitted() { return null; }
+ @Override public WaitingOn waitingOn() { return null; }
+ @Override public Writes writes() { return null; }
+ @Override public Result result() { return null; }
+ }
+
public static class Update
{
final RoutingKeys keys;
@@ -1515,9 +1090,9 @@ public abstract class Command implements CommonAttributes
this.appliedOrInvalidated = txnId.is(Key) ? null : new SimpleBitSet(txnIdCount(), false);
}
- public static Update initialise(SafeCommandStore safeStore, TxnId txnId, Timestamp executeAt, StoreParticipants participants, Deps deps)
+ public static Initialise initialise(SafeCommandStore safeStore, TxnId txnId, Timestamp executeAt, StoreParticipants participants, PartialDeps deps)
{
- Update update = new Update(txnId, deps.keyDeps.keys(), deps.rangeDeps, deps.directKeyDeps);
+ Initialise initialise = new Initialise(txnId, participants, executeAt, deps);
if (txnId.is(Txn.Kind.ExclusiveSyncPoint))
{
CommandStores.RangesForEpoch rangesForEpoch = safeStore.ranges();
@@ -1530,33 +1105,33 @@ public abstract class Command implements CommonAttributes
ranges = safeStore.redundantBefore().removePreBootstrap(txnId, ranges);
if (!ranges.isEmpty())
{
- deps.rangeDeps.forEach(participants.stillExecutes().slice(ranges, Slice.Minimal), update, (upd, idx) -> {
+ deps.rangeDeps.forEach(participants.stillExecutes().slice(ranges, Slice.Minimal), initialise, (upd, idx) -> {
TxnId id = upd.txnId(idx);
// because we use RX as RedundantBefore bounds, we must not let an RX on a closing range
// get ahead of one that isn't closed but has overlapping transactions (else we may erroneously treat as redundant)
if (id.epoch() >= epoch && (id.is(Txn.Kind.ExclusiveSyncPoint) || id.epoch() < maxEpoch))
- update.initialise(idx);
+ initialise.initialise(idx);
});
int lbound = deps.rangeDeps.txnIdCount();
- deps.directKeyDeps.forEach(ranges, 0, deps.directKeyDeps.txnIdCount(), update, deps.rangeDeps, (upd, rdeps, idx) -> {
+ deps.directKeyDeps.forEach(ranges, 0, deps.directKeyDeps.txnIdCount(), initialise, deps.rangeDeps, (upd, rdeps, idx) -> {
TxnId id = upd.txnId(idx + lbound);
if (id.epoch() >= epoch && id.epoch() < maxEpoch)
- update.initialise(idx + lbound);
+ initialise.initialise(idx + lbound);
});// TODO (expected): do same loop as above for keys, but mark the key if we find any matching id
- deps.keyDeps.keys().forEach(ranges, (upd, key, index) -> upd.initialise(index + upd.txnIdCount()), update);
+ deps.keyDeps.keys().forEach(ranges, (upd, key, index) -> upd.initialise(index + upd.txnIdCount()), initialise);
}
prevEpoch = epoch;
}
- return update;
+ return initialise;
}
else
{
Ranges executeRanges = participants.executeRanges(safeStore, txnId, executeAt);
// TODO (expected): refactor this to operate only on participants, not ranges
- deps.rangeDeps.forEach(participants.stillExecutes(), update, Update::initialise);
- deps.directKeyDeps.forEach(executeRanges, 0, deps.directKeyDeps.txnIdCount(), update, deps.rangeDeps, (upd, rdeps, index) -> upd.initialise(index + rdeps.txnIdCount()));
- deps.keyDeps.keys().forEach(executeRanges, (upd, key, index) -> upd.initialise(index + upd.txnIdCount()), update);
- return update;
+ deps.rangeDeps.forEach(participants.stillExecutes(), initialise, Update::initialise);
+ deps.directKeyDeps.forEach(executeRanges, 0, deps.directKeyDeps.txnIdCount(), initialise, deps.rangeDeps, (upd, rdeps, index) -> upd.initialise(index + rdeps.txnIdCount()));
+ deps.keyDeps.keys().forEach(executeRanges, (upd, key, index) -> upd.initialise(index + upd.txnIdCount()), initialise);
+ return initialise;
}
}
@@ -1572,7 +1147,7 @@ public abstract class Command implements CommonAttributes
return update;
}
- private void initialise(int i)
+ void initialise(int i)
{
waitingOn.set(i);
}
@@ -1696,6 +1271,7 @@ public abstract class Command implements CommonAttributes
public void updateExecuteAtLeast(TxnId txnId, Timestamp executeAtLeast)
{
+ Invariants.checkState(txnId.awaitsOnlyDeps());
Invariants.checkState(executeAtLeast.compareTo(txnId) > 0);
if (this.executeAtLeast == null || this.executeAtLeast.compareTo(executeAtLeast) < 0)
{
@@ -1876,82 +1452,310 @@ public abstract class Command implements CommonAttributes
return command;
return command instanceof Command.Executed ?
- Command.Executed.executed(command.asExecuted(), command, waitingOn.build()) :
- Command.Committed.committed(command, command, waitingOn.build());
+ executed(command, command.saveStatus(), waitingOn.build()) :
+ committed(command, waitingOn.isWaiting() ? SaveStatus.Stable : ReadyToExecute, waitingOn.build());
}
- static Command.PreAccepted preaccept(Command command, CommonAttributes attrs, Timestamp executeAt, Ballot ballot)
+ static Command.PreAccepted preaccept(Command command, SaveStatus saveStatus, StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps)
{
if (command.status() == Status.NotDefined)
{
- return Command.PreAccepted.preAccepted(attrs, executeAt, ballot);
+ return Command.PreAccepted.preaccepted(command.txnId(), saveStatus, command.durability(), participants, promised, executeAt, partialTxn, partialDeps);
}
- else if (command.status() == Status.AcceptedInvalidate && command.executeAt() == null)
+ else if (command.executeAt() == null && command.status().phase == Phase.Accept)
{
+ SaveStatus prevSaveStatus = command.saveStatus();
+ Invariants.checkState(prevSaveStatus == AcceptedInvalidate || prevSaveStatus == PreNotAccepted || prevSaveStatus == NotAccepted);
// TODO (desired): reconsider this special-casing
- return Command.Accepted.accepted(attrs, SaveStatus.enrich(command.saveStatus(), SaveStatus.PreAccepted.known), executeAt, ballot, command.acceptedOrCommitted());
+ return Command.Accepted.accepted(command.txnId(), SaveStatus.enrich(prevSaveStatus, SaveStatus.PreAccepted.known), command.durability(), participants, promised, executeAt, partialTxn, partialDeps, command.acceptedOrCommitted());
}
else
{
- Invariants.checkState(command.status() == Status.Accepted);
- return (Command.PreAccepted) command.updateAttributes(attrs, ballot);
+ Invariants.checkState(command.status() == Status.AcceptedMedium);
+ return (Command.PreAccepted) command.updatePromised(promised);
}
}
- static Command.Accepted markDefined(Command command, CommonAttributes attributes, Ballot promised)
+ static Command.Accepted markDefined(Command command, StoreParticipants newParticipants, Ballot promised, PartialTxn partialTxn)
{
- if (Command.isSameClass(command, Command.Accepted.class))
- return Command.Accepted.accepted(command.asAccepted(), attributes, SaveStatus.withDefinition(command.saveStatus()), promised);
- return (Command.Accepted) command.updateAttributes(attributes, promised);
+ Invariants.checkState(!(command instanceof Committed));
+ return Command.Accepted.accepted(command.txnId(), SaveStatus.withDefinition(command.saveStatus()), command.durability(),
+ newParticipants, promised, command.executeAt(), partialTxn, command.partialDeps(),
+ command.acceptedOrCommitted());
}
- static Command.Accepted accept(Command command, CommonAttributes attrs, Timestamp executeAt, Ballot ballot)
+ static Command.Accepted accept(Command current, SaveStatus saveStatus, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialDeps partialDeps, Ballot acceptedOrCommitted)
{
- return validate(new Command.Accepted(attrs, SaveStatus.get(Status.Accepted, command.known()), ballot, executeAt, ballot));
+ Invariants.checkState(saveStatus.status == Status.AcceptedSlow || saveStatus.status == Status.AcceptedMedium);
+ return validate(new Command.Accepted(current.txnId(), saveStatus, current.durability(), participants, promised, executeAt, current.partialTxn(), partialDeps, acceptedOrCommitted));
}
- static Command acceptInvalidated(Command command, Ballot ballot)
+ static Command notAccept(Status newStatus, Command copy, Ballot ballot)
{
- SaveStatus saveStatus = SaveStatus.get(Status.AcceptedInvalidate, command.known());
- if (saveStatus == AcceptedInvalidate)
- return validate(new Command.AcceptedInvalidateWithoutDefinition(command, ballot, ballot));
+ switch (newStatus)
+ {
+ default: throw illegalArgument("Invalid notAccept Status: " + newStatus);
+ case PreNotAccepted:
+ case NotAccepted:
+ case AcceptedInvalidate:
+ break;
+ }
+
+ SaveStatus saveStatus = SaveStatus.get(newStatus, copy.known());
+
+ PartialDeps partialDeps = copy.partialDeps();
+ if (saveStatus.known.is(DepsUnknown))
+ partialDeps = null;
+
+ if (!saveStatus.known.isDefinitionKnown())
+ return validate(notAccepted(copy.txnId, saveStatus, copy.durability, copy.participants, ballot, ballot, partialDeps));
- return validate(new Command.Accepted(command, saveStatus, ballot, command.executeAt(), command.partialTxn(), null, ballot));
+ return validate(new Command.Accepted(copy.txnId, saveStatus, copy.durability, copy.participants, ballot, copy.executeAt(), copy.partialTxn(), partialDeps, ballot));
}
- static Command.Committed commit(Command command, CommonAttributes attrs, Ballot ballot, Timestamp executeAt)
+ static Command.Committed commit(Command command, @Nonnull StoreParticipants participants, Ballot ballot, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps)
{
- return validate(Command.Committed.committed(attrs, SaveStatus.get(Status.Committed, command.known()), executeAt, Ballot.max(command.promised(), ballot), ballot, null));
+ return validate(committed(command.txnId(), SaveStatus.get(Status.Committed, command.known()), command.durability(), participants, ballot, executeAt, partialTxn, partialDeps, ballot, null));
}
- static Command.Committed stable(Command command, CommonAttributes attrs, Ballot ballot, Timestamp executeAt, Command.WaitingOn waitingOn)
+ static Command.Committed stable(Command command, @Nonnull StoreParticipants participants, Ballot ballot, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, WaitingOn waitingOn)
{
- return validate(Command.Committed.committed(attrs, SaveStatus.Stable, executeAt, Ballot.max(command.promised(), ballot), ballot, waitingOn));
+ return validate(committed(command.txnId(), SaveStatus.Stable, command.durability(), participants,
+ Ballot.max(ballot, command.promised()), executeAt, partialTxn, partialDeps,
+ Ballot.max(ballot, command.acceptedOrCommitted()), waitingOn));
}
- static Command precommit(CommonAttributes attrs, Command command, Timestamp executeAt)
+ static Command precommit(Command command, Timestamp executeAt)
{
SaveStatus saveStatus = SaveStatus.get(Status.PreCommitted, command.known());
- return validate(new Command.Accepted(attrs, saveStatus, command.promised(), executeAt, command.acceptedOrCommitted()));
+ PartialDeps partialDeps = command.partialDeps();
+ if (!saveStatus.known.deps().hasProposedOrDecidedDeps() && command.partialDeps() != null)
+ partialDeps = null;
+ return validate(new Command.Accepted(command.txnId, saveStatus, command.durability, command.participants, command.promised, executeAt, command.partialTxn, partialDeps, command.acceptedOrCommitted));
}
static Command.Committed readyToExecute(Command.Committed command)
{
- return Command.Committed.committed(command, command, SaveStatus.ReadyToExecute);
+ return committed(command, SaveStatus.ReadyToExecute);
}
- static Command.Executed preapplied(Command command, CommonAttributes attrs, Timestamp executeAt, Command.WaitingOn waitingOn, Writes writes, Result result)
+ static Command.Executed preapplied(Command command, @Nonnull StoreParticipants participants, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, Command.WaitingOn waitingOn, Writes writes, Result result)
{
- return Command.Executed.executed(attrs, SaveStatus.get(Status.PreApplied, command.known()), executeAt, command.promised(), command.acceptedOrCommitted(), waitingOn, writes, result);
+ return executed(command.txnId(), SaveStatus.get(Status.PreApplied, command.known()), command.durability(), participants, command.promised(), executeAt, partialTxn, partialDeps, command.acceptedOrCommitted(), waitingOn, writes, result);
}
static Command.Executed applying(Command.Executed command)
{
- return Command.Executed.executed(command, command, SaveStatus.Applying);
+ return executed(command, SaveStatus.Applying);
}
static Command.Executed applied(Command.Executed command)
{
- return Command.Executed.executed(command, command, SaveStatus.Applied);
+ return executed(command, SaveStatus.Applied);
+ }
+
+ private static SaveStatus validateCommandClass(SaveStatus status, Class<?> expected, Class<?> actual)
+ {
+ if (actual != expected)
+ {
+ throw illegalState(format("Cannot instantiate %s for status %s. %s expected",
+ actual.getSimpleName(), status, expected.getSimpleName()));
+ }
+ return status;
+ }
+
+ private static SaveStatus validateCommandClass(TxnId txnId, SaveStatus status, Class<?> klass)
+ {
+ switch (status)
+ {
+ case Uninitialised:
+ case NotDefined:
+ return validateCommandClass(status, NotDefined.class, klass);
+ case PreAccepted:
+ case PreAcceptedWithDeps:
+ case PreAcceptedWithVote:
+ return validateCommandClass(status, PreAccepted.class, klass);
+ case PreNotAccepted:
+ case NotAccepted:
+ case AcceptedInvalidate:
+ return validateCommandClass(status, NotAcceptedWithoutDefinition.class, klass);
+ case AcceptedInvalidateWithDefinition:
+ case PreNotAcceptedWithDefinition:
+ case PreNotAcceptedWithDefAndDeps:
+ case PreNotAcceptedWithDefAndVote:
+ case NotAcceptedWithDefinition:
+ case NotAcceptedWithDefAndVote:
+ case NotAcceptedWithDefAndDeps:
+ case AcceptedMedium:
+ case AcceptedMediumWithDefinition:
+ case AcceptedMediumWithDefAndVote:
+ case AcceptedSlow:
+ case AcceptedSlowWithDefinition:
+ case AcceptedSlowWithDefAndVote:
+ case PreCommitted:
+ case PreCommittedWithDeps:
+ case PreCommittedWithFixedDeps:
+ case PreCommittedWithDefinition:
+ case PreCommittedWithDefAndDeps:
+ case PreCommittedWithDefAndFixedDeps:
+ return validateCommandClass(status, Accepted.class, klass);
+ case Committed:
+ case ReadyToExecute:
+ case Stable:
+ return validateCommandClass(status, Committed.class, klass);
+ case PreApplied:
+ case Applying:
+ case Applied:
+ return validateCommandClass(status, Executed.class, klass);
+ case TruncatedApply:
+ case TruncatedApplyWithDeps:
+ case TruncatedApplyWithOutcome:
+ if (txnId.awaitsOnlyDeps())
+ return validateCommandClass(status, TruncatedAwaitsOnlyDeps.class, klass);
+ case Erased:
+ case ErasedOrVestigial:
+ case Invalidated:
+ return validateCommandClass(status, Truncated.class, klass);
+ default:
+ throw illegalState("Unhandled status " + status);
+ }
+ }
+
+ public static <T extends Command> T validate(T validate)
+ {
+ Known known = validate.known();
+ switch (known.route())
+ {
+ default: throw new UnhandledEnum(known.route());
+ case MaybeRoute: break;
+ case FullRoute: Invariants.checkState(Route.isFullRoute(validate.route())); break;
+ case CoveringRoute: Invariants.checkState(Route.isRoute(validate.route())); break;
+ }
+ {
+ PartialTxn partialTxn = validate.partialTxn();
+ switch (known.definition())
+ {
+ default: throw new UnhandledEnum(known.definition());
+ case DefinitionErased:
+ case DefinitionUnknown:
+ case NoOp:
+ Invariants.checkState(partialTxn == null, "partialTxn is defined %s", validate);
+ break;
+ case DefinitionKnown:
+ Invariants.checkState(partialTxn != null || validate.participants().owns().isEmpty(), "partialTxn is null");
+ break;
+ }
+ }
+ {
+ Timestamp executeAt = validate.executeAt();
+ switch (known.executeAt())
+ {
+ default: throw new UnhandledEnum(known.executeAt());
+ case ExecuteAtErased:
+ case ExecuteAtUnknown:
+ Invariants.checkState(executeAt == null || executeAt.compareTo(validate.txnId()) >= 0);
+ break;
+ case ExecuteAtKnown:
+ case ExecuteAtProposed:
+ Invariants.checkState(executeAt != null && executeAt.compareTo(validate.txnId()) >= 0);
+ break;
+ case NoExecuteAt:
+ Invariants.checkState(executeAt.equals(Timestamp.NONE));
+ break;
+ }
+ }
+ {
+ PartialDeps deps = validate.partialDeps();
+ switch (known.deps())
+ {
+ default: throw new UnhandledEnum(known.deps());
+ case DepsUnknown:
+ case DepsErased:
+ case NoDeps:
+ Invariants.checkState(deps == null);
+ break;
+ case DepsFromCoordinator:
+ case DepsProposedFixed:
+ case DepsProposed:
+ case DepsCommitted:
+ case DepsKnown:
+ Invariants.checkState(deps != null);
+ break;
+ }
+ }
+ {
+ Writes writes = validate.writes();
+ Result result = validate.result();
+ switch (known.outcome())
+ {
+ default: throw new UnhandledEnum(known.outcome());
+ case Apply:
+ if (validate.txnId().is(Write)) Invariants.checkState(writes != null, "Writes is null %s", validate);
+ else Invariants.checkState(writes == null, "Writes is not-null %s", validate);
+ Invariants.checkState(result != null, "Result is null %s", validate);
+ break;
+ case Abort:
+ Invariants.checkState(validate.durability().isMaybeInvalidated(), "%s is not invalidated", validate.durability());
+ case Unknown:
+ Invariants.checkState(validate.durability() != Local);
+ case Erased:
+ case WasApply:
+ Invariants.checkState(writes == null, "Writes exist for %s", validate);
+ Invariants.checkState(result == null, "Results exist %s", validate);
+ break;
+ }
+ }
+ switch (validate.saveStatus().execution)
+ {
+ case NotReady:
+ case CleaningUp:
+ break;
+ case ReadyToExclude:
+ Invariants.checkState(validate.saveStatus() != SaveStatus.Committed || validate.asCommitted().waitingOn == null);
+ break;
+ case WaitingToExecute:
+ case ReadyToExecute:
+ case Applied:
+ case Applying:
+ case WaitingToApply:
+ Invariants.checkState(validate.participants().executes() != null);
+ Invariants.checkState(validate.asCommitted().waitingOn != null);
+ break;
+ }
+ return validate;
+ }
+
+ public static Durability durability(Durability durability, SaveStatus status)
+ {
+ if (durability == NotDurable && status.compareTo(SaveStatus.PreApplied) >= 0 && status.compareTo(ErasedOrVestigial) < 0)
+ return Local; // not necessary anywhere, but helps for logical consistency
+ return durability;
+ }
+
+ private static boolean isSameClass(Command command, Class<? extends Command> klass)
+ {
+ return command.getClass() == klass;
+ }
+
+ private static void checkNewBallot(Ballot current, Ballot next, String name)
+ {
+ if (next.compareTo(current) < 0)
+ throw new IllegalArgumentException(format("Cannot update %s ballot from %s to %s. New ballot is less than current", name, current, next));
+ }
+
+ private static void checkPromised(Command command, Ballot ballot)
+ {
+ checkNewBallot(command.promised(), ballot, "promised");
+ }
+
+ private static void checkAccepted(Command command, Ballot ballot)
+ {
+ checkNewBallot(command.acceptedOrCommitted(), ballot, "accepted");
+ }
+
+ private static void checkSameClass(Command command, Class<? extends Command> klass, String errorMsg)
+ {
+ if (!isSameClass(command, klass))
+ throw illegalArgument(errorMsg + format(" expected %s got %s", klass.getSimpleName(), command.getClass().getSimpleName()));
}
}
diff --git a/accord-core/src/main/java/accord/local/CommandStore.java b/accord-core/src/main/java/accord/local/CommandStore.java
index e13a677c..6f94caf1 100644
--- a/accord-core/src/main/java/accord/local/CommandStore.java
+++ b/accord-core/src/main/java/accord/local/CommandStore.java
@@ -29,6 +29,7 @@ import accord.api.Agent;
import accord.local.CommandStores.RangesForEpoch;
import accord.primitives.RangeDeps;
import accord.primitives.Routables;
+import accord.primitives.Route;
import accord.primitives.Unseekables;
import accord.utils.async.AsyncChain;
@@ -513,6 +514,14 @@ public abstract class CommandStore implements AgentExecutor
return cs;
}
+ public static CommandStore currentOrElseSelect(Node node, Route<?> route)
+ {
+ CommandStore cs = maybeCurrent();
+ if (cs == null)
+ return node.commandStores().select(route);
+ return cs;
+ }
+
protected static void register(CommandStore store)
{
if (!store.inStore())
@@ -587,7 +596,7 @@ public abstract class CommandStore implements AgentExecutor
AsyncResult<Void> done = this.<Void>submit(empty(), safeStore -> {
for (Bootstrap prev : bootstraps)
{
- Ranges abort = prev.allValid.slice(removedRanges);
+ Ranges abort = prev.allValid.slice(removedRanges, Minimal);
if (!abort.isEmpty())
prev.invalidate(abort);
}
diff --git a/accord-core/src/main/java/accord/local/CommandStores.java b/accord-core/src/main/java/accord/local/CommandStores.java
index 7ed6bb1c..02638330 100644
--- a/accord-core/src/main/java/accord/local/CommandStores.java
+++ b/accord-core/src/main/java/accord/local/CommandStores.java
@@ -573,10 +573,7 @@ public abstract class CommandStores
{
Shard oldShard = oldShards.get(i);
Shard newShard = newShards.get(i);
- if (!oldShard.fastPathElectorate.containsAll(newShard.fastPathElectorate))
- return true;
-
- if (!newShard.fastPathElectorate.containsAll(oldShard.fastPathElectorate))
+ if (!oldShard.notInFastPath.equals(newShard.notInFastPath))
return true;
if (!newShard.nodes.equals(oldShard.nodes))
diff --git a/accord-core/src/main/java/accord/local/CommandSummaries.java b/accord-core/src/main/java/accord/local/CommandSummaries.java
index ce019fc7..178f4c13 100644
--- a/accord-core/src/main/java/accord/local/CommandSummaries.java
+++ b/accord-core/src/main/java/accord/local/CommandSummaries.java
@@ -35,17 +35,22 @@ import accord.primitives.TxnId;
import accord.primitives.Unseekable;
import accord.primitives.Unseekables;
import accord.utils.Invariants;
+import accord.utils.UnhandledEnum;
+import static accord.local.CommandSummaries.SummaryStatus.ACCEPTED;
import static accord.primitives.Routables.Slice.Minimal;
+import static accord.primitives.Txn.Kind.AnyGloballyVisible;
import static accord.primitives.Txn.Kind.ExclusiveSyncPoint;
-import static accord.primitives.Txn.Kind.Kinds.AnyGloballyVisible;
+import static accord.primitives.Txn.Kind.Nothing;
public interface CommandSummaries
{
enum SummaryStatus
{
NOT_DIRECTLY_WITNESSED,
- NOT_ACCEPTED,
+ PREACCEPTED,
+ PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE,
+ NOTACCEPTED,
ACCEPTED,
COMMITTED,
STABLE,
@@ -55,7 +60,7 @@ public interface CommandSummaries
public static final SummaryStatus NONE = null;
}
- enum IsDep { IS_DEP, NOT_ELIGIBLE, IS_NOT_DEP }
+ enum IsDep { IS_COORD_DEP, IS_NOT_COORD_DEP, NOT_ELIGIBLE, IS_STABLE_DEP, IS_NOT_STABLE_DEP }
class Summary
{
@@ -105,13 +110,13 @@ public interface CommandSummaries
protected final Unseekables<?> searchKeysOrRanges;
protected final RedundantBefore redundantBefore;
- // TODO (desired): separate out Kinds we need before/after primaryTxnId/executeAt
+ // TODO (expected): separate out Kinds we need before/after primaryTxnId/executeAt
protected final Kinds testKind;
protected final TxnId minTxnId;
protected final Timestamp maxTxnId;
@Nullable protected final TxnId findAsDep;
- // TODO (desired): provide executeAt to PreLoadContext so we can more aggressively filter what we load, esp. by Kind
+ // TODO (expected): provide executeAt to PreLoadContext so we can more aggressively filter what we load, esp. by Kind
public static Loader loader(RedundantBefore redundantBefore, PreLoadContext context)
{
return loader(redundantBefore, context.primaryTxnId(), context.keyHistory(), context.keys());
@@ -127,7 +132,7 @@ public interface CommandSummaries
TxnId minTxnId = redundantBefore.min(keysOrRanges, e -> e.gcBefore);
Timestamp maxTxnId = primaryTxnId == null || keyHistory == KeyHistory.RECOVER || !primaryTxnId.is(ExclusiveSyncPoint) ? Timestamp.MAX : primaryTxnId;
TxnId findAsDep = primaryTxnId != null && keyHistory == KeyHistory.RECOVER ? primaryTxnId : null;
- Kinds kinds = primaryTxnId == null ? AnyGloballyVisible : primaryTxnId.witnesses().or(keyHistory == KeyHistory.RECOVER ? primaryTxnId.witnessedBy() : Kinds.Nothing);
+ Kinds kinds = primaryTxnId == null ? AnyGloballyVisible : primaryTxnId.witnesses().or(keyHistory == KeyHistory.RECOVER ? primaryTxnId.witnessedBy() : Nothing);
return factory.create(keysOrRanges, redundantBefore, kinds, minTxnId, maxTxnId, findAsDep);
}
@@ -150,10 +155,15 @@ public interface CommandSummaries
{
switch (status)
{
- default: throw new AssertionError("Unhandled SummaryStatus: " + status);
- case NOT_ACCEPTED:
+ default: throw new UnhandledEnum(status);
+ case NOT_DIRECTLY_WITNESSED:
case INVALIDATED:
return false;
+ case PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE:
+ case NOTACCEPTED:
+ case PREACCEPTED:
+ if (!txnId.is(TxnId.FastPath.PRIVILEGED_COORDINATOR_WITH_DEPS))
+ return false;
case ACCEPTED:
return txnId.compareTo(findAsDep) > 0;
case COMMITTED:
@@ -204,7 +214,7 @@ public interface CommandSummaries
ranges = newRanges;
}
- Invariants.checkState(partialDeps != null || findAsDep == null || !saveStatus.known.deps.hasProposedOrDecidedDeps());
+ Invariants.checkState(partialDeps != null || findAsDep == null || !saveStatus.known.deps().hasProposedOrDecidedDeps());
IsDep isDep = null;
if (findAsDep != null)
{
@@ -214,8 +224,11 @@ public interface CommandSummaries
}
else
{
+ boolean isStable = summaryStatus.compareTo(ACCEPTED) >= 0;
Unseekables<?> participants = partialDeps.participants(findAsDep);
- isDep = participants != null && participants.containsAll(ranges) ? IsDep.IS_DEP : IsDep.IS_NOT_DEP;
+ isDep = participants != null && participants.containsAll(ranges)
+ ? (isStable ? IsDep.IS_STABLE_DEP : IsDep.IS_COORD_DEP)
+ : (isStable ? IsDep.IS_NOT_STABLE_DEP : IsDep.IS_NOT_COORD_DEP);
}
}
diff --git a/accord-core/src/main/java/accord/local/Commands.java b/accord-core/src/main/java/accord/local/Commands.java
index 91276a30..2307b9a9 100644
--- a/accord-core/src/main/java/accord/local/Commands.java
+++ b/accord-core/src/main/java/accord/local/Commands.java
@@ -29,11 +29,15 @@ import accord.api.Result;
import accord.api.RoutingKey;
import accord.api.VisibleForImplementation;
import accord.local.Command.WaitingOn;
+import accord.local.Command.WaitingOn.Update;
import accord.local.CommandStores.RangesForEpochSupplier;
import accord.local.RedundantBefore.RedundantBeforeSupplier;
import accord.local.cfk.CommandsForKey;
+import accord.messages.Accept;
+import accord.messages.Commit;
import accord.primitives.AbstractUnseekableKeys;
import accord.primitives.Ballot;
+import accord.primitives.Deps;
import accord.primitives.FullRoute;
import accord.primitives.Known;
import accord.primitives.PartialDeps;
@@ -44,10 +48,12 @@ import accord.primitives.Route;
import accord.primitives.SaveStatus;
import accord.primitives.Status;
import accord.primitives.Timestamp;
+import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.primitives.Unseekables;
import accord.primitives.Writes;
import accord.utils.Invariants;
+import accord.utils.UnhandledEnum;
import accord.utils.async.AsyncChain;
import accord.utils.async.AsyncChains;
@@ -64,6 +70,10 @@ import static accord.local.Command.Truncated.erasedOrVestigial;
import static accord.local.Command.Truncated.invalidated;
import static accord.local.Command.Truncated.truncatedApply;
import static accord.local.Command.Truncated.truncatedApplyWithOutcome;
+import static accord.local.Commands.Validated.INSUFFICIENT;
+import static accord.local.Commands.Validated.UPDATE_TXN_IGNORE_DEPS;
+import static accord.local.Commands.Validated.UPDATE_TXN_KEEP_DEPS;
+import static accord.local.Commands.Validated.UPDATE_TXN_AND_DEPS;
import static accord.local.KeyHistory.INCR;
import static accord.local.KeyHistory.SYNC;
import static accord.local.PreLoadContext.contextFor;
@@ -71,10 +81,16 @@ import static accord.local.RedundantBefore.PreBootstrapOrStale.FULLY;
import static accord.local.RedundantStatus.WAS_OWNED_RETIRED;
import static accord.local.StoreParticipants.Filter.LOAD;
import static accord.local.StoreParticipants.Filter.UPDATE;
+import static accord.messages.Commit.Kind.StableMediumPath;
+import static accord.primitives.Known.KnownDeps.DepsKnown;
+import static accord.primitives.Known.KnownDeps.DepsProposedFixed;
import static accord.primitives.Routables.Slice.Minimal;
import static accord.primitives.SaveStatus.Applying;
import static accord.primitives.SaveStatus.Erased;
import static accord.primitives.SaveStatus.LocalExecution.WaitingToExecute;
+import static accord.primitives.SaveStatus.PreAccepted;
+import static accord.primitives.SaveStatus.PreAcceptedWithDeps;
+import static accord.primitives.SaveStatus.PreAcceptedWithVote;
import static accord.primitives.SaveStatus.TruncatedApply;
import static accord.primitives.SaveStatus.Uninitialised;
import static accord.primitives.Status.Applied;
@@ -82,7 +98,7 @@ import static accord.primitives.Status.Committed;
import static accord.primitives.Status.Durability;
import static accord.primitives.Status.Invalidated;
import static accord.primitives.Known.KnownExecuteAt.ExecuteAtKnown;
-import static accord.primitives.Known.KnownRoute.Full;
+import static accord.primitives.Known.KnownRoute.FullRoute;
import static accord.primitives.Status.NotDefined;
import static accord.primitives.Status.PreApplied;
import static accord.primitives.Status.PreCommitted;
@@ -90,7 +106,8 @@ import static accord.primitives.Status.Stable;
import static accord.primitives.Status.Truncated;
import static accord.primitives.Route.isFullRoute;
import static accord.primitives.Txn.Kind.EphemeralRead;
-import static accord.primitives.Txn.Kind.Read;
+import static accord.primitives.Txn.Kind.Write;
+import static accord.primitives.TxnId.FastPath.PRIVILEGED_COORDINATOR_WITH_DEPS;
import static accord.utils.Invariants.illegalState;
public class Commands
@@ -103,18 +120,25 @@ public class Commands
public enum AcceptOutcome { Success, Redundant, RejectedBallot, Retired, Truncated }
- public static AcceptOutcome preaccept(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, TxnId txnId, long acceptEpoch, PartialTxn partialTxn, FullRoute<?> route)
+ public static AcceptOutcome preaccept(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, TxnId txnId, Txn partialTxn, @Nullable Deps partialDeps, boolean hasCoordinatorVote, FullRoute<?> route)
{
- return preacceptOrRecover(safeStore, safeCommand, participants, txnId, acceptEpoch, partialTxn, route, Ballot.ZERO);
+ Invariants.checkState(partialDeps == null || txnId.is(PRIVILEGED_COORDINATOR_WITH_DEPS));
+ Invariants.checkState(!hasCoordinatorVote || txnId.hasPrivilegedCoordinator());
+ SaveStatus newSaveStatus;
+ if (partialDeps != null) newSaveStatus = PreAcceptedWithDeps;
+ else if (hasCoordinatorVote) newSaveStatus = PreAcceptedWithVote;
+ else newSaveStatus = PreAccepted;
+
+ return preacceptOrRecover(safeStore, safeCommand, participants, newSaveStatus, txnId, partialTxn, partialDeps, route, Ballot.ZERO);
}
public static AcceptOutcome recover(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, TxnId txnId, PartialTxn partialTxn, FullRoute<?> route, Ballot ballot)
{
// for recovery we only ever propose either the original epoch or an Accept that we witness; otherwise we invalidate
- return preacceptOrRecover(safeStore, safeCommand, participants, txnId, txnId.epoch(), partialTxn, route, ballot);
+ return preacceptOrRecover(safeStore, safeCommand, participants, SaveStatus.PreAccepted, txnId, partialTxn, null, route, ballot);
}
- private static AcceptOutcome preacceptOrRecover(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, TxnId txnId, long acceptEpoch, PartialTxn partialTxn, FullRoute<?> route, Ballot ballot)
+ private static AcceptOutcome preacceptOrRecover(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, SaveStatus newSaveStatus, TxnId txnId, Txn txn, @Nullable Deps deps, FullRoute<?> route, Ballot ballot)
{
final Command command = safeCommand.current();
if (command.hasBeen(Truncated))
@@ -131,7 +155,7 @@ public class Commands
return AcceptOutcome.RejectedBallot;
}
- if (command.known().definition.isKnown())
+ if (command.known().definition().isKnown())
{
Invariants.checkState(command.status() == Invalidated || command.executeAt() != null);
logger.trace("{}: skipping preaccept - already known ({})", txnId, command.status());
@@ -141,11 +165,12 @@ public class Commands
return ballot.equals(Ballot.ZERO) ? AcceptOutcome.Redundant : AcceptOutcome.Success;
}
- if (command.known().deps.hasProposedOrDecidedDeps()) participants = command.participants().supplement(participants);
+ if (command.known().deps().hasProposedOrDecidedDeps()) participants = command.participants().supplement(participants);
else participants = participants.filter(UPDATE, safeStore, txnId, null);
- Invariants.checkState(validate(SaveStatus.PreAccepted, command, participants, txnId, route, partialTxn, null));
- CommonAttributes attrs = set(safeStore, SaveStatus.PreAccepted, command, command, participants, ballot, partialTxn, null);
+ Validated validated = validate(ballot, newSaveStatus, command, participants, route, txn, deps);
+ Invariants.checkState(validated != INSUFFICIENT);
+
if (command.executeAt() == null)
{
// unlike in the Accord paper, we partition shards within a node, so that to ensure a total order we must either:
@@ -155,12 +180,23 @@ public class Commands
// invalidate any transactions that were not completed by their initial coordinator
// TODO (desired): limit preaccept to keys we include, to avoid inflating unnecessary state
Timestamp executeAt = safeStore.commandStore().preaccept(txnId, route, safeStore, ballot.equals(Ballot.ZERO));
- safeCommand.preaccept(safeStore, attrs, executeAt, ballot);
+ if (txnId != executeAt || !command.is(NotDefined))
+ {
+ newSaveStatus = PreAccepted;
+ validated = UPDATE_TXN_IGNORE_DEPS;
+ }
+
+ PartialTxn partialTxn = prepareTxn(newSaveStatus, participants, command, txn);
+ PartialDeps partialDeps = prepareDeps(validated, participants, command, deps);
+ participants = prepareParticipants(validated, participants, command);
+ safeCommand.preaccept(safeStore, newSaveStatus, participants, ballot, executeAt, partialTxn, partialDeps);
}
else
{
// TODO (expected, ?): in the case that we are pre-committed but had not been preaccepted/accepted, should we inform progressLog?
- safeCommand.markDefined(safeStore, attrs, ballot);
+ PartialTxn partialTxn = prepareTxn(newSaveStatus, participants, command, txn);
+ participants = prepareParticipants(validated, participants, command);
+ safeCommand.markDefined(safeStore, participants, ballot, partialTxn);
}
safeStore.notifyListeners(safeCommand, command);
@@ -188,7 +224,7 @@ public class Commands
return true;
}
- public static AcceptOutcome accept(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, TxnId txnId, Ballot ballot, Route<?> route, Timestamp executeAt, PartialDeps partialDeps)
+ public static AcceptOutcome accept(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, TxnId txnId, Accept.Kind kind, Ballot ballot, Route<?> route, Timestamp executeAt, PartialDeps deps)
{
final Command command = safeCommand.current();
if (command.hasBeen(PreCommitted))
@@ -199,43 +235,52 @@ public class Commands
if (command.promised().compareTo(ballot) > 0)
{
- logger.trace("{}: skipping accept - witnessed higher ballot ({} > {})", txnId, command.promised(), ballot);
+ if (logger.isTraceEnabled())
+ logger.trace("{}: skipping accept - witnessed higher ballot ({} > {})", txnId, command.promised(), ballot);
return AcceptOutcome.RejectedBallot;
}
+ SaveStatus newSaveStatus = SaveStatus.get(kind == Accept.Kind.MEDIUM ? Status.AcceptedMedium : Status.AcceptedSlow, command.known());
participants = participants.filter(UPDATE, safeStore, txnId, null);
- Invariants.checkState(validate(SaveStatus.Accepted, command, participants, txnId, route, null, partialDeps));
+ Validated validated = validate(ballot, newSaveStatus, command, participants, route, null, deps);
+ Invariants.checkState(validated != INSUFFICIENT);
- // TODO (desired, clarity/efficiency): we don't need to set the route here, and perhaps we don't even need to
- // distributed partialDeps at all, since all we gain is not waiting for these transactions to commit during
- // recovery. We probably don't want to directly persist a Route in any other circumstances, either, to ease persistence.
- CommonAttributes attrs = set(safeStore, SaveStatus.Accepted, command, command, participants, ballot, null, partialDeps);
+ PartialDeps partialDeps = prepareDeps(validated, participants, command, deps);
+ participants = prepareParticipants(validated, participants, command);
- safeCommand.accept(safeStore, route, attrs, executeAt, ballot);
+ safeCommand.accept(safeStore, newSaveStatus, participants, ballot, executeAt, partialDeps, ballot);
safeStore.notifyListeners(safeCommand, command);
return AcceptOutcome.Success;
}
- public static AcceptOutcome acceptInvalidate(SafeCommandStore safeStore, SafeCommand safeCommand, Ballot ballot)
+ public static AcceptOutcome notAccept(SafeCommandStore safeStore, SafeCommand safeCommand, Status status, Ballot ballot)
{
// TODO (expected): save some partial route we can use to determine if it can be GC'd
final Command command = safeCommand.current();
if (command.hasBeen(PreCommitted))
{
- logger.trace("{}: skipping accept invalidated - already committed or invalidated ({})", command.txnId(), command.status());
+ logger.trace("{}: skipping not accept - already committed or invalidated ({})", command.txnId(), command.status());
return AcceptOutcome.Redundant;
}
if (command.promised().compareTo(ballot) > 0)
{
- logger.trace("{}: skipping accept invalidated - witnessed higher ballot ({} > {})", command.txnId(), command.promised(), ballot);
+ if (logger.isTraceEnabled())
+ logger.trace("{}: skipping {} - witnessed higher ballot ({} > {})", command.txnId(), status, command.promised(), ballot);
return AcceptOutcome.RejectedBallot;
}
- logger.trace("{}: accepted invalidated", command.txnId());
+ if (status.compareTo(command.status()) < 0 && command.acceptedOrCommitted().equals(ballot))
+ {
+ if (logger.isTraceEnabled())
+ logger.trace("{}: skipping {} - already acceptedOrCommitted ballot {} with status {}", command.txnId(), status, ballot, command.status());
+ return AcceptOutcome.Redundant;
+ }
+
+ logger.trace("{}: not accepted ({})", command.txnId(), status);
- safeCommand.acceptInvalidated(safeStore, ballot);
+ safeCommand.notAccept(safeStore, status, ballot);
safeStore.notifyListeners(safeCommand, command);
return AcceptOutcome.Success;
}
@@ -244,13 +289,13 @@ public class Commands
// relies on mutual exclusion for each key
- public static CommitOutcome commit(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, SaveStatus newStatus, Ballot ballot, TxnId txnId, Route<?> route, @Nullable PartialTxn partialTxn, Timestamp executeAt, PartialDeps partialDeps)
+ public static CommitOutcome commit(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, SaveStatus newSaveStatus, Ballot ballot, TxnId txnId, Route<?> route, @Nullable Txn txn, Timestamp executeAt, Deps deps, @Nullable Commit.Kind kind)
{
final Command command = safeCommand.current();
SaveStatus curStatus = command.saveStatus();
- Invariants.checkArgument(newStatus == SaveStatus.Committed || newStatus == SaveStatus.Stable);
- if (newStatus == SaveStatus.Committed && ballot.compareTo(command.promised()) < 0)
+ Invariants.checkArgument(newSaveStatus == SaveStatus.Committed || newSaveStatus == SaveStatus.Stable);
+ if (newSaveStatus == SaveStatus.Committed && ballot.compareTo(command.promised()) < 0)
return curStatus.is(Truncated) || participants.owns().isEmpty()
? CommitOutcome.Redundant : CommitOutcome.Rejected;
@@ -262,13 +307,13 @@ public class Commands
safeStore.agent().onInconsistentTimestamp(command, (curStatus.status == Invalidated ? Timestamp.NONE : command.executeAt()), executeAt);
}
- if (curStatus.compareTo(newStatus) > 0 || curStatus.hasBeen(Stable))
+ if (curStatus.compareTo(newSaveStatus) > 0 || curStatus.hasBeen(Stable))
{
logger.trace("{}: skipping commit - already newer or stable ({})", txnId, command.status());
return CommitOutcome.Redundant;
}
- if (curStatus == SaveStatus.Committed && newStatus == SaveStatus.Committed)
+ if (curStatus == SaveStatus.Committed && newSaveStatus == SaveStatus.Committed)
{
if (ballot.equals(command.acceptedOrCommitted()))
return CommitOutcome.Redundant;
@@ -278,17 +323,24 @@ public class Commands
}
participants = participants.filter(UPDATE, safeStore, txnId, executeAt);
- if (!validate(newStatus, command, participants, executeAt, route, partialTxn, partialDeps))
+ Known known = curStatus.known;
+ if (kind == StableMediumPath && known.is(DepsProposedFixed))
+ known = known.with(DepsKnown);
+ Validated validated = validate(ballot, known, newSaveStatus, command, participants, route, txn, deps);
+ if (validated == INSUFFICIENT)
return CommitOutcome.Insufficient;
- CommonAttributes attrs = set(safeStore, newStatus, command, command, participants, ballot, partialTxn, partialDeps);
+ PartialTxn partialTxn = prepareTxn(newSaveStatus, participants, command, txn);
+ PartialDeps partialDeps = prepareDeps(validated, participants, command, deps);
+ participants = prepareParticipants(validated, participants, command);
- logger.trace("{}: committed with executeAt: {}, deps: {}", txnId, executeAt, partialDeps);
+ if (logger.isTraceEnabled())
+ logger.trace("{}: committed with executeAt: {}, deps: {}", txnId, executeAt, deps);
final Command.Committed committed;
- if (newStatus == SaveStatus.Stable)
+ if (newSaveStatus == SaveStatus.Stable)
{
- WaitingOn waitingOn = initialiseWaitingOn(safeStore, txnId, attrs, executeAt, attrs.route());
- committed = safeCommand.stable(safeStore, attrs, Ballot.max(command.acceptedOrCommitted(), ballot), executeAt, waitingOn);
+ WaitingOn waitingOn = initialiseWaitingOn(safeStore, txnId, executeAt, participants, partialDeps);
+ committed = safeCommand.stable(safeStore, participants, ballot, executeAt, partialTxn, partialDeps, waitingOn);
safeStore.agent().metricsEventsListener().onStable(committed);
// TODO (expected, safety): introduce intermediate status to avoid reentry when notifying listeners (which might notify us)
maybeExecute(safeStore, safeCommand, true, true);
@@ -296,7 +348,7 @@ public class Commands
else
{
Invariants.checkArgument(command.acceptedOrCommitted().compareTo(ballot) <= 0);
- committed = safeCommand.commit(safeStore, attrs, ballot, executeAt);
+ committed = safeCommand.commit(safeStore, participants, ballot, executeAt, partialTxn, partialDeps);
safeStore.notifyListeners(safeCommand, committed);
safeStore.agent().metricsEventsListener().onCommitted(committed);
}
@@ -326,14 +378,14 @@ public class Commands
}
}
- CommonAttributes attrs = supplementParticipants(command, participants);
- safeCommand.precommit(safeStore, attrs, executeAt);
+ supplementParticipants(safeStore, safeCommand, participants);
+ safeCommand.precommit(safeStore, executeAt);
safeStore.notifyListeners(safeCommand, command);
logger.trace("{}: precommitted with executeAt: {}", txnId, executeAt);
return CommitOutcome.Success;
}
- public static void ephemeralRead(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, Route<?> route, TxnId txnId, PartialTxn partialTxn, PartialDeps partialDeps)
+ public static void ephemeralRead(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, Route<?> route, TxnId txnId, PartialTxn txn, PartialDeps deps)
{
// TODO (expected): introduce in-memory only commands
Command command = safeCommand.current();
@@ -346,9 +398,14 @@ public class Commands
participants = participants.supplement(route);
participants = participants.filter(UPDATE, safeStore, txnId, null);
- Invariants.checkState(validate(SaveStatus.Stable, command, participants, txnId, route, partialTxn, partialDeps));
- CommonAttributes attrs = set(safeStore, SaveStatus.Stable, command, command, participants, Ballot.ZERO, partialTxn, partialDeps);
- safeCommand.stable(safeStore, attrs, Ballot.ZERO, txnId, initialiseWaitingOn(safeStore, txnId, attrs, txnId, route));
+ Validated validated = validate(null, SaveStatus.Stable, command, participants, route, txn, deps);
+ Invariants.checkState(validated != INSUFFICIENT);
+
+ PartialTxn partialTxn = prepareTxn(SaveStatus.Stable, participants, command, txn);
+ PartialDeps partialDeps = prepareDeps(validated, participants, command, deps);
+ participants = prepareParticipants(validated, participants, command);
+
+ safeCommand.stable(safeStore, participants, Ballot.ZERO, txnId, partialTxn, partialDeps, initialiseWaitingOn(safeStore, txnId, txnId, participants, partialDeps));
maybeExecute(safeStore, safeCommand, false, true);
}
@@ -394,7 +451,7 @@ public class Commands
public enum ApplyOutcome { Success, Redundant, Insufficient }
- public static ApplyOutcome apply(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, TxnId txnId, Route<?> route, Timestamp executeAt, @Nullable PartialDeps partialDeps, @Nullable PartialTxn partialTxn, Writes writes, Result result)
+ public static ApplyOutcome apply(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants, TxnId txnId, Route<?> route, Timestamp executeAt, @Nullable Deps deps, @Nullable Txn txn, Writes writes, Result result)
{
Command command = safeCommand.current();
if (command.hasBeen(PreApplied) && executeAt.equals(command.executeAt()))
@@ -410,15 +467,20 @@ public class Commands
}
participants = participants.filter(UPDATE, safeStore, txnId, executeAt);
- if (!validate(SaveStatus.PreApplied, command, participants, executeAt, route, partialTxn, partialDeps))
+ Validated validated = validate(Ballot.ZERO, SaveStatus.PreApplied, command, participants, route, txn, deps);
+ if (validated == INSUFFICIENT)
return ApplyOutcome.Insufficient; // TODO (expected, consider): this should probably be an assertion failure if !TrySet
- CommonAttributes attrs = set(safeStore, SaveStatus.PreApplied, command, command, participants, null, partialTxn, partialDeps);
+ PartialTxn partialTxn = prepareTxn(SaveStatus.PreApplied, participants, command, txn);
+ PartialDeps partialDeps = prepareDeps(validated, participants, command, deps);
+ participants = prepareParticipants(validated, participants, command);
- WaitingOn waitingOn = !command.hasBeen(Stable) ? initialiseWaitingOn(safeStore, txnId, attrs, executeAt, attrs.route()) : command.asCommitted().waitingOn();
+ WaitingOn waitingOn = !command.hasBeen(Stable) ? initialiseWaitingOn(safeStore, txnId, executeAt, participants, partialDeps)
+ : command.asCommitted().waitingOn();
- safeCommand.preapplied(safeStore, attrs, executeAt, waitingOn, writes, result);
- logger.trace("{}: apply, status set to Executed with executeAt: {}, deps: {}", txnId, executeAt, partialDeps);
+ safeCommand.preapplied(safeStore, participants, executeAt, partialTxn, partialDeps, waitingOn, writes, result);
+ if (logger.isTraceEnabled())
+ logger.trace("{}: apply, status set to Executed with executeAt: {}, deps: {}", txnId, executeAt, partialDeps);
// must signal preapplied first, else we may be applied (and have cleared progress log state) already before maybeExecute exits
maybeExecute(safeStore, safeCommand, true, true);
@@ -438,15 +500,17 @@ public class Commands
return;
}
- logger.trace("{}: updating as listener in response to change on {} with status {} ({})",
- listener.txnId(), updated.txnId(), updated.status(), updated);
+ if (logger.isTraceEnabled())
+ logger.trace("{}: updating as listener in response to change on {} with status {} ({})",
+ listener.txnId(), updated.txnId(), updated.status(), updated);
+
switch (updated.status())
{
default:
throw illegalState("Unexpected status: " + updated.status());
case NotDefined:
case PreAccepted:
- case Accepted:
+ case AcceptedMedium:
case AcceptedInvalidate:
break;
@@ -558,7 +622,8 @@ public class Commands
public static boolean maybeExecute(SafeCommandStore safeStore, SafeCommand safeCommand, Command command, boolean alwaysNotifyListeners, boolean notifyWaitingOn)
{
if (logger.isTraceEnabled())
- logger.trace("{}: Maybe executing with status {}. Will notify listeners on noop: {}", command.txnId(), command.status(), alwaysNotifyListeners);
+ logger.trace("{}: Maybe executing with status {}. Will notify listeners on noop: {}",
+ command.txnId(), command.status(), alwaysNotifyListeners);
if (command.status() != Stable && command.saveStatus() != SaveStatus.PreApplied)
{
@@ -597,9 +662,9 @@ public class Commands
case PreApplied:
Command.Executed executed = command.asExecuted();
- Participants<?> executes = txnId.is(Read) ? null
- : executed.participants().stillExecutes()
- .intersecting(executed.writes().keys);
+ Participants<?> executes = !txnId.is(Write) ? null
+ : executed.participants().stillExecutes()
+ .intersecting(executed.writes().keys);
if (executes == null || executes.isEmpty())
{
logger.trace("{}: applying no-op", txnId);
@@ -619,24 +684,23 @@ public class Commands
}
}
- protected static WaitingOn initialiseWaitingOn(SafeCommandStore safeStore, TxnId waitingId, CommonAttributes waiting, Timestamp waitingExecuteAt, Route<?> route)
+ protected static WaitingOn initialiseWaitingOn(SafeCommandStore safeStore, TxnId waitingId, Timestamp waitingExecuteAt, StoreParticipants participants, PartialDeps deps)
{
if (waitingId.awaitsOnlyDeps())
waitingExecuteAt = Timestamp.maxForEpoch(waitingId.epoch());
- PartialDeps deps = waiting.partialDeps();
- WaitingOn.Update update = WaitingOn.Update.initialise(safeStore, waitingId, waitingExecuteAt, waiting.participants(), deps);
- return updateWaitingOn(safeStore, waiting, waitingExecuteAt, update, route.participants()).build();
+ WaitingOn.Initialise initialise = Update.initialise(safeStore, waitingId, waitingExecuteAt, participants, deps);
+ return updateWaitingOn(safeStore, initialise, waitingExecuteAt, initialise).build();
}
- protected static WaitingOn.Update updateWaitingOn(SafeCommandStore safeStore, CommonAttributes waiting, Timestamp executeAt, WaitingOn.Update update, Participants<?> participants)
+ protected static Update updateWaitingOn(SafeCommandStore safeStore, ICommand waiting, Timestamp executeAt, WaitingOn.Update initialise)
{
RedundantBefore redundantBefore = safeStore.redundantBefore();
- TxnId minWaitingOnTxnId = update.minWaitingOnTxnId();
- if (minWaitingOnTxnId != null && redundantBefore.hasLocallyRedundantDependencies(update.minWaitingOnTxnId(), executeAt, participants))
- redundantBefore.removeRedundantDependencies(participants, update);
+ TxnId minWaitingOnTxnId = initialise.minWaitingOnTxnId();
+ if (minWaitingOnTxnId != null && redundantBefore.hasLocallyRedundantDependencies(initialise.minWaitingOnTxnId(), executeAt, waiting.participants().executes()))
+ redundantBefore.removeRedundantDependencies(waiting.participants().executes(), initialise);
- update.forEachWaitingOnId(safeStore, update, waiting, executeAt, (store, upd, w, exec, i) -> {
+ initialise.forEachWaitingOnId(safeStore, initialise, waiting, executeAt, (store, upd, w, exec, i) -> {
// we don't want cleanup to transitively invoke a listener we've registered,
// as we might still be initialising the WaitingOn collection
SafeCommand dep = store.unsafeGetNoCleanup(upd.txnId(i));
@@ -645,20 +709,20 @@ public class Commands
updateWaitingOn(store, w, exec, upd, dep);
});
- return update;
+ return initialise;
}
/**
* @param dependencySafeCommand is either committed truncated, or invalidated
* @return true iff {@code maybeExecute} might now have a different outcome
*/
- private static boolean updateWaitingOn(SafeCommandStore safeStore, CommonAttributes waiting, Timestamp waitingExecuteAt, WaitingOn.Update waitingOn, SafeCommand dependencySafeCommand)
+ private static boolean updateWaitingOn(SafeCommandStore safeStore, ICommand waiting, Timestamp waitingExecuteAt, Update waitingOn, SafeCommand dependencySafeCommand)
{
TxnId waitingId = waiting.txnId();
Command dependency = dependencySafeCommand.current();
Invariants.checkState(dependency.hasBeen(PreCommitted));
TxnId dependencyId = dependency.txnId();
- if (waitingId.awaitsOnlyDeps() && dependency.known().executeAt == ExecuteAtKnown && dependency.executeAt().compareTo(waitingId) > 0)
+ if (waitingId.awaitsOnlyDeps() && dependency.known().is(ExecuteAtKnown) && dependency.executeAt().compareTo(waitingId) > 0)
waitingOn.updateExecuteAtLeast(waitingId, dependency.executeAt());
if (dependency.hasBeen(Truncated))
@@ -669,7 +733,7 @@ public class Commands
case TruncatedApply:
case TruncatedApplyWithOutcome:
case TruncatedApplyWithDeps:
- Invariants.checkState(dependency.executeAt().compareTo(waitingExecuteAt) < 0 || waitingId.awaitsOnlyDeps() || !dependency.txnId().kind().witnesses(waitingId));
+ Invariants.checkState(dependency.executeAt().compareTo(waitingExecuteAt) < 0 || waitingId.awaitsOnlyDeps() || !dependency.txnId().witnesses(waitingId));
case ErasedOrVestigial:
case Erased:
logger.trace("{}: {} is truncated. Stop listening and removing from waiting on commit set.", waitingId, dependencyId);
@@ -718,7 +782,7 @@ public class Commands
if (command.hasBeen(Applied))
return;
- WaitingOn.Update waitingOn = new WaitingOn.Update(command);
+ Update waitingOn = new Update(command);
if (updateWaitingOn(safeStore, command, command.executeAt(), waitingOn, predecessor))
{
safeCommand.updateWaitingOn(waitingOn);
@@ -758,7 +822,7 @@ public class Commands
if (keyIndex < 0 || !currentWaitingOn.isWaitingOnKey(keyIndex))
return;
- WaitingOn.Update waitingOn = new WaitingOn.Update(committed);
+ Update waitingOn = new Update(committed);
waitingOn.removeWaitingOnKey(keyIndex);
safeCommand.updateWaitingOn(waitingOn);
if (!waitingOn.isWaiting())
@@ -781,16 +845,16 @@ public class Commands
}
else
{
- CommonAttributes attributes = command.mutable().setParticipants(participants);
+ command = command.updateParticipants(participants);
if (!safeCommand.txnId().awaitsOnlyDeps())
{
- safeCommand.update(safeStore, truncatedApply(attributes, TruncatedApply, executeAt, null, null));
+ safeCommand.update(safeStore, truncatedApply(command, TruncatedApply, executeAt, null, null));
}
else if (safeCommand.current().saveStatus().hasBeen(Applied))
{
Timestamp executesAtLeast = safeCommand.current().executesAtLeast();
if (executesAtLeast == null) safeCommand.update(safeStore, erased(command));
- else safeCommand.update(safeStore, truncatedApply(attributes, TruncatedApply, executeAt, null, null, executesAtLeast));
+ else safeCommand.update(safeStore, truncatedApply(command, TruncatedApply, executeAt, null, null, executesAtLeast));
}
safeStore.progressLog().clear(command.txnId());
}
@@ -912,7 +976,7 @@ public class Commands
RedundantStatus status = redundantBefore.status(txnId, participants.owns());
switch (status)
{
- default: throw new AssertionError("Unhandled RedundantStatus: " + status);
+ default: throw new UnhandledEnum(status);
case LIVE:
case WAS_OWNED:
case WAS_OWNED_CLOSED:
@@ -963,18 +1027,18 @@ public class Commands
if (command.durability().compareTo(durability) >= 0)
return command;
- CommonAttributes attrs = supplementParticipants(command, participants);
- participants = attrs.participants();
+ Command updated = supplementParticipants(command, participants);
+ participants = updated.participants();
if (executeAt != null && command.status().hasBeen(Committed) && !command.executeAt().equals(executeAt))
safeStore.agent().onInconsistentTimestamp(command, command.asCommitted().executeAt(), executeAt);
if (command.durability().compareTo(durability) < 0)
{
- attrs = attrs.mutable().durability(durability);
+ updated = updated.updateDurability(durability);
TxnId txnId = command.txnId();
if (dependencyElision() == IF_DURABLE && CommandsForKey.manages(txnId))
{
- PreLoadContext context = PreLoadContext.contextFor(attrs.participants().touches(), INCR);
+ PreLoadContext context = PreLoadContext.contextFor(updated.participants().touches(), INCR);
PreLoadContext execute = safeStore.canExecute(context);
if (execute != null)
{
@@ -993,8 +1057,8 @@ public class Commands
}
}
- Command updated = safeCommand.updateAttributes(safeStore, attrs);
- if (maybeCleanup(safeStore, safeCommand, command, participants))
+ updated = safeCommand.update(safeStore, updated);
+ if (maybeCleanup(safeStore, safeCommand, updated, participants))
updated = safeCommand.current();
safeStore.notifyListeners(safeCommand, command);
@@ -1115,7 +1179,7 @@ public class Commands
Command dep = depSafe.current();
SaveStatus depStatus = dep.saveStatus();
SaveStatus.LocalExecution depExecution = depStatus.execution;
- if (!waitingId.awaitsOnlyDeps() && depStatus.known.executeAt == ExecuteAtKnown && dep.executeAt().compareTo(waiting.executeAt()) > 0)
+ if (!waitingId.awaitsOnlyDeps() && depStatus.known.is(ExecuteAtKnown) && dep.executeAt().compareTo(waiting.executeAt()) > 0)
depExecution = SaveStatus.LocalExecution.Applied;
Participants<?> participants = null;
@@ -1166,10 +1230,10 @@ public class Commands
switch (depExecution)
{
- default: throw new AssertionError("Unhandled LocalExecution: " + depStatus.execution);
+ default: throw new UnhandledEnum(depStatus.execution);
case NotReady:
if (logger.isTraceEnabled()) logger.trace("{} blocked on {} until ReadyToExclude", waitingId, dep.txnId());
- safeStore.registerListener(depSafe, HasDecidedExecuteAt.minSaveStatus, waitingId);
+ safeStore.registerListener(depSafe, HasDecidedExecuteAt.unblockedFrom, waitingId);
safeStore.progressLog().waiting(HasDecidedExecuteAt, safeStore, depSafe, dep.route(), participants, null);
return;
@@ -1232,7 +1296,7 @@ public class Commands
Command.Committed current = safeCommand.current().asCommitted();
RedundantBefore redundantBefore = safeStore.redundantBefore();
- WaitingOn.Update update = new WaitingOn.Update(current.waitingOn);
+ Update update = new Update(current.waitingOn);
TxnId minWaitingOnTxnId = update.minWaitingOnTxnId();
if (minWaitingOnTxnId != null && redundantBefore.hasLocallyRedundantDependencies(update.minWaitingOnTxnId(), current.executeAt(), current.participants().owns()))
redundantBefore.removeRedundantDependencies(current.participants().owns(), update);
@@ -1249,19 +1313,19 @@ public class Commands
if (!current.waitingOn.isWaitingOn(wasOwned))
return current;
- WaitingOn.Update update = new WaitingOn.Update(current.waitingOn);
+ Update update = new Update(current.waitingOn);
update.removeWaitingOn(wasOwned);
return safeCommand.updateWaitingOn(update);
}
- public static CommonAttributes supplementParticipants(Command command, StoreParticipants participants)
+ public static Command supplementParticipants(Command command, StoreParticipants participants)
{
StoreParticipants curParticipants = command.participants();
participants = curParticipants.supplementOrMerge(command.saveStatus(), participants);
if (curParticipants == participants)
return command;
- return command.mutable().setParticipants(participants);
+ return command.updateParticipants(participants);
}
public static Command supplementParticipants(SafeCommandStore safeStore, SafeCommand safeCommand, StoreParticipants participants)
@@ -1283,81 +1347,106 @@ public class Commands
return safeCommand.updateParticipants(safeStore, updated);
}
- private static CommonAttributes set(SafeCommandStore safeStore, SaveStatus newStatus, Command cur, CommonAttributes upd,
- StoreParticipants participants, @Nullable Ballot newAcceptedOrCommittedBallot,
- @Nullable PartialTxn partialTxn, @Nullable PartialDeps partialDeps)
+ private static PartialTxn prepareTxn(SaveStatus newSaveStatus, StoreParticipants newParticipants, Command upd, @Nullable Txn txn)
{
- Known haveKnown = cur.saveStatus().known;
- Known expectKnown = newStatus.known;
+ PartialTxn cur = upd.partialTxn();
+ if (txn == null || !newSaveStatus.known.definition().isKnown())
+ return cur;
- if (partialTxn != null && expectKnown.definition.isKnown())
- {
- partialTxn = partialTxn.intersecting(participants.stillOwns(), true);
- if (upd.partialTxn() != null)
- upd = upd.mutable().partialTxn(upd.partialTxn().with(partialTxn));
- else
- upd = upd.mutable().partialTxn(partialTxn);
- }
+ if (cur != null && cur.covers(newParticipants.stillOwns()))
+ return cur;
- if (!expectKnown.deps.hasProposedOrDecidedDeps())
+ PartialTxn add = txn.intersecting(newParticipants.stillOwns(), true);
+ return cur == null ? add : cur.with(add);
+ }
+
+ private static StoreParticipants prepareParticipants(Validated validated, StoreParticipants newParticipants, Command upd)
+ {
+ if (validated != UPDATE_TXN_KEEP_DEPS)
+ return newParticipants.supplement(upd.participants());
+
+ // unsafe to update participants.touches() without updating deps, as we expect them to cover the same keys and ranges
+ StoreParticipants cur = upd.participants();
+ if ((newParticipants.executes() != null && cur.executes() == null) || (!cur.hasFullRoute() && newParticipants.hasFullRoute()))
{
- participants = participants.supplement(cur.participants());
- upd = upd.mutable().setParticipants(participants);
+ StoreParticipants result = cur;
+ if (newParticipants.executes() != null)
+ result = result.withExecutes(newParticipants.executes(), newParticipants.stillExecutes());
+ if (!cur.hasFullRoute() && newParticipants.hasFullRoute())
+ result = result.supplement(newParticipants.route());
+ return result;
}
- else if (haveKnown.deps != Known.KnownDeps.DepsKnown && (haveKnown.deps != expectKnown.deps
- || (newAcceptedOrCommittedBallot != null && !newAcceptedOrCommittedBallot.equals(cur.acceptedOrCommitted()))))
+ return cur.supplement(newParticipants);
+ }
+
+ private static PartialDeps prepareDeps(Validated validated, StoreParticipants participants, Command upd, Deps newDeps)
+ {
+ switch (validated)
{
- participants = participants.supplement(cur.participants());
- upd = upd.mutable().setParticipants(participants);
- upd = upd.mutable().partialDeps(partialDeps.intersecting(participants.stillTouches()));
+ default: throw new UnhandledEnum(validated);
+ case UPDATE_TXN_KEEP_DEPS:
+ return upd.partialDeps();
+ case UPDATE_TXN_AND_DEPS:
+ return newDeps.intersecting(participants.stillTouches());
+ case UPDATE_TXN_IGNORE_DEPS:
+ return null;
}
+ }
- return upd;
+ enum Validated { INSUFFICIENT, UPDATE_TXN_IGNORE_DEPS, UPDATE_TXN_KEEP_DEPS, UPDATE_TXN_AND_DEPS }
+
+ private static Validated validate(@Nullable Ballot ballot, SaveStatus newStatus, Command cur, StoreParticipants participants,
+ Route<?> addRoute, @Nullable Txn addPartialTxn, @Nullable Deps partialDeps)
+ {
+ return validate(ballot, cur.known(), newStatus, cur, participants, addRoute, addPartialTxn, partialDeps);
}
- private static boolean validate(SaveStatus newStatus, Command cur, StoreParticipants participants,
- Timestamp executeAt, Route<?> addRoute,
- @Nullable PartialTxn addPartialTxn,
- @Nullable PartialDeps newPartialDeps)
+ private static Validated validate(@Nullable Ballot ballot, Known haveKnown, SaveStatus newStatus, Command cur, StoreParticipants participants,
+ Route<?> addRoute, @Nullable Txn addPartialTxn, @Nullable Deps partialDeps)
{
- Known haveKnown = cur.saveStatus().known;
Known expectKnown = newStatus.known;
Invariants.checkState(addRoute == participants.route());
- if (expectKnown.route == Full && !isFullRoute(cur.route()) && !isFullRoute(addRoute))
- return false;
+ if (expectKnown.has(FullRoute) && !isFullRoute(cur.route()) && !isFullRoute(addRoute))
+ return INSUFFICIENT;
- if (expectKnown.definition.isKnown())
+ if (expectKnown.definition().isKnown())
{
if (cur.txnId().isSystemTxn())
{
if (cur.partialTxn() == null && addPartialTxn == null)
- return false;
+ return INSUFFICIENT;
}
- else if (haveKnown.definition.isKnown())
+ else if (haveKnown.definition().isKnown())
{
// TODO (desired): avoid converting to participants before subtracting
Participants<?> extraScope = participants.stillOwns();
PartialTxn partialTxn = cur.partialTxn();
if (partialTxn != null)
extraScope = extraScope.without(partialTxn.keys().toParticipants());
- if (!containsAll(cur.txnId(), executeAt, addPartialTxn, extraScope))
- return false;
+ if (!containsAll(addPartialTxn, extraScope))
+ return INSUFFICIENT;
}
else
{
- if (!containsAll(cur.txnId(), executeAt, addPartialTxn, participants.stillOwns()))
- return false;
+ if (!containsAll(addPartialTxn, participants.stillOwns()))
+ return INSUFFICIENT;
}
}
- if (haveKnown.deps != expectKnown.deps && expectKnown.deps.hasProposedOrDecidedDeps())
- return containsAll(cur.txnId(), newPartialDeps, participants.stillTouches());
+ if (!expectKnown.deps().hasPreAcceptedOrProposedOrDecidedDeps())
+ return UPDATE_TXN_IGNORE_DEPS;
- return true;
+ if (haveKnown.is(DepsKnown) || (haveKnown.deps() == expectKnown.deps() && (ballot == null || ballot.equals(cur.acceptedOrCommitted()))))
+ return UPDATE_TXN_KEEP_DEPS;
+
+ if (!containsAll(partialDeps, participants.stillTouches()))
+ return INSUFFICIENT;
+
+ return UPDATE_TXN_AND_DEPS;
}
- private static <V> boolean containsAll(TxnId txnId, Timestamp executeAt, PartialTxn adding, Participants<?> required)
+ private static <V> boolean containsAll(Txn adding, Participants<?> required)
{
if (adding == null ? required.isEmpty() : adding.covers(required))
return true;
@@ -1365,7 +1454,7 @@ public class Commands
return false;
}
- private static <V> boolean containsAll(TxnId txnId, PartialDeps adding, Participants<?> required)
+ private static <V> boolean containsAll(Deps adding, Participants<?> required)
{
if (adding == null ? required.isEmpty() : adding.covers(required))
return true;
diff --git a/accord-core/src/main/java/accord/local/CommonAttributes.java b/accord-core/src/main/java/accord/local/CommonAttributes.java
deleted file mode 100644
index 771e66b2..00000000
--- a/accord-core/src/main/java/accord/local/CommonAttributes.java
+++ /dev/null
@@ -1,134 +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 accord.local;
-
-import accord.primitives.PartialDeps;
-import accord.primitives.PartialTxn;
-import accord.primitives.Route;
-import accord.primitives.Status;
-import accord.primitives.TxnId;
-import accord.utils.Invariants;
-
-import static accord.primitives.Status.Durability.NotDurable;
-
-public interface CommonAttributes
-{
- TxnId txnId();
- Status.Durability durability();
- StoreParticipants participants();
- default Route<?> route() { return participants().route(); }
- PartialTxn partialTxn();
- PartialDeps partialDeps();
-
- default Mutable mutable()
- {
- return new Mutable(this);
- }
-
- class Mutable implements CommonAttributes
- {
- private TxnId txnId;
- private Status.Durability durability;
- private StoreParticipants participants;
- private PartialTxn partialTxn;
- private PartialDeps partialDeps;
-
- public Mutable(TxnId txnId)
- {
- this.txnId = txnId;
- this.participants = StoreParticipants.empty(txnId);
- this.durability = NotDurable;
- }
-
- public Mutable(CommonAttributes attributes)
- {
- this.txnId = attributes.txnId();
- this.durability = attributes.durability();
- this.participants = Invariants.nonNull(attributes.participants());
- this.partialTxn = attributes.partialTxn();
- this.partialDeps = attributes.partialDeps();
- }
-
- @Override
- public Mutable mutable()
- {
- return this;
- }
-
- @Override
- public TxnId txnId()
- {
- return txnId;
- }
-
- public Mutable txnId(TxnId txnId)
- {
- this.txnId = txnId;
- return this;
- }
-
- @Override
- public Status.Durability durability()
- {
- return durability;
- }
-
- public Mutable durability(Status.Durability durability)
- {
- this.durability = durability;
- return this;
- }
-
- @Override
- public StoreParticipants participants()
- {
- return participants;
- }
-
- public Mutable setParticipants(StoreParticipants participants)
- {
- this.participants = participants;
- return this;
- }
-
- @Override
- public PartialTxn partialTxn()
- {
- return partialTxn;
- }
-
- public Mutable partialTxn(PartialTxn partialTxn)
- {
- this.partialTxn = partialTxn;
- return this;
- }
-
- @Override
- public PartialDeps partialDeps()
- {
- return partialDeps;
- }
-
- public Mutable partialDeps(PartialDeps partialDeps)
- {
- this.partialDeps = partialDeps;
- return this;
- }
- }
-}
diff --git a/accord-core/src/main/java/accord/local/ICommand.java b/accord-core/src/main/java/accord/local/ICommand.java
new file mode 100644
index 00000000..c04c735c
--- /dev/null
+++ b/accord-core/src/main/java/accord/local/ICommand.java
@@ -0,0 +1,242 @@
+/*
+ * 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.Result;
+import accord.local.Command.WaitingOn;
+import accord.primitives.Ballot;
+import accord.primitives.PartialDeps;
+import accord.primitives.PartialTxn;
+import accord.primitives.Status;
+import accord.primitives.Timestamp;
+import accord.primitives.TxnId;
+import accord.primitives.Writes;
+import accord.utils.Invariants;
+
+import static accord.primitives.Status.Durability.NotDurable;
+
+public interface ICommand
+{
+ TxnId txnId();
+ Status.Durability durability();
+ StoreParticipants participants();
+ Ballot promised();
+ PartialTxn partialTxn();
+ PartialDeps partialDeps();
+ Timestamp executeAt();
+ Ballot acceptedOrCommitted();
+ WaitingOn waitingOn();
+ Writes writes();
+ Result result();
+
+ default Builder deconstruct()
+ {
+ return new Builder(this);
+ }
+
+ class Builder implements ICommand
+ {
+ private TxnId txnId;
+ private Status.Durability durability;
+ private StoreParticipants participants;
+ private Ballot promised;
+ private PartialTxn partialTxn;
+ private PartialDeps partialDeps;
+ private Timestamp executeAt;
+ private Ballot acceptedOrCommitted;
+ private WaitingOn waitingOn;
+ private Writes writes;
+ private Result result;
+
+ public Builder(TxnId txnId)
+ {
+ this.txnId = txnId;
+ this.participants = StoreParticipants.empty(txnId);
+ this.durability = NotDurable;
+ }
+
+ public Builder(ICommand copy)
+ {
+ this.txnId = copy.txnId();
+ this.durability = copy.durability();
+ this.participants = Invariants.nonNull(copy.participants());
+ this.promised = copy.promised();
+ this.partialTxn = copy.partialTxn();
+ this.partialDeps = copy.partialDeps();
+ this.executeAt = copy.executeAt();
+ this.acceptedOrCommitted = copy.acceptedOrCommitted();
+ this.waitingOn = copy.waitingOn();
+ this.writes = copy.writes();
+ this.result = copy.result();
+ }
+
+ @Override
+ public Builder deconstruct()
+ {
+ return this;
+ }
+
+ @Override
+ public TxnId txnId()
+ {
+ return txnId;
+ }
+
+ public Builder txnId(TxnId txnId)
+ {
+ this.txnId = txnId;
+ return this;
+ }
+
+ @Override
+ public Status.Durability durability()
+ {
+ return durability;
+ }
+
+ public Builder durability(Status.Durability durability)
+ {
+ this.durability = durability;
+ return this;
+ }
+
+ @Override
+ public StoreParticipants participants()
+ {
+ return participants;
+ }
+
+ @Override
+ public Ballot promised()
+ {
+ return promised;
+ }
+
+ public Builder promised(Ballot promised)
+ {
+ this.promised = promised;
+ return this;
+ }
+
+ public Builder setParticipants(StoreParticipants participants)
+ {
+ this.participants = participants;
+ return this;
+ }
+
+ @Override
+ public PartialTxn partialTxn()
+ {
+ return partialTxn;
+ }
+
+ public Builder partialTxn(PartialTxn partialTxn)
+ {
+ this.partialTxn = partialTxn;
+ return this;
+ }
+
+ @Override
+ public PartialDeps partialDeps()
+ {
+ return partialDeps;
+ }
+
+ @Override
+ public Timestamp executeAt()
+ {
+ return executeAt;
+ }
+
+ public Builder executeAt(Timestamp executeAt)
+ {
+ this.executeAt = executeAt;
+ return this;
+ }
+
+ @Override
+ public Ballot acceptedOrCommitted()
+ {
+ return acceptedOrCommitted;
+ }
+
+ @Override
+ public WaitingOn waitingOn()
+ {
+ return waitingOn;
+ }
+
+ public Builder waitingOn(WaitingOn waitingOn)
+ {
+ this.waitingOn = waitingOn;
+ return this;
+ }
+
+ @Override
+ public Writes writes()
+ {
+ return writes;
+ }
+
+ public Builder writes(Writes writes)
+ {
+ this.writes = writes;
+ return this;
+ }
+
+ @Override
+ public Result result()
+ {
+ return result;
+ }
+
+ public Builder result(Result result)
+ {
+ this.result = result;
+ return this;
+ }
+
+ public Builder acceptedOrCommitted(Ballot acceptedOrCommitted)
+ {
+ this.acceptedOrCommitted = acceptedOrCommitted;
+ return this;
+ }
+
+ public Builder partialDeps(PartialDeps partialDeps)
+ {
+ this.partialDeps = partialDeps;
+ return this;
+ }
+ }
+
+ class EmptyCommand implements ICommand
+ {
+ @Override public TxnId txnId() { return null; }
+ @Override public Status.Durability durability() { return null; }
+ @Override public StoreParticipants participants() { return null; }
+ @Override public Ballot promised() { return null; }
+ @Override public PartialTxn partialTxn() { return null; }
+ @Override public PartialDeps partialDeps() { return null; }
+ @Override public Timestamp executeAt() { return null; }
+ @Override public Ballot acceptedOrCommitted() { return null; }
+ @Override public WaitingOn waitingOn() { return null; }
+ @Override public Writes writes() { return null; }
+ @Override public Result result() { return null; }
+ }
+}
diff --git a/accord-core/src/main/java/accord/local/KeyHistory.java b/accord-core/src/main/java/accord/local/KeyHistory.java
index 67445271..90ffd934 100644
--- a/accord-core/src/main/java/accord/local/KeyHistory.java
+++ b/accord-core/src/main/java/accord/local/KeyHistory.java
@@ -18,6 +18,8 @@
package accord.local;
+import accord.utils.UnhandledEnum;
+
/**
* For operations that need information on historical operations, this indicates the
* amount of data needed.
@@ -63,7 +65,7 @@ public enum KeyHistory
{
switch (that)
{
- default: throw new AssertionError("Unhandled KeyHistory: " + that);
+ default: throw new UnhandledEnum(that);
case NONE:
return true;
case RECOVER:
diff --git a/accord-core/src/main/java/accord/local/Node.java b/accord-core/src/main/java/accord/local/Node.java
index 254280e0..8d19412b 100644
--- a/accord-core/src/main/java/accord/local/Node.java
+++ b/accord-core/src/main/java/accord/local/Node.java
@@ -19,12 +19,10 @@
package accord.local;
import java.util.Collection;
-import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -73,6 +71,7 @@ import accord.primitives.FullRoute;
import accord.primitives.Ranges;
import accord.primitives.Routable.Domain;
import accord.primitives.Routables;
+import accord.primitives.Seekables;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
@@ -96,6 +95,10 @@ import accord.utils.async.AsyncResults;
import accord.utils.async.Cancellable;
import net.nicoulaj.compilecommand.annotations.Inline;
+import static accord.api.ProtocolModifiers.Toggles.ensurePermitted;
+import static accord.api.ProtocolModifiers.Toggles.defaultMediumPath;
+import static accord.api.ProtocolModifiers.Toggles.usePrivilegedCoordinator;
+import static accord.primitives.TxnId.FastPath.UNOPTIMISED;
import static accord.utils.Invariants.illegalState;
import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -561,8 +564,7 @@ public class Node implements ConfigurationService.Listener, NodeCommandStoreServ
// send to every node besides ourselves
public void send(Topology topology, Request send)
{
- Set<Id> contacted = new HashSet<>();
- topology.forEach(shard -> send(shard, send, contacted));
+ topology.nodes().forEach(id -> send(id, send));
}
public void send(Shard shard, Request send)
@@ -581,14 +583,6 @@ public class Node implements ConfigurationService.Listener, NodeCommandStoreServ
shard.nodes.forEach(node -> messageSink.send(node, send, executor, callback));
}
- private <T> void send(Shard shard, Request send, Set<Id> alreadyContacted)
- {
- shard.nodes.forEach(node -> {
- if (alreadyContacted.add(node))
- send(node, send);
- });
- }
-
public void send(Collection<Id> to, Request send)
{
checkIterationSafe(to);
@@ -686,19 +680,63 @@ public class Node implements ConfigurationService.Listener, NodeCommandStoreServ
messageSink.reply(replyingToNode, replyContext, send);
}
+ public TxnId nextTxnId(Txn.Kind rw, Domain domain)
+ {
+ return nextTxnId(rw, domain, defaultMediumPath().bits);
+ }
+
/**
* TODO (required): Make sure we cannot re-issue the same txnid on startup
*/
- public TxnId nextTxnId(Txn.Kind rw, Domain domain)
+ public TxnId nextTxnId(Txn.Kind rw, Domain domain, int flags)
{
- TxnId txnId = new TxnId(uniqueNow(), rw, domain);
+ TxnId txnId = new TxnId(uniqueNow(), flags, rw, domain);
Invariants.checkState((txnId.lsb & (0xffff & ~TxnId.IDENTITY_FLAGS)) == 0);
return txnId;
}
+ public TxnId nextTxnId(Txn txn)
+ {
+ Seekables<?, ?> keys = txn.keys();
+ Txn.Kind kind = txn.kind();
+ Domain domain = keys.domain();
+ if (!usePrivilegedCoordinator())
+ return nextTxnId(kind, domain);
+
+ Timestamp now = uniqueNow();
+ int flags = computeBestDefaultTxnIdFlags(keys, now.epoch());
+ TxnId txnId = new TxnId(now, flags, kind, domain);
+ Invariants.checkState((txnId.lsb & (0xffff & ~TxnId.IDENTITY_FLAGS)) == 0);
+ return txnId;
+ }
+
+ private int computeBestDefaultTxnIdFlags(Routables<?> keys, long epoch)
+ {
+ if (!topology.hasEpoch(epoch) || !usePrivilegedCoordinator())
+ return defaultMediumPath().bits;
+
+ TxnId.FastPath fastPath = ensurePermitted(topology().selectFastPath(keys, epoch));
+ return fastPath.bits | defaultMediumPath().bits;
+ }
+
+ public TxnId nextTxnId(Txn txn, TxnId.FastPath fastPath, TxnId.MediumPath mediumPath)
+ {
+ Seekables<?, ?> keys = txn.keys();
+ Txn.Kind kind = txn.kind();
+ Domain domain = keys.domain();
+
+ Timestamp now = uniqueNow();
+ fastPath = ensurePermitted(fastPath);
+ if (fastPath != UNOPTIMISED && (!topology.hasEpoch(now.epoch()) || !topology.supportsPrivilegedFastPath(keys, now.epoch())))
+ fastPath = UNOPTIMISED;
+
+ return nextTxnId(kind, domain, fastPath.bits | mediumPath.bits);
+ }
+
public AsyncResult<Result> coordinate(Txn txn)
{
- return coordinate(nextTxnId(txn.kind(), txn.keys().domain()), txn);
+ TxnId txnId = nextTxnId(txn);
+ return coordinate(txnId, txn);
}
public AsyncResult<Result> coordinate(TxnId txnId, Txn txn)
@@ -706,6 +744,7 @@ public class Node implements ConfigurationService.Listener, NodeCommandStoreServ
return coordinate(txnId, txn, txnId.epoch(), Long.MAX_VALUE);
}
+ // TODO (required): plumb deadlineNanos in (perhaps on integration side, but maybe introduce some context we can pass through for the MessageSink)
public AsyncResult<Result> coordinate(TxnId txnId, Txn txn, long minEpoch, long deadlineNanos)
{
// TODO (desirable, consider): The combination of updating the epoch of the next timestamp with epochs we don't have topologies for,
diff --git a/accord-core/src/main/java/accord/local/NodeCommandStoreService.java b/accord-core/src/main/java/accord/local/NodeCommandStoreService.java
index 4ee7686e..b72388bf 100644
--- a/accord-core/src/main/java/accord/local/NodeCommandStoreService.java
+++ b/accord-core/src/main/java/accord/local/NodeCommandStoreService.java
@@ -20,6 +20,7 @@ package accord.local;
import accord.api.Timeouts;
import accord.primitives.Timestamp;
+import accord.topology.TopologyManager;
public interface NodeCommandStoreService extends TimeService
{
@@ -29,4 +30,5 @@ public interface NodeCommandStoreService extends TimeService
DurableBefore durableBefore();
Timestamp uniqueNow();
Timestamp uniqueNow(Timestamp atLeast);
+ TopologyManager topology();
}
diff --git a/accord-core/src/main/java/accord/local/RedundantBefore.java b/accord-core/src/main/java/accord/local/RedundantBefore.java
index c0e4d391..70529fc8 100644
--- a/accord-core/src/main/java/accord/local/RedundantBefore.java
+++ b/accord-core/src/main/java/accord/local/RedundantBefore.java
@@ -64,6 +64,7 @@ import static accord.local.RedundantStatus.WAS_OWNED_PARTIALLY_RETIRED;
import static accord.local.RedundantStatus.WAS_OWNED_RETIRED;
import static accord.primitives.Txn.Kind.ExclusiveSyncPoint;
import static accord.utils.Invariants.illegalState;
+import static accord.utils.Invariants.partiallyOrdered;
public class RedundantBefore extends ReducingRangeMap<RedundantBefore.Entry>
{
@@ -186,15 +187,9 @@ public class RedundantBefore extends ReducingRangeMap<RedundantBefore.Entry>
this.staleUntilAtLeast = staleUntilAtLeast;
checkNoneOrRX(locallyWitnessedOrInvalidatedBefore, locallyAppliedOrInvalidatedBefore, locallyDecidedAndAppliedOrInvalidatedBefore,
shardAppliedOrInvalidatedBefore, gcBefore);
- checkPartiallyOrdered(locallyDecidedAndAppliedOrInvalidatedBefore, locallyAppliedOrInvalidatedBefore);
- checkPartiallyOrdered(shardAppliedOrInvalidatedBefore, locallyAppliedOrInvalidatedBefore);
- checkPartiallyOrdered(gcBefore, shardAppliedOrInvalidatedBefore, shardOnlyAppliedOrInvalidatedBefore);
- }
-
- private static void checkPartiallyOrdered(TxnId ... txnIds)
- {
- for (int i = 1 ; i < txnIds.length ; ++i)
- Invariants.checkArgument(txnIds[i - 1].compareTo(txnIds[i]) <= 0);
+ partiallyOrdered(locallyDecidedAndAppliedOrInvalidatedBefore, locallyAppliedOrInvalidatedBefore);
+ partiallyOrdered(shardAppliedOrInvalidatedBefore, locallyAppliedOrInvalidatedBefore);
+ partiallyOrdered(gcBefore, shardAppliedOrInvalidatedBefore, shardOnlyAppliedOrInvalidatedBefore);
}
private static void checkNoneOrRX(TxnId ... txnIds)
diff --git a/accord-core/src/main/java/accord/local/SafeCommand.java b/accord-core/src/main/java/accord/local/SafeCommand.java
index 4d1d0f24..b18fd023 100644
--- a/accord-core/src/main/java/accord/local/SafeCommand.java
+++ b/accord-core/src/main/java/accord/local/SafeCommand.java
@@ -18,13 +18,17 @@
package accord.local;
+import javax.annotation.Nonnull;
+
import accord.api.Result;
import accord.local.Command.Truncated;
import accord.primitives.Ballot;
+import accord.primitives.PartialDeps;
+import accord.primitives.PartialTxn;
+import accord.primitives.SaveStatus;
import accord.primitives.Status;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
-import accord.primitives.Unseekables;
import accord.primitives.Writes;
import accord.utils.Invariants;
@@ -65,7 +69,7 @@ public abstract class SafeCommand
return update;
}
- private <C extends Command> C incidentalUpdate(C update)
+ public <C extends Command> C incidentalUpdate(C update)
{
if (current() == update)
return update;
@@ -79,14 +83,6 @@ public abstract class SafeCommand
return incidentalUpdate(Command.updateWaitingOn(current().asCommitted(), waitingOn));
}
- public Command updateAttributes(SafeCommandStore safeStore, CommonAttributes attrs)
- {
- Command prev = current();
- Command update = incidentalUpdate(prev.updateAttributes(attrs));
- safeStore.progressLog().update(safeStore, txnId, prev, update);
- return update;
- }
-
public Command updateParticipants(SafeCommandStore safeStore, StoreParticipants participants)
{
Command prev = current();
@@ -98,14 +94,14 @@ public abstract class SafeCommand
return update;
}
- public Command.PreAccepted preaccept(SafeCommandStore safeStore, CommonAttributes attrs, Timestamp executeAt, Ballot ballot)
+ public Command.PreAccepted preaccept(SafeCommandStore safeStore, SaveStatus saveStatus, StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps)
{
- return update(safeStore, Command.preaccept(current(), attrs, executeAt, ballot));
+ return update(safeStore, Command.preaccept(current(), saveStatus, participants, promised, executeAt, partialTxn, partialDeps));
}
- public Command.Accepted markDefined(SafeCommandStore safeStore, CommonAttributes attributes, Ballot promised)
+ public Command.Accepted markDefined(SafeCommandStore safeStore, StoreParticipants participants, Ballot promised, PartialTxn partialTxn)
{
- return update(safeStore, Command.markDefined(current(), attributes, promised));
+ return update(safeStore, Command.markDefined(current(), participants, promised, partialTxn));
}
public Command updatePromised(Ballot promised)
@@ -113,26 +109,24 @@ public abstract class SafeCommand
return incidentalUpdate(current().updatePromised(promised));
}
- public Command.Accepted accept(SafeCommandStore safeStore, Unseekables<?> keysOrRanges, CommonAttributes attrs, Timestamp executeAt, Ballot ballot)
+ public Command.Accepted accept(SafeCommandStore safeStore, SaveStatus status, @Nonnull StoreParticipants participants, Ballot promised, Timestamp executeAt, PartialDeps partialDeps, Ballot acceptedOrCommitted)
{
- Command current = current();
- Command.Accepted updated = Command.accept(current, attrs, executeAt, ballot);
- return update(safeStore, updated);
+ return update(safeStore, Command.accept(current(), status, participants, promised, executeAt, partialDeps, acceptedOrCommitted));
}
- public Command acceptInvalidated(SafeCommandStore safeStore, Ballot ballot)
+ public Command notAccept(SafeCommandStore safeStore, Status status, Ballot ballot)
{
- return update(safeStore, Command.acceptInvalidated(current(), ballot));
+ return update(safeStore, Command.notAccept(status, current(), ballot));
}
- public Command.Committed commit(SafeCommandStore safeStore, CommonAttributes attrs, Ballot ballot, Timestamp executeAt)
+ public Command.Committed commit(SafeCommandStore safeStore, @Nonnull StoreParticipants participants, Ballot ballot, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps)
{
- return update(safeStore, Command.commit(current(), attrs, ballot, executeAt));
+ return update(safeStore, Command.commit(current(), participants, ballot, executeAt, partialTxn, partialDeps));
}
- public Command.Committed stable(SafeCommandStore safeStore, CommonAttributes attrs, Ballot ballot, Timestamp executeAt, Command.WaitingOn waitingOn)
+ public Command.Committed stable(SafeCommandStore safeStore, @Nonnull StoreParticipants participants, Ballot ballot, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, Command.WaitingOn waitingOn)
{
- return update(safeStore, Command.stable(current(), attrs, ballot, executeAt, waitingOn));
+ return update(safeStore, Command.stable(current(), participants, ballot, executeAt, partialTxn, partialDeps, waitingOn));
}
public Truncated commitInvalidated(SafeCommandStore safeStore)
@@ -144,9 +138,9 @@ public abstract class SafeCommand
return update(safeStore, Truncated.invalidated(current));
}
- public Command precommit(SafeCommandStore safeStore, CommonAttributes attrs, Timestamp executeAt)
+ public Command precommit(SafeCommandStore safeStore, Timestamp executeAt)
{
- return update(safeStore, Command.precommit(attrs, current(), executeAt));
+ return update(safeStore, Command.precommit(current(), executeAt));
}
public Command.Committed readyToExecute(SafeCommandStore safeStore)
@@ -154,9 +148,9 @@ public abstract class SafeCommand
return update(safeStore, Command.readyToExecute(current().asCommitted()));
}
- public Command.Executed preapplied(SafeCommandStore safeStore, CommonAttributes attrs, Timestamp executeAt, Command.WaitingOn waitingOn, Writes writes, Result result)
+ public Command.Executed preapplied(SafeCommandStore safeStore, @Nonnull StoreParticipants participants, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, Command.WaitingOn waitingOn, Writes writes, Result result)
{
- return update(safeStore, Command.preapplied(current(), attrs, executeAt, waitingOn, writes, result));
+ return update(safeStore, Command.preapplied(current(), participants, executeAt, partialTxn, partialDeps, waitingOn, writes, result));
}
public Command.Executed applying(SafeCommandStore safeStore)
diff --git a/accord-core/src/main/java/accord/local/SafeCommandStore.java b/accord-core/src/main/java/accord/local/SafeCommandStore.java
index c661a1b0..429b0be7 100644
--- a/accord-core/src/main/java/accord/local/SafeCommandStore.java
+++ b/accord-core/src/main/java/accord/local/SafeCommandStore.java
@@ -46,6 +46,7 @@ import accord.primitives.Unseekables;
import accord.utils.Invariants;
import accord.utils.SimpleBitSet;
import accord.utils.SortedList;
+import accord.utils.UnhandledEnum;
import accord.utils.async.AsyncChain;
import static accord.local.KeyHistory.INCR;
@@ -269,7 +270,7 @@ public abstract class SafeCommandStore implements RangesForEpochSupplier, Redund
{
SaveStatus oldSaveStatus = prev == null ? SaveStatus.Uninitialised : prev.saveStatus();
SaveStatus newSaveStatus = updated.saveStatus();
- if (newSaveStatus.status.equals(oldSaveStatus.status) && oldSaveStatus.known.definition.isKnown())
+ if (newSaveStatus.status.equals(oldSaveStatus.status) && oldSaveStatus.known.definition().isKnown())
return;
TxnId txnId = updated.txnId();
@@ -390,7 +391,7 @@ public abstract class SafeCommandStore implements RangesForEpochSupplier, Redund
RedundantStatus status = redundantBefore.status(maxTxnId, key);
switch (status)
{
- default: throw new AssertionError("Unhandled RedundantStatus: " + status);
+ default: throw new UnhandledEnum(status);
case NOT_OWNED:
case WAS_OWNED:
case WAS_OWNED_CLOSED:
diff --git a/accord-core/src/main/java/accord/local/StoreParticipants.java b/accord-core/src/main/java/accord/local/StoreParticipants.java
index cd91450b..7f4e5c6c 100644
--- a/accord-core/src/main/java/accord/local/StoreParticipants.java
+++ b/accord-core/src/main/java/accord/local/StoreParticipants.java
@@ -36,7 +36,7 @@ import accord.utils.Invariants;
import static accord.local.StoreParticipants.Filter.QUERY;
import static accord.local.StoreParticipants.Filter.UPDATE;
-import static accord.primitives.Known.KnownRoute.Maybe;
+import static accord.primitives.Known.KnownRoute.MaybeRoute;
import static accord.primitives.Routable.Domain.Key;
import static accord.primitives.Routables.Slice.Minimal;
import static accord.primitives.Route.tryCastToRoute;
@@ -88,6 +88,11 @@ public class StoreParticipants
{
return hasTouched;
}
+
+ public boolean touchesOnlyOwned()
+ {
+ return touches.equals(owns());
+ }
}
public static class FilteredStoreParticipants extends FullStoreParticipants
@@ -224,6 +229,11 @@ public class StoreParticipants
return owns;
}
+ public boolean touchesOnlyOwned()
+ {
+ return true;
+ }
+
/**
* touches, but excluding any stale, pre-bootstrap or retired ranges that are also shard redundant,
* as we do not need to do anything with these keys locally
@@ -275,7 +285,7 @@ public class StoreParticipants
/**
* Do not invoke this method on a participants we will use to query esp. e.g. for calculateDeps.
- * TODO (required): create separate Query object that cannot be filtered or used to update a Command
+ * TODO (desired): create separate Query object that cannot be filtered or used to update a Command
*/
public StoreParticipants filter(Filter filter, RedundantBefore redundantBefore, TxnId txnId, @Nullable EpochSupplier executeAt)
{
@@ -353,7 +363,7 @@ public class StoreParticipants
if (currentSaveStatus.compareTo(Erased) >= 0)
return this;
- if (currentSaveStatus.known.route == Maybe)
+ if (currentSaveStatus.known.has(MaybeRoute))
return merge(that);
return supplement(that);
@@ -403,6 +413,14 @@ public class StoreParticipants
return new FullStoreParticipants(route, owns, executes, touches, hasTouched);
}
+ public StoreParticipants withExecutes(Participants<?> executes, Participants<?> stillExecutes)
+ {
+ if (executes == stillExecutes)
+ return update(route, owns, executes, touches(), hasTouched());
+
+ return new FilteredStoreParticipants(route, owns, executes, touches(), hasTouched(), stillOwns(), stillTouches(), stillExecutes);
+ }
+
// TODO (required): retire this method, merge with executes()
public Ranges executeRanges(SafeCommandStore safeStore, TxnId txnId, Timestamp executeAt)
{
@@ -415,16 +433,13 @@ public class StoreParticipants
}
// TODO (required): synchronise with latest standard logic
- public static Route<?> touches(SafeCommandStore safeStore, TxnId txnId, EpochSupplier toEpoch, Route<?> route)
+ public static Route<?> touches(SafeCommandStore safeStore, EpochSupplier fromEpoch, TxnId txnId, EpochSupplier toEpoch, Route<?> route)
{
// TODO (required): remove pre-bootstrap?
if (txnId.is(ExclusiveSyncPoint))
return route.slice(safeStore.ranges().all(), Minimal);
- if (txnId == toEpoch || txnId.epoch() == toEpoch.epoch())
- return route;
-
- return route.slice(safeStore.ranges().allBetween(txnId.epoch(), toEpoch), Minimal);
+ return route.slice(safeStore.ranges().allBetween(fromEpoch.epoch(), toEpoch), Minimal);
}
@Override
@@ -519,10 +534,11 @@ public class StoreParticipants
return create(tryCastToRoute(participants), owns, executes, touches, touches);
}
- public static StoreParticipants invalidate(SafeCommandStore safeStore, Participants<?> participants, TxnId txnId)
+ public static StoreParticipants notAccept(SafeCommandStore safeStore, Participants<?> participants, TxnId txnId)
{
RangesForEpoch storeRanges = safeStore.ranges();
Ranges ownedRanges = storeRanges.allAt(txnId.epoch());
+ // TODO (expected): do we need to extend touches here? Feels likely this harks back to before we saved hasTouched
Ranges touchesRanges = storeRanges.all();
Participants<?> owns = participants.slice(ownedRanges, Minimal);
Participants<?> touches = ownedRanges == touchesRanges || owns == participants ? owns : participants.slice(touchesRanges, Minimal);
diff --git a/accord-core/src/main/java/accord/local/cfk/CommandsForKey.java b/accord-core/src/main/java/accord/local/cfk/CommandsForKey.java
index f0500ae4..4fd58bba 100644
--- a/accord-core/src/main/java/accord/local/cfk/CommandsForKey.java
+++ b/accord-core/src/main/java/accord/local/cfk/CommandsForKey.java
@@ -53,25 +53,28 @@ import accord.primitives.Txn.Kind.Kinds;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import accord.utils.SortedArrays;
+import accord.utils.UnhandledEnum;
import accord.utils.btree.BTree;
import static accord.api.ProgressLog.BlockedUntil.CanApply;
import static accord.api.ProgressLog.BlockedUntil.HasStableDeps;
import static accord.api.ProtocolModifiers.Toggles.dependencyElision;
-import static accord.local.CommandSummaries.IsDep.IS_DEP;
-import static accord.local.CommandSummaries.IsDep.IS_NOT_DEP;
+import static accord.local.CommandSummaries.IsDep.IS_COORD_DEP;
+import static accord.local.CommandSummaries.IsDep.IS_STABLE_DEP;
+import static accord.local.CommandSummaries.IsDep.IS_NOT_COORD_DEP;
+import static accord.local.CommandSummaries.IsDep.IS_NOT_STABLE_DEP;
import static accord.local.CommandSummaries.IsDep.NOT_ELIGIBLE;
import static accord.local.CommandSummaries.SummaryStatus.APPLIED;
-import static accord.local.CommandSummaries.SummaryStatus.COMMITTED;
-import static accord.local.CommandSummaries.SummaryStatus.NOT_ACCEPTED;
+import static accord.local.CommandSummaries.SummaryStatus.PREACCEPTED;
+import static accord.local.cfk.CommandsForKey.InternalStatus.ACCEPTED;
import static accord.local.cfk.CommandsForKey.InternalStatus.APPLIED_DURABLE;
import static accord.local.cfk.CommandsForKey.InternalStatus.APPLIED_NOT_DURABLE;
-import static accord.local.cfk.CommandsForKey.InternalStatus.INVALIDATED;
-import static accord.local.cfk.CommandsForKey.InternalStatus.PREACCEPTED_OR_ACCEPTED_INVALIDATE;
+import static accord.local.cfk.CommandsForKey.InternalStatus.COMMITTED;
+import static accord.local.cfk.CommandsForKey.InternalStatus.PRUNED;
import static accord.local.cfk.CommandsForKey.InternalStatus.STABLE;
import static accord.local.cfk.CommandsForKey.InternalStatus.TO_SUMMARY_STATUS;
+import static accord.local.cfk.CommandsForKey.InternalStatus.INVALIDATED;
import static accord.local.cfk.CommandsForKey.InternalStatus.TRANSITIVE;
-import static accord.local.cfk.CommandsForKey.InternalStatus.PRUNED;
import static accord.local.cfk.CommandsForKey.InternalStatus.from;
import static accord.local.cfk.CommandsForKey.InternalStatus.prunedFrom;
import static accord.local.cfk.PostProcess.notifyManagedPreBootstrap;
@@ -84,8 +87,10 @@ import static accord.local.cfk.UpdateUnmanagedMode.UPDATE;
import static accord.local.cfk.Updating.insertOrUpdate;
import static accord.local.CommandSummaries.ComputeIsDep.IGNORE;
import static accord.primitives.Routable.Domain.Key;
-import static accord.primitives.Txn.Kind.Kinds.AnyGloballyVisible;
+import static accord.primitives.Timestamp.Flag.UNSTABLE;
+import static accord.primitives.Txn.Kind.AnyGloballyVisible;
import static accord.primitives.Txn.Kind.Write;
+import static accord.primitives.TxnId.FastPath.PRIVILEGED_COORDINATOR_WITH_DEPS;
import static accord.primitives.TxnId.NO_TXNIDS;
import static accord.utils.Invariants.Paranoia.LINEAR;
import static accord.utils.Invariants.Paranoia.NONE;
@@ -293,7 +298,7 @@ public class CommandsForKey extends CommandsForKeyUpdate
if (!newStatus.hasBallot)
return false;
- Ballot prevAcceptedOrCommitted = prev == null ? Ballot.ZERO : prev.acceptedOrCommitted();
+ Ballot prevAcceptedOrCommitted = prev.acceptedOrCommitted();
Ballot newAcceptedOrCommitted = updated.acceptedOrCommitted();
return newAcceptedOrCommitted.compareTo(prevAcceptedOrCommitted) > 0;
@@ -806,10 +811,14 @@ public class CommandsForKey extends CommandsForKeyUpdate
// TODO (expected): use TRANSITIVE instead of TRANSITIVE_VISIBLE when we don't need to sync dependencies
TRANSITIVE (SummaryStatus.NOT_DIRECTLY_WITNESSED, false, false, false, false),
TRANSITIVE_VISIBLE (SummaryStatus.NOT_DIRECTLY_WITNESSED, false, false, false, false),
- PREACCEPTED_OR_ACCEPTED_INVALIDATE (NOT_ACCEPTED, false, false, true, false),
- // even though we don't need the executeAt for deps calculation, we do need it for recovery to decide membership of the earlierWait collection
- ACCEPTED (SummaryStatus.ACCEPTED, true, true, true, false),
- COMMITTED (SummaryStatus.COMMITTED, true, true, true, true),
+ PREACCEPTED_WITHOUT_DEPS (SummaryStatus.PREACCEPTED, false, false, true, false),
+ PREACCEPTED_WITH_DEPS (SummaryStatus.PREACCEPTED, false, true, true, false),
+ PRENOTACCEPTED_WITH_DEPS (SummaryStatus.PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE, false, true, true, false),
+ PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE (SummaryStatus.PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE, false, false, true, false),
+ NOTACCEPTED_WITH_COORDINATOR_DEPS (SummaryStatus.NOTACCEPTED, false, true, true, false),
+ NOTACCEPTED (SummaryStatus.NOTACCEPTED, false, false, true, false),
+ ACCEPTED (SummaryStatus.ACCEPTED, false, true, true, false),
+ COMMITTED (SummaryStatus.COMMITTED, true, true, true, true),
STABLE (SummaryStatus.STABLE, true, true, false, true),
// TODO (expected): do not encode missing collection for APPLIED transactions,
// as anything they should have witnessed can be treated as supersedingRejected
@@ -828,13 +837,31 @@ public class CommandsForKey extends CommandsForKeyUpdate
static
{
- FROM_SAVE_STATUS.put(SaveStatus.PreAccepted, PREACCEPTED_OR_ACCEPTED_INVALIDATE);
- FROM_SAVE_STATUS.put(SaveStatus.AcceptedInvalidateWithDefinition, PREACCEPTED_OR_ACCEPTED_INVALIDATE);
- FROM_SAVE_STATUS.put(SaveStatus.Accepted, ACCEPTED);
- FROM_SAVE_STATUS.put(SaveStatus.AcceptedWithDefinition, ACCEPTED);
- FROM_SAVE_STATUS.put(SaveStatus.PreCommittedWithDefinition, PREACCEPTED_OR_ACCEPTED_INVALIDATE);
- FROM_SAVE_STATUS.put(SaveStatus.PreCommittedWithAcceptedDeps, ACCEPTED);
- FROM_SAVE_STATUS.put(SaveStatus.PreCommittedWithDefinitionAndAcceptedDeps, ACCEPTED);
+ FROM_SAVE_STATUS.put(SaveStatus.PreAccepted, PREACCEPTED_WITHOUT_DEPS);
+ FROM_SAVE_STATUS.put(SaveStatus.PreAcceptedWithVote, PREACCEPTED_WITHOUT_DEPS);
+ FROM_SAVE_STATUS.put(SaveStatus.PreAcceptedWithDeps, PREACCEPTED_WITH_DEPS);
+ FROM_SAVE_STATUS.put(SaveStatus.AcceptedInvalidate, PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE);
+ FROM_SAVE_STATUS.put(SaveStatus.AcceptedInvalidateWithDefinition, PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE);
+ FROM_SAVE_STATUS.put(SaveStatus.PreNotAccepted, PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE);
+ FROM_SAVE_STATUS.put(SaveStatus.PreNotAcceptedWithDefinition, PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE);
+ FROM_SAVE_STATUS.put(SaveStatus.PreNotAcceptedWithDefAndVote, PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE);
+ FROM_SAVE_STATUS.put(SaveStatus.PreNotAcceptedWithDefAndDeps, PRENOTACCEPTED_WITH_DEPS);
+ FROM_SAVE_STATUS.put(SaveStatus.NotAccepted, PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE);
+ FROM_SAVE_STATUS.put(SaveStatus.NotAcceptedWithDefinition, PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE);
+ FROM_SAVE_STATUS.put(SaveStatus.NotAcceptedWithDefAndVote, PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE);
+ FROM_SAVE_STATUS.put(SaveStatus.NotAcceptedWithDefAndDeps, PRENOTACCEPTED_WITH_DEPS);
+ FROM_SAVE_STATUS.put(SaveStatus.AcceptedMedium, ACCEPTED);
+ FROM_SAVE_STATUS.put(SaveStatus.AcceptedMediumWithDefinition, ACCEPTED);
+ FROM_SAVE_STATUS.put(SaveStatus.AcceptedMediumWithDefAndVote, ACCEPTED);
+ FROM_SAVE_STATUS.put(SaveStatus.AcceptedSlow, ACCEPTED);
+ FROM_SAVE_STATUS.put(SaveStatus.AcceptedSlowWithDefinition, ACCEPTED);
+ FROM_SAVE_STATUS.put(SaveStatus.AcceptedSlowWithDefAndVote, ACCEPTED);
+ FROM_SAVE_STATUS.put(SaveStatus.PreCommitted, PREACCEPTED_WITHOUT_DEPS);
+ FROM_SAVE_STATUS.put(SaveStatus.PreCommittedWithDefinition, PREACCEPTED_WITHOUT_DEPS);
+ FROM_SAVE_STATUS.put(SaveStatus.PreCommittedWithDeps, ACCEPTED);
+ FROM_SAVE_STATUS.put(SaveStatus.PreCommittedWithFixedDeps, ACCEPTED);
+ FROM_SAVE_STATUS.put(SaveStatus.PreCommittedWithDefAndDeps, ACCEPTED);
+ FROM_SAVE_STATUS.put(SaveStatus.PreCommittedWithDefAndFixedDeps, ACCEPTED);
FROM_SAVE_STATUS.put(SaveStatus.Committed, COMMITTED);
FROM_SAVE_STATUS.put(SaveStatus.Stable, STABLE);
FROM_SAVE_STATUS.put(SaveStatus.ReadyToExecute, STABLE);
@@ -849,6 +876,8 @@ public class CommandsForKey extends CommandsForKeyUpdate
// superseding APPLIED command?
// TODO (expected): if we truncate (but don't invalidate) a command that had not been decided, we should probably erase it?
FROM_SAVE_STATUS.put(SaveStatus.Invalidated, INVALIDATED);
+ for (SaveStatus saveStatus : SaveStatus.values())
+ Invariants.checkState(FROM_SAVE_STATUS.get(saveStatus) != null || saveStatus.is(Status.Truncated) || saveStatus.is(Status.NotDefined));
SummaryStatus[] summaryStatuses = SummaryStatus.values();
TO_SUMMARY_STATUS = Arrays.copyOf(summaryStatuses, summaryStatuses.length + 1);
@@ -963,6 +992,7 @@ public class CommandsForKey extends CommandsForKeyUpdate
// a btree keyed by TxnId we have encountered since pruning that occur before prunedBefore;
// mapping to those TxnId that had witnessed this potentially-pruned TxnId.
+ // note that this may include TxnId < bootstrappedAt
final Object[] loadingPruned;
// this points to the primary prunedBefore APPLIED write, but note that if this CFK spans multiple epochs
// we retain the latest APPLIED write per epoch so that any dependencies we compute that might include
@@ -1229,7 +1259,7 @@ public class CommandsForKey extends CommandsForKeyUpdate
switch (testStartedAt)
{
- default: throw new AssertionError("Unhandled TestStartedAt: " + testStartedAt);
+ default: throw new UnhandledEnum(testStartedAt);
case STARTED_BEFORE: start = 0; end = insertPos; break;
case STARTED_AFTER: start = insertPos; end = byId.length; break;
case ANY: start = 0; end = byId.length;
@@ -1253,7 +1283,6 @@ public class CommandsForKey extends CommandsForKeyUpdate
}
else
{
-
boolean hasAsDep;
if (loadingFor == null)
{
@@ -1269,11 +1298,21 @@ public class CommandsForKey extends CommandsForKeyUpdate
// we could use expontentialSearch and moving index for improved algorithmic complexity,
// but since should be rarely taken path probably not worth code complexity
loadingIndex = SortedArrays.exponentialSearch(loadingFor, loadingIndex, loadingFor.length, txn);
- if (hasAsDep = (loadingIndex >= 0)) ++loadingIndex;
- else loadingIndex = -1 - loadingIndex;
+ if (loadingIndex >= 0)
+ {
+ hasAsDep = !loadingFor[loadingIndex].is(UNSTABLE);
+ ++loadingIndex;
+ }
+ else
+ {
+ hasAsDep = false;
+ loadingIndex = -1 - loadingIndex;
+ }
}
- dep = hasAsDep ? IS_DEP : IS_NOT_DEP;
+ if (txn.compareTo(ACCEPTED) >= 0) dep = hasAsDep ? IS_STABLE_DEP : IS_NOT_STABLE_DEP;
+ else if (txn.is(PRIVILEGED_COORDINATOR_WITH_DEPS)) dep = hasAsDep ? IS_COORD_DEP : IS_NOT_COORD_DEP;
+ else dep = NOT_ELIGIBLE;
}
}
@@ -1419,8 +1458,8 @@ public class CommandsForKey extends CommandsForKeyUpdate
public CommandsForKeyUpdate callback(Command update)
{
- if (wasPruned(update))
- return prunedCallback(update);
+ if (maybePruned(update))
+ return maybePrunedCallback(update);
return update(update);
}
@@ -1445,25 +1484,25 @@ public class CommandsForKey extends CommandsForKeyUpdate
return new CommandsForKey(key, boundsInfo, byId, committedByExecuteAt, minUndecidedById, maxAppliedWriteByExecuteAt, maxHlc, loadingPruned, prunedBeforeById, unmanageds);
}
- CommandsForKeyUpdate prunedCallback(Command update)
+ CommandsForKeyUpdate maybePrunedCallback(Command update)
{
TxnId txnId = update.txnId();
- InternalStatus newStatus = prunedFrom(update.saveStatus());
+ InternalStatus ifPrunedStatus = prunedFrom(update.saveStatus());
boolean isLoadingPruned = loadingPrunedFor(loadingPruned, txnId, null) != null;
if (!isLoadingPruned)
{
- if (!manages(txnId))
- newStatus = PRUNED;
-
TxnInfo cur = get(txnId);
- if (newStatus == PRUNED && (cur == null || cur.is(PRUNED)))
- return this;
+ if (cur != null && !cur.is(PRUNED))
+ return update(update); // if we're not pruned, this should be treated as a regular callback
+
+ if (!manages(txnId))
+ ifPrunedStatus = PRUNED;
}
- return update(newStatus, update);
+ return update(ifPrunedStatus, update);
}
- private boolean wasPruned(Command command)
+ private boolean maybePruned(Command command)
{
TxnId txnId = command.txnId();
TxnInfo prunedBefore = prunedBefore();
@@ -1471,7 +1510,7 @@ public class CommandsForKey extends CommandsForKeyUpdate
return false;
if (!manages(txnId))
return true;
- return command.hasBeen(Status.Truncated) || (command.hasBeen(Status.Committed) && prunedBefore.compareTo(command.executeAt()) > 0);
+ return command.hasBeen(Status.Truncated) || (command.hasBeen(Status.Applied) && prunedBefore.executeAt.compareTo(command.executeAt()) > 0);
}
private CommandsForKeyUpdate update(InternalStatus newStatus, Command updated)
@@ -1483,8 +1522,11 @@ public class CommandsForKey extends CommandsForKeyUpdate
return this;
boolean mayExecute = mayExecute(txnId, newStatus, updated);
- boolean isOutOfRange = !mayExecute && newStatus.compareTo(COMMITTED) >= 0 && newStatus.compareTo(APPLIED) <= 0
- && manages(txnId) && !updated.participants().stillTouches(key);
+ boolean isOutOfRange = !mayExecute && newStatus.compareTo(APPLIED) <= 0 && manages(txnId)
+ && !updated.participants().stillTouches(key);
+
+ if (isOutOfRange && newStatus.compareTo(COMMITTED) < 0)
+ return this;
TxnId[] loadingAsPrunedFor = loadingPrunedFor(loadingPruned, txnId, null); // we default to null to distinguish between no match, and a match with NO_TXNIDS
boolean wasPruned = loadingAsPrunedFor != null;
@@ -1511,7 +1553,7 @@ public class CommandsForKey extends CommandsForKeyUpdate
if (c > 0)
{
// newStatus moves us backwards; we only permit this for (Pre)?(Not)?Accepted states
- if (cur.compareTo(COMMITTED) >= 0 || newStatus.compareTo(PREACCEPTED_OR_ACCEPTED_INVALIDATE) < 0)
+ if (cur.compareTo(COMMITTED) >= 0 || newStatus.compareTo(PREACCEPTED) <= 0)
return this;
// and only when the new ballot is strictly greater
@@ -1764,7 +1806,7 @@ public class CommandsForKey extends CommandsForKeyUpdate
Kind kind = txn.kind();
if (txn.mayExecute() && !txn.hasNotifiedReady())
{
- if (i >= mayNotExecuteBeforeIndex && (kinds.test(kind) || i == mayExecuteAny) && !isWaitingOnPruned(loadingPruned, txn, txn.executeAt))
+ if (i >= mayNotExecuteBeforeIndex && (kinds.test(kind) || i == mayExecuteAny) && !isWaitingOnPruned(loadingPruned, txn, txn.executeAt, boundsInfo))
{
switch (txn.status())
{
@@ -1816,7 +1858,7 @@ public class CommandsForKey extends CommandsForKeyUpdate
}
for (int j = missingFrom ; j < missing.length ; ++j)
{
- if (!managesExecution(missing[j]))
+ if (!managesExecution(missing[j]) || missing[j].is(UNSTABLE))
--missingCount;
}
}
@@ -1849,7 +1891,7 @@ public class CommandsForKey extends CommandsForKeyUpdate
{
switch (kind)
{
- default: throw new AssertionError("Unhandled Txn.Kind: " + kind);
+ default: throw new UnhandledEnum(kind);
case EphemeralRead:
throw illegalState("Invalid Txn.Kind for CommandsForKey: " + kind);
@@ -1869,7 +1911,7 @@ public class CommandsForKey extends CommandsForKeyUpdate
{
switch (kind)
{
- default: throw new AssertionError("Unhandled Txn.Kind: " + kind);
+ default: throw new UnhandledEnum(kind);
case EphemeralRead:
case ExclusiveSyncPoint:
throw illegalState("Invalid Txn.Kind for CommandsForKey: " + kind);
@@ -2140,7 +2182,7 @@ public class CommandsForKey extends CommandsForKeyUpdate
for (TxnId missingId : txn.missing())
{
- Invariants.checkState(txn.kind().witnesses(missingId));
+ Invariants.checkState(txn.witnesses(missingId));
TxnInfo missingInfo = get(missingId, byId);
Invariants.checkState(missingInfo.status().compareTo(InternalStatus.COMMITTED) < 0);
Invariants.checkState(txn.depsKnownBefore().compareTo(missingId) >= 0);
diff --git a/accord-core/src/main/java/accord/local/cfk/NotifySink.java b/accord-core/src/main/java/accord/local/cfk/NotifySink.java
index 8dfa1e39..35090c6e 100644
--- a/accord-core/src/main/java/accord/local/cfk/NotifySink.java
+++ b/accord-core/src/main/java/accord/local/cfk/NotifySink.java
@@ -85,18 +85,19 @@ interface NotifySink
StoreParticipants participants = command.participants();
if (!participants.hasTouched(key))
{
+ if (!safeStore.ranges().allSince(command.txnId().epoch()).contains(key))
+ {
+ // we raced with new topology information letting us know we don't own the epoch
+ Invariants.checkState(safeStore.ranges().allBefore(command.txnId().epoch()).contains(key));
+ return;
+ }
+
Invariants.checkState(txnId.is(Routable.Domain.Key));
// make sure we will notify the CommandsForKey that's waiting
safeCommand.updateParticipants(safeStore, command.participants().supplementHasTouched(RoutingKeys.of(key)));
}
if (command.saveStatus().compareTo(waitingOnStatus) >= 0)
{
- // TODO (required): we expect this invariant to fail periodically today due to how we handle losing ranges
- // (specifically at minimum in the case where a CommandsForKey is provided a later transaction as a dependency
- // for an earlier transaction in order to permit CFK to fill in that dependency history).
- // This should be revisited at the same time as we resolve epoch changes with Recovery, as we expect
- // to have earlier epochs continue to maintain recovery state until the new epoch is fully ready
- // as this simplifies recovery and makes it more deterministic (avoiding epoch chasing).
Invariants.checkState(command.saveStatus().hasBeen(Truncated) || command.participants().touches(key));
// if we're committed but not invalidated, that means EITHER we have raced with a commit+
// OR we adopted as a dependency a <...?>
diff --git a/accord-core/src/main/java/accord/local/cfk/PostProcess.java b/accord-core/src/main/java/accord/local/cfk/PostProcess.java
index a67b6a1f..2fc85a07 100644
--- a/accord-core/src/main/java/accord/local/cfk/PostProcess.java
+++ b/accord-core/src/main/java/accord/local/cfk/PostProcess.java
@@ -268,7 +268,13 @@ abstract class PostProcess
{
// notify commit uses exclusive bounds, as we use minUndecided
Timestamp minUndecided = minUndecidedById < 0 ? Timestamp.MAX : byId[minUndecidedById];
- if (!BTree.isEmpty(loadingPruned)) minUndecided = Timestamp.min(minUndecided, BTree.<Pruning.LoadingPruned>findByIndex(loadingPruned, 0));
+ if (!BTree.isEmpty(loadingPruned))
+ {
+ TxnId minLoadingPruned = bootstrappedAt == null ? BTree.findByIndex(loadingPruned, 0)
+ : BTree.ceil(loadingPruned, TxnId::compareTo, bootstrappedAt);
+
+ minUndecided = Timestamp.nonNullOrMin(minUndecided, minLoadingPruned);
+ }
int end = findCommit(unmanageds, minUndecided);
if (end > 0)
{
diff --git a/accord-core/src/main/java/accord/local/cfk/Pruning.java b/accord-core/src/main/java/accord/local/cfk/Pruning.java
index af9cfffe..006bd6a5 100644
--- a/accord-core/src/main/java/accord/local/cfk/Pruning.java
+++ b/accord-core/src/main/java/accord/local/cfk/Pruning.java
@@ -44,6 +44,7 @@ import static accord.local.cfk.CommandsForKey.mayExecute;
import static accord.local.cfk.CommandsForKey.redundantBefore;
import static accord.local.cfk.Updating.nextUndecided;
import static accord.local.cfk.Utils.removeRedundantMissing;
+import static accord.primitives.Timestamp.Flag.UNSTABLE;
import static accord.primitives.Txn.Kind.Write;
import static accord.primitives.TxnId.NO_TXNIDS;
import static accord.utils.ArrayBuffers.cachedAny;
@@ -69,8 +70,9 @@ public class Pruning
{
static class Merge implements UpdateFunction<TxnId, LoadingPruned>
{
- final TxnId[] witnessedBy;
- final List<TxnId> inserted;
+ private final TxnId[] witnessedBy;
+ private TxnId[] witnessedByUnstable;
+ private final List<TxnId> inserted;
Merge(TxnId[] witnessedBy, List<TxnId> inserted)
{
@@ -82,6 +84,15 @@ public class Pruning
@Override
public LoadingPruned insert(TxnId insert)
{
+ TxnId[] witnessedBy = this.witnessedBy;
+ if (insert.is(UNSTABLE))
+ {
+ insert = insert.withoutNonIdentityFlags();
+ // TODO (desired): this is a bit ugly - maybe we should save all prunedIds in byId by default?
+ if (witnessedByUnstable == null)
+ witnessedByUnstable = new TxnId[] { witnessedBy[0].addFlag(UNSTABLE) };
+ witnessedBy = witnessedByUnstable;
+ }
inserted.add(insert);
return new LoadingPruned(insert, witnessedBy);
}
@@ -152,14 +163,15 @@ public class Pruning
/**
* Return true if {@code waitingId} is waiting for any transaction with a lower TxnId than waitingExecuteAt
*/
- static boolean isWaitingOnPruned(Object[] loadingPruned, TxnId waitingId, Timestamp waitingExecuteAt)
+ static boolean isWaitingOnPruned(Object[] loadingPruned, TxnId waitingId, Timestamp waitingExecuteAt, RedundantBefore.Entry boundsInfo)
{
if (BTree.isEmpty(loadingPruned))
return false;
- int ceilIndex = BTree.ceilIndex(loadingPruned, Timestamp::compareTo, waitingExecuteAt);
+ int startIndex = BTree.ceilIndex(loadingPruned, Timestamp::compareTo, boundsInfo.bootstrappedAt);
+ int endIndex = BTree.ceilIndex(loadingPruned, Timestamp::compareTo, waitingExecuteAt);
// TODO (desired): this is O(n.lg n), whereas we could import the accumulate function and perform in O(max(m, lg n))
- for (int i = 0; i < ceilIndex; ++i)
+ for (int i = startIndex; i < endIndex; ++i)
{
LoadingPruned loading = BTree.findByIndex(loadingPruned, i);
if (!managesExecution(loading)) continue;
@@ -327,7 +339,12 @@ public class Pruning
case TRANSITIVE:
case TRANSITIVE_VISIBLE:
- case PREACCEPTED_OR_ACCEPTED_INVALIDATE:
+ case PREACCEPTED_WITHOUT_DEPS:
+ case PREACCEPTED_WITH_DEPS:
+ case PRENOTACCEPTED_OR_ACCEPTED_INVALIDATE:
+ case PRENOTACCEPTED_WITH_DEPS:
+ case NOTACCEPTED:
+ case NOTACCEPTED_WITH_COORDINATOR_DEPS:
case ACCEPTED:
newByIdBuffer[pos - ++retainCount] = txn;
if (i == minUndecidedById)
diff --git a/accord-core/src/main/java/accord/local/cfk/SafeCommandsForKey.java b/accord-core/src/main/java/accord/local/cfk/SafeCommandsForKey.java
index 7839f3ec..d0914d47 100644
--- a/accord-core/src/main/java/accord/local/cfk/SafeCommandsForKey.java
+++ b/accord-core/src/main/java/accord/local/cfk/SafeCommandsForKey.java
@@ -49,7 +49,7 @@ public abstract class SafeCommandsForKey implements SafeState<CommandsForKey>
void updatePruned(SafeCommandStore safeStore, Command nextCommand, NotifySink notifySink)
{
CommandsForKey prevCfk = current();
- update(safeStore, nextCommand, prevCfk, prevCfk.prunedCallback(nextCommand), notifySink);
+ update(safeStore, nextCommand, prevCfk, prevCfk.maybePrunedCallback(nextCommand), notifySink);
}
public void update(SafeCommandStore safeStore, Command nextCommand)
diff --git a/accord-core/src/main/java/accord/local/cfk/Serialize.java b/accord-core/src/main/java/accord/local/cfk/Serialize.java
index be31d696..9e210bed 100644
--- a/accord-core/src/main/java/accord/local/cfk/Serialize.java
+++ b/accord-core/src/main/java/accord/local/cfk/Serialize.java
@@ -39,11 +39,13 @@ import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.utils.BitUtils;
import accord.utils.Invariants;
+import accord.utils.UnhandledEnum;
import accord.utils.VIntCoding;
import static accord.local.cfk.CommandsForKey.NO_BOUNDS_INFO;
import static accord.local.cfk.CommandsForKey.NO_PENDING_UNMANAGED;
import static accord.primitives.Txn.Kind.ExclusiveSyncPoint;
+import static accord.primitives.TxnId.MediumPath.MEDIUM_PATH_TRACK_STABLE;
import static accord.primitives.TxnId.NO_TXNIDS;
import static accord.primitives.Txn.Kind.Read;
import static accord.primitives.Txn.Kind.SyncPoint;
@@ -837,7 +839,8 @@ public class Serialize
if (readEpochBytes > 0)
epoch += readEpochBytes == 1 ? (in.get() & 0xff) : in.getInt();
- txnIds[i] = kind != null ? new TxnId(epoch, hlc, kind, domain, node)
+ // TODO (required): efficiently encode medium path / coordinator optimisation Flag
+ txnIds[i] = kind != null ? new TxnId(epoch, hlc, 0, kind, domain, node)
: TxnId.fromValues(epoch, hlc, flags, node);
prevEpoch = epoch;
@@ -1050,7 +1053,7 @@ public class Serialize
return (bootstrappedAt != null && bootstrappedAt.flags() != RX_FLAGS) || (redundantBefore.flags() != RX_FLAGS);
}
- private static final int RX_FLAGS = new TxnId(0, 0, ExclusiveSyncPoint, Domain.Range, Node.Id.NONE).flags();
+ private static final int RX_FLAGS = new TxnId(0, 0, MEDIUM_PATH_TRACK_STABLE.bits, ExclusiveSyncPoint, Domain.Range, Node.Id.NONE).flags();
private static boolean hasMaxHlc(CommandsForKey cfk)
{
@@ -1106,6 +1109,7 @@ public class Serialize
*/
private static int txnIdFlagsBits(TxnId txnId, boolean extended)
{
+ // TODO (required): we expect to almost always have more flags than this, so optimise handling
if (txnId.flags() > Timestamp.KIND_AND_DOMAIN_FLAGS)
return 0;
@@ -1131,7 +1135,7 @@ public class Serialize
case ExclusiveSyncPoint: return 5;
default: return 0;
}
- default: throw new AssertionError("Unhandled Domain: " + domain);
+ default: throw new UnhandledEnum(domain);
}
}
diff --git a/accord-core/src/main/java/accord/local/cfk/Updating.java b/accord-core/src/main/java/accord/local/cfk/Updating.java
index a1438603..90396169 100644
--- a/accord-core/src/main/java/accord/local/cfk/Updating.java
+++ b/accord-core/src/main/java/accord/local/cfk/Updating.java
@@ -37,6 +37,7 @@ import accord.local.RedundantBefore;
import accord.local.SafeCommand;
import accord.local.cfk.CommandsForKey.InternalStatus;
import accord.local.cfk.PostProcess.LoadPruned;
+import accord.primitives.Deps.DepList;
import accord.primitives.Status;
import accord.local.cfk.CommandsForKey.TxnInfo;
import accord.primitives.Ballot;
@@ -47,7 +48,7 @@ import accord.primitives.TxnId;
import accord.utils.Invariants;
import accord.utils.RelationMultiMap;
import accord.utils.SortedArrays;
-import accord.utils.SortedCursor;
+import accord.utils.SortedList.MergeCursor;
import static accord.local.CommandSummaries.SummaryStatus.APPLIED;
import static accord.local.KeyHistory.SYNC;
@@ -70,6 +71,7 @@ import static accord.local.cfk.Utils.removePrunedAdditions;
import static accord.local.cfk.Utils.removeUnmanaged;
import static accord.local.cfk.Utils.validateMissing;
import static accord.primitives.Routable.Domain.Range;
+import static accord.primitives.Timestamp.Flag.UNSTABLE;
import static accord.primitives.Txn.Kind.EphemeralRead;
import static accord.primitives.Txn.Kind.ExclusiveSyncPoint;
import static accord.primitives.Txn.Kind.Write;
@@ -205,7 +207,7 @@ class Updating
ballot = command.acceptedOrCommitted();
Timestamp depsKnownBefore = newStatus.depsKnownBefore(txnId, executeAt);
- SortedCursor<TxnId> deps = command.partialDeps().txnIds(cfk.key());
+ MergeCursor<TxnId, DepList> deps = command.partialDeps().txnIds(cfk.key());
deps.find(cfk.redundantBefore());
return computeInfoAndAdditions(cfk.byId, insertPos, updatePos, txnId, newStatus, mayExecute, ballot, executeAt, depsKnownBefore, deps);
@@ -215,7 +217,7 @@ class Updating
* We return an Object here to avoid wasting allocations; most of the time we expect a new TxnInfo to be returned,
* but if we have transitive dependencies to insert we return an InfoWithAdditions
*/
- static Object computeInfoAndAdditions(TxnInfo[] byId, int insertPos, int updatePos, TxnId plainTxnId, InternalStatus newStatus, boolean mayExecute, Ballot ballot, Timestamp executeAt, Timestamp depsKnownBefore, SortedCursor<TxnId> deps)
+ static Object computeInfoAndAdditions(TxnInfo[] byId, int insertPos, int updatePos, TxnId plainTxnId, InternalStatus newStatus, boolean mayExecute, Ballot ballot, Timestamp executeAt, Timestamp depsKnownBefore, MergeCursor<TxnId, DepList> deps)
{
TxnId[] additions = NO_TXNIDS, missing = NO_TXNIDS;
int additionCount = 0, missingCount = 0;
@@ -239,6 +241,13 @@ class Updating
int c = t.compareTo(d);
if (c == 0)
{
+ if (d.is(UNSTABLE) && t.compareTo(COMMITTED) < 0 && t.witnesses(d))
+ {
+ if (missingCount == missing.length)
+ missing = cachedTxnIds().resize(missing, missingCount, Math.max(8, missingCount * 2));
+ missing[missingCount++] = d;
+ }
+
++txnIdsIndex;
deps.advance();
}
@@ -246,7 +255,7 @@ class Updating
{
// we expect to be missing ourselves
// we also permit any transaction we have recorded as COMMITTED or later to be missing, as recovery will not need to consult our information
- if (txnIdsIndex != updatePos && txnIdsIndex < depsKnownBeforePos && t.compareTo(COMMITTED) < 0 && plainTxnId.kind().witnesses(t))
+ if (txnIdsIndex != updatePos && txnIdsIndex < depsKnownBeforePos && t.compareTo(COMMITTED) < 0 && plainTxnId.witnesses(t))
{
if (missingCount == missing.length)
missing = cachedTxnIds().resize(missing, missingCount, Math.max(8, missingCount * 2));
@@ -256,7 +265,7 @@ class Updating
}
else
{
- if (plainTxnId.kind().witnesses(d))
+ if (plainTxnId.witnesses(d))
{
if (additionCount >= additions.length)
additions = cachedTxnIds().resize(additions, additionCount, Math.max(8, additionCount * 2));
@@ -278,9 +287,13 @@ class Updating
{
do
{
- if (additionCount >= additions.length)
- additions = cachedTxnIds().resize(additions, additionCount, Math.max(8, additionCount * 2));
- additions[additionCount++] = deps.cur();
+ TxnId d = deps.cur();
+ if (plainTxnId.witnesses(d))
+ {
+ if (additionCount >= additions.length)
+ additions = cachedTxnIds().resize(additions, additionCount, Math.max(8, additionCount * 2));
+ additions[additionCount++] = deps.cur().withoutNonIdentityFlags();
+ }
deps.advance();
}
while (deps.hasCur());
@@ -292,7 +305,7 @@ class Updating
if (txnIdsIndex != updatePos && byId[txnIdsIndex].compareTo(COMMITTED) < 0)
{
TxnId txnId = byId[txnIdsIndex].plainTxnId();
- if ((plainTxnId.kind().witnesses(txnId)))
+ if ((plainTxnId.witnesses(txnId)))
{
if (missingCount == missing.length)
missing = cachedTxnIds().resize(missing, missingCount, Math.max(8, missingCount * 2));
@@ -635,7 +648,7 @@ class Updating
{
for (int i = pos; i <= maxAppliedWriteByExecuteAt; ++i)
{
- if (committedByExecuteAt[pos].kind().witnesses(newInfo) && reportLinearizabilityViolations())
+ if (committedByExecuteAt[pos].witnesses(newInfo) && reportLinearizabilityViolations())
logger.error("Linearizability violation on key {}: {} is committed to execute (at {}) before {} that should witness it but has already applied (at {})", cfk.key, newInfo.plainTxnId(), newInfo.plainExecuteAt(), committedByExecuteAt[i].plainTxnId(), committedByExecuteAt[i].plainExecuteAt());
}
}
@@ -807,7 +820,7 @@ class Updating
// recovery decisions about transactions we do own
// We make sure to compute dependencies that satisfy all participating epochs for RX/KX
int toIndex = txnIds.size();
- while (toIndex > waitingFromIndex && txnIds.get(toIndex - 1).epoch() >= cfk.boundsInfo.endOwnershipEpoch)
+ while (toIndex > 0 && txnIds.get(toIndex - 1).epoch() >= cfk.boundsInfo.endOwnershipEpoch)
--toIndex;
if (i < toIndex)
@@ -855,7 +868,7 @@ class Updating
if (c >= 0)
{
TxnInfo txn = byId[j];
- if (c == 0 || txn.is(waitingTxnId.witnesses()))
+ if (c == 0 || txn.witnessedBy(waitingTxnId))
{
if (i >= waitingFromIndex && waitingToApply)
{
@@ -919,7 +932,7 @@ class Updating
{
TxnInfo txn = byId[w];
if (!txn.mayExecute()
- || !txn.is(waitingTxnId.witnesses())
+ || !txn.witnessedBy(waitingTxnId)
|| txn.isAtLeast(APPLIED))
continue;
diff --git a/accord-core/src/main/java/accord/local/cfk/Utils.java b/accord-core/src/main/java/accord/local/cfk/Utils.java
index c9839a5e..6a18a4f8 100644
--- a/accord-core/src/main/java/accord/local/cfk/Utils.java
+++ b/accord-core/src/main/java/accord/local/cfk/Utils.java
@@ -52,12 +52,12 @@ class Utils
int j = 0;
for (int i = 0 ; i < additionIndex ; ++i)
{
- if (!txn.kind().witnesses(additions[i])) continue;
+ if (!txn.witnesses(additions[i])) continue;
j = SortedArrays.exponentialSearch(missing, j, missing.length, additions[i]);
if (shouldNotHaveMissing != NO_TXNIDS && Arrays.binarySearch(shouldNotHaveMissing, txn) >= 0) Invariants.checkState(j < 0);
else Invariants.checkState(j >= 0);
}
- if (curInfo == null && newInfo.compareTo(COMMITTED) < 0 && txn.kind().witnesses(newInfo) && txn.depsKnownBefore().compareTo(newInfo) > 0 && (shouldNotHaveMissing == NO_TXNIDS || Arrays.binarySearch(shouldNotHaveMissing, txn) < 0))
+ if (curInfo == null && newInfo.compareTo(COMMITTED) < 0 && txn.witnesses(newInfo) && txn.depsKnownBefore().compareTo(newInfo) > 0 && (shouldNotHaveMissing == NO_TXNIDS || Arrays.binarySearch(shouldNotHaveMissing, txn) < 0))
Invariants.checkState(Arrays.binarySearch(missing, newInfo) >= 0);
}
}
@@ -80,7 +80,7 @@ class Utils
{
TxnInfo txn = committedByExecuteAt[i];
if (txn.getClass() == TxnInfo.class) continue;
- if (!txn.kind().witnesses(removeTxnId)) continue;
+ if (!txn.witnesses(removeTxnId)) continue;
TxnId[] missing = txn.missing();
TxnId[] newMissing = removeOneMissing(missing, removeTxnId);
@@ -107,8 +107,8 @@ class Utils
TxnInfo txn = byId[i];
if (txn.getClass() == TxnInfo.class) continue;
if (!txn.hasDeps()) continue;
- if (!txn.kind().witnesses(removeTxnId)) continue;
- if (txn.isNot(ACCEPTED) && txn.mayExecute()) continue;
+ if (!txn.witnesses(removeTxnId)) continue;
+ if (txn.compareTo(ACCEPTED) > 0 && txn.mayExecute()) continue;
TxnId[] missing = txn.missing();
TxnId[] newMissing = removeOneMissing(missing, removeTxnId);
@@ -139,7 +139,7 @@ class Utils
if (txn.mayExecute()) continue;
if (!txn.hasDeps()) continue;
if (txn.executeAt == txn) continue;
- if (!txn.kind().witnesses(insertTxnId)) continue;
+ if (!txn.witnesses(insertTxnId)) continue;
if (txn.depsKnownBefore().compareTo(newInfo) < 0) continue;
TxnId[] missing = txn.missing();
@@ -155,7 +155,7 @@ class Utils
{
TxnInfo txn = committedByExecuteAt[i];
if (txn == newInfo) continue;
- if (!txn.kind().witnesses(insertTxnId)) continue;
+ if (!txn.witnesses(insertTxnId)) continue;
if (doNotInsert != NO_TXNIDS)
{
@@ -188,8 +188,8 @@ class Utils
TxnInfo txn = byId[minByIdSearchIndex];
if (txn == newInfo) continue;
if (!txn.hasDeps()) continue;
- if (!txn.kind().witnesses(insertTxnId)) continue;
- if (txn.isNot(ACCEPTED) && txn.mayExecute()) continue;
+ if (!txn.witnesses(insertTxnId)) continue;
+ if (txn.compareTo(ACCEPTED) > 0 && txn.mayExecute()) continue;
if (minDoNotInsertSearchIndex < doNotInsert.length && doNotInsert[minDoNotInsertSearchIndex].equals(txn))
{
++minDoNotInsertSearchIndex;
@@ -209,8 +209,8 @@ class Utils
if (txn == newInfo) continue;
// TODO (expected): optimise this with flag bits
if (!txn.hasDeps()) continue;
- if (!txn.kind().witnesses(insertTxnId)) continue;
- if (txn.isNot(ACCEPTED) && txn.mayExecute()) continue;
+ if (!txn.witnesses(insertTxnId)) continue;
+ if (txn.compareTo(ACCEPTED) > 0 && txn.mayExecute()) continue;
if (minDoNotInsertSearchIndex < doNotInsert.length && doNotInsert[minDoNotInsertSearchIndex].equals(txn))
{
++minDoNotInsertSearchIndex;
diff --git a/accord-core/src/main/java/accord/messages/Accept.java b/accord-core/src/main/java/accord/messages/Accept.java
index 14ddbed9..a4dcab3c 100644
--- a/accord-core/src/main/java/accord/messages/Accept.java
+++ b/accord-core/src/main/java/accord/messages/Accept.java
@@ -21,7 +21,6 @@ package accord.messages;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import accord.api.RoutingKey;
import accord.local.Command;
import accord.local.Commands;
import accord.local.Commands.AcceptOutcome;
@@ -45,9 +44,13 @@ import accord.topology.Topologies;
import accord.utils.Invariants;
import accord.utils.async.Cancellable;
+import static accord.api.ProtocolModifiers.Toggles.filterDuplicateDependenciesFromAcceptReply;
import static accord.local.Commands.AcceptOutcome.Redundant;
import static accord.local.Commands.AcceptOutcome.RejectedBallot;
import static accord.local.Commands.AcceptOutcome.Success;
+import static accord.local.KeyHistory.ASYNC;
+import static accord.local.KeyHistory.SYNC;
+import static accord.messages.Accept.Kind.MEDIUM;
// TODO (low priority, efficiency): use different objects for send and receive, so can be more efficient
// (e.g. serialize without slicing, and without unnecessary fields)
@@ -55,27 +58,32 @@ public class Accept extends TxnRequest.WithUnsynced<Accept.AcceptReply>
{
public static class SerializerSupport
{
- public static Accept create(TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch, Ballot ballot, Timestamp executeAt, PartialDeps partialDeps)
+ public static Accept create(TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch, Kind kind, Ballot ballot, Timestamp executeAt, PartialDeps partialDeps)
{
- return new Accept(txnId, scope, waitForEpoch, minEpoch, ballot, executeAt, partialDeps);
+ return new Accept(txnId, scope, waitForEpoch, minEpoch, kind, ballot, executeAt, partialDeps);
}
}
+ public enum Kind { SLOW, MEDIUM }
+
+ public final Kind kind;
public final Ballot ballot;
public final Timestamp executeAt;
public final PartialDeps partialDeps;
- public Accept(Id to, Topologies topologies, Ballot ballot, TxnId txnId, FullRoute<?> route, Timestamp executeAt, Deps deps)
+ public Accept(Id to, Topologies topologies, Kind kind, Ballot ballot, TxnId txnId, FullRoute<?> route, Timestamp executeAt, Deps deps)
{
super(to, topologies, txnId, route);
+ this.kind = kind;
this.ballot = ballot;
this.executeAt = executeAt;
this.partialDeps = deps.intersecting(scope);
}
- private Accept(TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch, Ballot ballot, Timestamp executeAt, PartialDeps partialDeps)
+ private Accept(TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch, Kind kind, Ballot ballot, Timestamp executeAt, PartialDeps partialDeps)
{
super(txnId, scope, waitForEpoch, minEpoch);
+ this.kind = kind;
this.ballot = ballot;
this.executeAt = executeAt;
this.partialDeps = partialDeps;
@@ -86,7 +94,7 @@ public class Accept extends TxnRequest.WithUnsynced<Accept.AcceptReply>
{
StoreParticipants participants = StoreParticipants.update(safeStore, scope, minEpoch, txnId, txnId.epoch(), executeAt.epoch());
SafeCommand safeCommand = safeStore.get(txnId, participants);
- switch (Commands.accept(safeStore, safeCommand, participants, txnId, ballot, scope, executeAt, partialDeps))
+ switch (Commands.accept(safeStore, safeCommand, participants, txnId, kind, ballot, scope, executeAt, partialDeps))
{
default: throw new IllegalStateException();
case Redundant:
@@ -94,16 +102,20 @@ public class Accept extends TxnRequest.WithUnsynced<Accept.AcceptReply>
return AcceptReply.redundant(ballot, participants, safeCommand.current());
case RejectedBallot:
return new AcceptReply(safeCommand.current().promised());
- // if owns is empty, we're just fetching deps
- // TODO (desired): optimise deps calculation; for some keys we only need to return the last RX
case Retired:
+ // if we're Retired, participants.owns() is empty, so we're just fetching deps
+ // TODO (desired): optimise deps calculation; for some keys we only need to return the last RX
case Success:
- // TODO (desired, efficiency): only return delta of sent and calculated deps
+ if (kind == MEDIUM)
+ return new AcceptReply(Deps.NONE);
+
Deps deps = calculateDeps(safeStore, participants);
if (deps == null)
return AcceptReply.redundant(ballot, participants, safeCommand.current());
Invariants.checkState(deps.maxTxnId(txnId).epoch() <= executeAt.epoch());
+ if (filterDuplicateDependenciesFromAcceptReply())
+ deps = deps.without(this.partialDeps);
return new AcceptReply(deps);
}
}
@@ -114,15 +126,9 @@ public class Accept extends TxnRequest.WithUnsynced<Accept.AcceptReply>
}
@Override
- public AcceptReply reduce(AcceptReply ok1, AcceptReply ok2)
+ public AcceptReply reduce(AcceptReply r1, AcceptReply r2)
{
- if (!ok1.isOk() || !ok2.isOk())
- return ok1.outcome().compareTo(ok2.outcome()) >= 0 ? ok1 : ok2;
-
- Deps deps = ok1.deps.with(ok2.deps);
- if (deps == ok1.deps) return ok1;
- if (deps == ok2.deps) return ok2;
- return new AcceptReply(deps);
+ return AcceptReply.reduce(r1, r2);
}
@Override
@@ -146,7 +152,7 @@ public class Accept extends TxnRequest.WithUnsynced<Accept.AcceptReply>
@Override
public KeyHistory keyHistory()
{
- return KeyHistory.SYNC;
+ return kind == MEDIUM ? ASYNC : SYNC;
}
@Override
@@ -157,7 +163,8 @@ public class Accept extends TxnRequest.WithUnsynced<Accept.AcceptReply>
public String toString() {
return "Accept{" +
- "ballot: " + ballot +
+ "kind: " + kind +
+ ", ballot: " + ballot +
", txnId: " + txnId +
", executeAt: " + executeAt +
", deps: " + partialDeps +
@@ -166,7 +173,7 @@ public class Accept extends TxnRequest.WithUnsynced<Accept.AcceptReply>
public static final class AcceptReply implements Reply
{
- public static final AcceptReply ACCEPT_INVALIDATE = new AcceptReply(Success);
+ public static final AcceptReply SUCCESS = new AcceptReply(Success);
public final AcceptOutcome outcome;
public final Ballot supersededBy;
@@ -217,6 +224,17 @@ public class Accept extends TxnRequest.WithUnsynced<Accept.AcceptReply>
return new AcceptReply(superseding, command.executeAtIfKnown());
}
+ public static AcceptReply reduce(AcceptReply r1, AcceptReply r2)
+ {
+ if (!r1.isOk() || !r2.isOk())
+ return r1.outcome().compareTo(r2.outcome()) >= 0 ? r1 : r2;
+
+ Deps deps = r1.deps == null ? r2.deps : r2.deps == null ? r1.deps : r1.deps.with(r2.deps);
+ if (deps == r1.deps) return r1;
+ if (deps == r2.deps) return r2;
+ return new AcceptReply(deps);
+ }
+
@Override
public MessageType type()
{
@@ -249,31 +267,32 @@ public class Accept extends TxnRequest.WithUnsynced<Accept.AcceptReply>
}
}
- public static class Invalidate extends AbstractRequest<AcceptReply>
+ public static class NotAccept extends AbstractRequest<AcceptReply>
{
+ public final Status status;
public final Ballot ballot;
- // should not be a non-participating home key
- public final RoutingKey someKey;
+ public final Participants<?> participants;
- public Invalidate(Ballot ballot, TxnId txnId, RoutingKey someKey)
+ public NotAccept(Status status, Ballot ballot, TxnId txnId, Participants<?> participants)
{
super(txnId);
+ this.status = status;
this.ballot = ballot;
- this.someKey = someKey;
+ this.participants = participants;
}
@Override
public Cancellable submit()
{
- return node.mapReduceConsumeLocal(this, someKey, txnId.epoch(), this);
+ return node.mapReduceConsumeLocal(this, participants, txnId.epoch(), txnId.epoch(), this);
}
@Override
public AcceptReply apply(SafeCommandStore safeStore)
{
- StoreParticipants participants = StoreParticipants.invalidate(safeStore, Participants.singleton(txnId.domain(), someKey), txnId);
+ StoreParticipants participants = StoreParticipants.notAccept(safeStore, this.participants, txnId);
SafeCommand safeCommand = safeStore.get(txnId, participants);
- AcceptOutcome outcome = Commands.acceptInvalidate(safeStore, safeCommand, ballot);
+ AcceptOutcome outcome = Commands.notAccept(safeStore, safeCommand, status, ballot);
switch (outcome)
{
default: throw new IllegalArgumentException("Unknown status: " + outcome);
@@ -282,22 +301,28 @@ public class Accept extends TxnRequest.WithUnsynced<Accept.AcceptReply>
return AcceptReply.redundant(ballot, participants, safeCommand.current());
case Retired:
case Success:
- return AcceptReply.ACCEPT_INVALIDATE;
+ return AcceptReply.SUCCESS;
case RejectedBallot:
return new AcceptReply(safeCommand.current().promised());
}
}
+ @Override
+ public AcceptReply reduce(AcceptReply r1, AcceptReply r2)
+ {
+ return AcceptReply.reduce(r1, r2);
+ }
+
@Override
public MessageType type()
{
- return MessageType.ACCEPT_INVALIDATE_REQ;
+ return MessageType.NOT_ACCEPT_REQ;
}
@Override
public String toString()
{
- return "AcceptInvalidate{ballot:" + ballot + ", txnId:" + txnId + ", key:" + someKey + '}';
+ return "NotAccept{kind: " + status + ", ballot:" + ballot + ", txnId:" + txnId + ", key:" + participants + '}';
}
@Override
diff --git a/accord-core/src/main/java/accord/messages/Apply.java b/accord-core/src/main/java/accord/messages/Apply.java
index 00e2e289..760d9444 100644
--- a/accord-core/src/main/java/accord/messages/Apply.java
+++ b/accord-core/src/main/java/accord/messages/Apply.java
@@ -64,7 +64,7 @@ public class Apply extends TxnRequest<ApplyReply>
public final PartialDeps deps; // TODO (expected): this should be nullable, and only included if we did not send Commit (or if sending Maximal apply)
public final @Nullable PartialTxn txn;
public final @Nullable FullRoute<?> fullRoute;
- public final Writes writes;
+ public final @Nullable Writes writes;
public final Result result;
public final long minEpoch;
diff --git a/accord-core/src/main/java/accord/messages/Await.java b/accord-core/src/main/java/accord/messages/Await.java
index 88475038..0a5f8157 100644
--- a/accord-core/src/main/java/accord/messages/Await.java
+++ b/accord-core/src/main/java/accord/messages/Await.java
@@ -62,9 +62,9 @@ public class Await implements Request, MapReduceConsume<SafeCommandStore, Void>,
{
public static class SerializerSupport
{
- public static Await create(TxnId txnId, Participants<?> scope, BlockedUntil blockedUntil, long minAwaitEpoch, long maxAwaitEpoch, int callbackId)
+ public static Await create(TxnId txnId, Participants<?> scope, BlockedUntil blockedUntil, boolean notifyProgressLog, long minAwaitEpoch, long maxAwaitEpoch, int callbackId)
{
- return new Await(txnId, scope, blockedUntil, minAwaitEpoch, maxAwaitEpoch, callbackId);
+ return new Await(txnId, scope, blockedUntil, minAwaitEpoch, maxAwaitEpoch, callbackId, notifyProgressLog);
}
}
@@ -73,10 +73,11 @@ public class Await implements Request, MapReduceConsume<SafeCommandStore, Void>,
public final BlockedUntil blockedUntil;
public final long minAwaitEpoch, maxAwaitEpoch;
public final int callbackId; // < 0 means synchronous await
+ public final boolean notifyProgressLog;
- private transient Node node;
- private transient Id replyTo;
- private transient ReplyContext replyContext;
+ transient Node node;
+ transient Id replyTo;
+ transient ReplyContext replyContext;
private transient volatile RemoteListeners.Registration asyncRegistration;
private static final AtomicReferenceFieldUpdater<Await, RemoteListeners.Registration> registrationUpdater = AtomicReferenceFieldUpdater.newUpdater(Await.class, RemoteListeners.Registration.class, "asyncRegistration");
@@ -91,10 +92,11 @@ public class Await implements Request, MapReduceConsume<SafeCommandStore, Void>,
// we use exactly -1 to make serialization easy (can increment by 1 and store a non-negative integer)
private static final int SYNCHRONOUS_CALLBACKID = -1;
- public Await(Id to, Topologies topologies, TxnId txnId, Participants<?> participants, BlockedUntil blockedUntil, int callbackId)
+ public Await(Id to, Topologies topologies, TxnId txnId, Participants<?> participants, BlockedUntil blockedUntil, int callbackId, boolean notifyProgressLog)
{
this.txnId = txnId;
this.callbackId = callbackId;
+ this.notifyProgressLog = notifyProgressLog;
this.scope = computeScope(to, topologies, participants);
this.blockedUntil = blockedUntil;
this.maxAwaitEpoch = topologies.currentEpoch();
@@ -102,12 +104,12 @@ public class Await implements Request, MapReduceConsume<SafeCommandStore, Void>,
Invariants.checkState(minAwaitEpoch >= txnId.epoch());
}
- public Await(Id to, Topologies topologies, TxnId txnId, Participants<?> participants, BlockedUntil blockedUntil)
+ public Await(Id to, Topologies topologies, TxnId txnId, Participants<?> participants, BlockedUntil blockedUntil, boolean notifyProgressLog)
{
- this(to, topologies, txnId, participants, blockedUntil, SYNCHRONOUS_CALLBACKID);
+ this(to, topologies, txnId, participants, blockedUntil, SYNCHRONOUS_CALLBACKID, notifyProgressLog);
}
- private Await(TxnId txnId, Participants<?> scope, BlockedUntil blockedUntil, long minAwaitEpoch, long maxAwaitEpoch, int callbackId)
+ private Await(TxnId txnId, Participants<?> scope, BlockedUntil blockedUntil, long minAwaitEpoch, long maxAwaitEpoch, int callbackId, boolean notifyProgressLog)
{
this.txnId = txnId;
this.scope = scope;
@@ -115,6 +117,7 @@ public class Await implements Request, MapReduceConsume<SafeCommandStore, Void>,
this.minAwaitEpoch = minAwaitEpoch;
this.maxAwaitEpoch = maxAwaitEpoch;
this.callbackId = callbackId;
+ this.notifyProgressLog = notifyProgressLog;
Invariants.checkState(minAwaitEpoch >= txnId.epoch());
}
@@ -134,8 +137,11 @@ public class Await implements Request, MapReduceConsume<SafeCommandStore, Void>,
SafeCommand safeCommand = safeStore.get(txnId, participants);
Command command = safeCommand.current();
Invariants.checkState(minAwaitEpoch >= txnId.epoch());
- if (command.saveStatus().compareTo(blockedUntil.minSaveStatus) >= 0)
+ if (command.saveStatus().compareTo(blockedUntil.unblockedFrom) >= 0)
+ {
+ onNotWaiting(safeStore, safeCommand);
return null;
+ }
Commands.supplementParticipants(safeStore, safeCommand, participants);
@@ -144,7 +150,7 @@ public class Await implements Request, MapReduceConsume<SafeCommandStore, Void>,
RemoteListeners.Registration registered = asyncRegistration;
if (registered == null)
{
- registered = node.remoteListeners().register(txnId, blockedUntil.minSaveStatus, blockedUntil.remoteDurability, replyTo, callbackId);
+ registered = node.remoteListeners().register(txnId, blockedUntil.unblockedFrom, blockedUntil.remoteDurability, replyTo, callbackId);
if (!registrationUpdater.compareAndSet(this, null, registered))
registered = asyncRegistration;
}
@@ -158,7 +164,8 @@ public class Await implements Request, MapReduceConsume<SafeCommandStore, Void>,
synchronouslyWaitingOnUpdater.incrementAndGet(this);
}
- safeStore.progressLog().waiting(blockedUntil, safeStore, safeCommand, null, null, participants);
+ if (notifyProgressLog)
+ safeStore.progressLog().waiting(blockedUntil, safeStore, safeCommand, null, null, participants);
return null;
}
@@ -197,7 +204,7 @@ public class Await implements Request, MapReduceConsume<SafeCommandStore, Void>,
}
else
{
- node.reply(replyTo, replyContext, AwaitOk.Ready, null);
+ onSynchronousAwaitComplete();
}
}
}
@@ -258,13 +265,13 @@ public class Await implements Request, MapReduceConsume<SafeCommandStore, Void>,
@Override
public boolean notify(SafeCommandStore safeStore, SafeCommand safeCommand)
{
- Command command = safeCommand.current();
- if (command.saveStatus().compareTo(blockedUntil.minSaveStatus) >= 0)
+ if (!checkOneSynchronousAwait(safeCommand))
return true;
+ onNotWaiting(safeStore, safeCommand);
if (-1 == synchronouslyWaitingOnUpdater.decrementAndGet(this))
{
- node.reply(replyTo, replyContext, AwaitOk.Ready, null);
+ onSynchronousAwaitComplete();
if (timeout != null)
timeout.cancel();
syncRegistrations = null;
@@ -273,6 +280,23 @@ public class Await implements Request, MapReduceConsume<SafeCommandStore, Void>,
return false;
}
+ protected boolean checkOneSynchronousAwait(SafeCommand safeCommand)
+ {
+ Command command = safeCommand.current();
+ SaveStatus saveStatus = command.saveStatus();
+ return saveStatus.compareTo(blockedUntil.unblockedFrom) >= 0
+ || (blockedUntil.additionallyUnblockedBy != null && blockedUntil.additionallyUnblockedBy.test(saveStatus));
+ }
+
+ protected void onNotWaiting(SafeCommandStore safeStore, SafeCommand safeCommand)
+ {
+ }
+
+ protected void onSynchronousAwaitComplete()
+ {
+ node.reply(replyTo, replyContext, AwaitOk.Ready, null);
+ }
+
public static class AsyncAwaitComplete implements Request, PreLoadContext, Consumer<SafeCommandStore>
{
public final TxnId txnId;
diff --git a/accord-core/src/main/java/accord/messages/BeginInvalidation.java b/accord-core/src/main/java/accord/messages/BeginInvalidation.java
index 8d826135..41857ecb 100644
--- a/accord-core/src/main/java/accord/messages/BeginInvalidation.java
+++ b/accord-core/src/main/java/accord/messages/BeginInvalidation.java
@@ -63,7 +63,7 @@ public class BeginInvalidation extends AbstractRequest<BeginInvalidation.Invalid
@Override
public InvalidateReply apply(SafeCommandStore safeStore)
{
- StoreParticipants participants = StoreParticipants.invalidate(safeStore, this.participants, txnId);
+ StoreParticipants participants = StoreParticipants.notAccept(safeStore, this.participants, txnId);
SafeCommand safeCommand = safeStore.get(txnId, participants);
Command command = safeCommand.current();
boolean acceptedFastPath;
@@ -158,7 +158,7 @@ public class BeginInvalidation extends AbstractRequest<BeginInvalidation.Invalid
public boolean hasDecision()
{
- return maxKnowledgeStatus.known.executeAt.hasDecision();
+ return maxKnowledgeStatus.known.executeAt().hasDecision();
}
public boolean isPromiseRejected()
diff --git a/accord-core/src/main/java/accord/messages/BeginRecovery.java b/accord-core/src/main/java/accord/messages/BeginRecovery.java
index a258e112..ed4eae81 100644
--- a/accord-core/src/main/java/accord/messages/BeginRecovery.java
+++ b/accord-core/src/main/java/accord/messages/BeginRecovery.java
@@ -34,6 +34,7 @@ import accord.primitives.PartialDeps;
import accord.primitives.PartialTxn;
import accord.primitives.Participants;
import accord.primitives.Route;
+import accord.primitives.SaveStatus;
import accord.primitives.Status;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
@@ -43,11 +44,14 @@ import accord.primitives.Unseekables;
import accord.primitives.Writes;
import accord.topology.Topologies;
import accord.utils.Invariants;
+import accord.utils.UnhandledEnum;
import accord.utils.async.Cancellable;
-import static accord.local.CommandSummaries.IsDep.IS_NOT_DEP;
import static accord.local.CommandSummaries.ComputeIsDep.EITHER;
import static accord.local.CommandSummaries.SummaryStatus.NOT_DIRECTLY_WITNESSED;
+import static accord.local.CommandSummaries.SummaryStatus.ACCEPTED;
+import static accord.local.CommandSummaries.SummaryStatus.NOTACCEPTED;
+import static accord.local.CommandSummaries.SummaryStatus.PREACCEPTED;
import static accord.local.CommandSummaries.TestStartedAt.ANY;
import static accord.messages.BeginRecovery.RecoverReply.Kind.Ok;
import static accord.messages.BeginRecovery.RecoverReply.Kind.Reject;
@@ -56,9 +60,11 @@ import static accord.messages.BeginRecovery.RecoverReply.Kind.Truncated;
import static accord.messages.PreAccept.calculateDeps;
import static accord.primitives.EpochSupplier.constant;
import static accord.primitives.Known.KnownDeps.DepsUnknown;
-import static accord.primitives.Status.Accepted;
+import static accord.primitives.Status.AcceptedMedium;
import static accord.primitives.Status.Phase;
import static accord.primitives.Status.PreAccepted;
+import static accord.primitives.TxnId.FastPath.PRIVILEGED_COORDINATOR_WITH_DEPS;
+import static accord.primitives.TxnId.MediumPath.MEDIUM_PATH_WAIT_ON_RECOVERY;
import static accord.utils.Invariants.illegalState;
public class BeginRecovery extends TxnRequest.WithUnsynced<BeginRecovery.RecoverReply>
@@ -130,41 +136,44 @@ public class BeginRecovery extends TxnRequest.WithUnsynced<BeginRecovery.Recover
LatestDeps deps; {
PartialDeps coordinatedDeps = command.partialDeps();
Deps localDeps = null;
- if (!command.known().deps.hasCommittedOrDecidedDeps())
+ if (!command.known().deps().hasCommittedOrDecidedDeps())
{
localDeps = calculateDeps(safeStore, txnId, participants, constant(minEpoch), txnId, false);
}
if (localDeps != null && coordinatedDeps != null && !participants.touches().equals(coordinatedDeps.covering))
{
- deps = LatestDeps.create(coordinatedDeps.covering, command.known().deps, command.acceptedOrCommitted(), coordinatedDeps, null);
+ deps = LatestDeps.create(coordinatedDeps.covering, command.known().deps(), command.acceptedOrCommitted(), coordinatedDeps, null);
deps = LatestDeps.merge(deps, LatestDeps.create(participants.touches(), DepsUnknown, Ballot.ZERO, null, localDeps));
}
else
{
Participants<?> knownFor = coordinatedDeps == null ? participants.touches() : coordinatedDeps.covering;
- deps = LatestDeps.create(knownFor, command.known().deps, command.acceptedOrCommitted(), coordinatedDeps, localDeps);
+ deps = LatestDeps.create(knownFor, command.known().deps(), command.acceptedOrCommitted(), coordinatedDeps, localDeps);
}
}
boolean supersedingRejects;
Deps earlierNoWait, earlierWait;
- if (command.hasBeen(Accepted))
+ Deps laterNoWait, laterWait;
+ if (command.hasBeen(AcceptedMedium))
{
supersedingRejects = false;
earlierNoWait = earlierWait = Deps.NONE;
+ laterNoWait = laterWait = Deps.NONE;
}
else
{
// TODO (expected): modify the mapReduce API to perform this check in a single pass
class Visitor implements CommandSummaries.AllCommandVisitor, AutoCloseable
{
- Deps.Builder earlierNoWait, earlierWait;
+ Deps.Builder earlierNoWait, laterWait;
+ Deps.Builder earlierWait, laterNoWait;
boolean supersedingRejects;
@Override
public boolean visit(Unseekable keyOrRange, TxnId testTxnId, Timestamp testExecuteAt, SummaryStatus status, IsDep dep)
{
- if (status == NOT_DIRECTLY_WITNESSED || !txnId.is(testTxnId.witnesses()))
+ if (status == NOT_DIRECTLY_WITNESSED || !txnId.witnessedBy(testTxnId))
return true;
int c = testTxnId.compareTo(txnId);
@@ -175,12 +184,12 @@ public class BeginRecovery extends TxnRequest.WithUnsynced<BeginRecovery.Recover
{
switch (dep)
{
- default: throw new AssertionError("Unhandled IsDep: " + dep);
- case IS_DEP:
+ default: throw new UnhandledEnum(dep);
+ case IS_STABLE_DEP:
ensureEarlierNoWait().add(keyOrRange, testTxnId);
break;
- case IS_NOT_DEP:
+ case IS_NOT_STABLE_DEP:
/*
* The idea here is to discover those transactions that have been decided to execute after us
* and did not witness us as part of their pre-accept or accept round, as this means that we CANNOT have
@@ -195,6 +204,7 @@ public class BeginRecovery extends TxnRequest.WithUnsynced<BeginRecovery.Recover
switch (status)
{
case INVALIDATED:
+ // TODO (desired): optionally exclude these and other normally-unnecessary entries on e.g. first recovery attempt
ensureEarlierNoWait().add(keyOrRange, testTxnId);
break;
@@ -202,15 +212,61 @@ public class BeginRecovery extends TxnRequest.WithUnsynced<BeginRecovery.Recover
if (testExecuteAt.compareTo(txnId) > 0)
ensureEarlierWait().add(keyOrRange, testTxnId);
break;
+
+ case PREACCEPTED:
+ // no need to wait for potential medium path transactions started before us, only after
+ // however, both privileged coordinator optimisations require waiting for an earlier potential fast path to decide itself
+ // TODO (desired): compute against shard whether this is necessary - for most quorum configurations it doesn't
+ if (txnId.hasPrivilegedCoordinator())
+ ensureEarlierWait().add(keyOrRange, testTxnId);
}
}
}
else
{
- if (dep == IS_NOT_DEP)
+ switch (dep)
{
- supersedingRejects = true;
- return false;
+ case IS_STABLE_DEP:
+ // if we're accepted on the slow path, we can't rule out the possibility that there's a medium path commit that will be resurrected
+ // so we can't use this information to exclude the need to wait (without a quorum, but we don't want to have too many sets).
+ // however, we only care about cases where we might wait for the transaction
+ if (status != ACCEPTED && (testTxnId.is(PRIVILEGED_COORDINATOR_WITH_DEPS) || testTxnId.is(MEDIUM_PATH_WAIT_ON_RECOVERY)))
+ ensureLaterNoWait().add(keyOrRange, testTxnId);
+ break;
+
+ case IS_NOT_STABLE_DEP:
+ /*
+ * The idea here is to discover those transactions that were started after us and have been Accepted
+ * and did not witness us as part of their pre-accept round, as this means that we CANNOT have taken
+ * the fast path. This is central to safe recovery, as if every transaction that executes later has
+ * witnessed us we are safe to propose the pre-accept timestamp regardless, whereas if any transaction
+ * has not witnessed us we can safely invalidate (us).
+ */
+ supersedingRejects = true;
+ return false;
+
+ case NOT_ELIGIBLE:
+ // Must be pre-notaccepted, not-accepted or preaccepted without coordinator optimisation
+ // OR may be an out of range committed transaction (which can be ignored)
+ if (testTxnId.is(MEDIUM_PATH_WAIT_ON_RECOVERY))
+ {
+ // if the medium path is potentially active, then either it has been durably disabled, or we must wait to see if witnessed
+ if (status == NOTACCEPTED) ensureLaterNoWait().add(keyOrRange, testTxnId);
+ else if (status == PREACCEPTED) ensureLaterWait().add(keyOrRange, testTxnId);
+ }
+ // if the medium path is disabled we can safely proceed as we know the slow path must be taken,
+ // so we will either be witnessed or the command will be invalidated.
+ break;
+
+ case IS_COORD_DEP:
+ // the original coordinator witnessed us, so if it takes the fast or medium path we will be a durable dependency
+ // if it doesn't, it will take the slow path (and witness us), or be invalidated (in which case it doesn't matter)
+ break;
+
+ case IS_NOT_COORD_DEP:
+ Invariants.checkArgument(testTxnId.is(PRIVILEGED_COORDINATOR_WITH_DEPS));
+ // we don't know if we've been witnessed by the original coordinator that may yet take the fast (or medium) path
+ ensureLaterWait().add(keyOrRange, testTxnId);
}
}
@@ -231,6 +287,20 @@ public class BeginRecovery extends TxnRequest.WithUnsynced<BeginRecovery.Recover
return earlierWait;
}
+ private Deps.Builder ensureLaterWait()
+ {
+ if (laterWait == null)
+ laterWait = new Deps.Builder(true);
+ return laterWait;
+ }
+
+ private Deps.Builder ensureLaterNoWait()
+ {
+ if (laterNoWait == null)
+ laterNoWait = new Deps.Builder(true);
+ return laterNoWait;
+ }
+
@Override
public void close()
{
@@ -244,26 +314,37 @@ public class BeginRecovery extends TxnRequest.WithUnsynced<BeginRecovery.Recover
earlierWait.close();
earlierWait = null;
}
+ if (laterWait != null)
+ {
+ laterWait.close();
+ laterWait = null;
+ }
+ if (laterNoWait != null)
+ {
+ laterNoWait.close();
+ laterNoWait = null;
+ }
}
}
-
try (Visitor visitor = new Visitor())
{
safeStore.visit(participants.owns(), txnId, txnId.witnessedBy(), ANY, txnId, EITHER, visitor);
supersedingRejects = visitor.supersedingRejects;
earlierNoWait = visitor.earlierNoWait == null ? Deps.NONE : visitor.earlierNoWait.build();
earlierWait = visitor.earlierWait == null ? Deps.NONE : visitor.earlierWait.build();
+ laterNoWait = visitor.laterNoWait == null ? Deps.NONE : visitor.laterNoWait.build();
+ laterWait = visitor.laterWait == null ? Deps.NONE : visitor.laterWait.build();
}
}
- Status status = command.status();
+ SaveStatus saveStatus = command.saveStatus();
Ballot accepted = command.acceptedOrCommitted();
Timestamp executeAt = command.executeAt();
Writes writes = command.writes();
Result result = command.result();
- boolean acceptsFastPath = executeAt.equals(txnId) || participants.owns().isEmpty();
- return new RecoverOk(txnId, status, accepted, executeAt, deps, earlierWait, earlierNoWait, acceptsFastPath, supersedingRejects, writes, result);
+ boolean acceptsFastPath = participants.owns().isEmpty() || (txnId.hasPrivilegedCoordinator() ? saveStatus.known.hasPrivilegedVote() : executeAt.equals(txnId));
+ return new RecoverOk(txnId, saveStatus.status, accepted, executeAt, deps, earlierWait, earlierNoWait, laterWait, laterNoWait, acceptsFastPath, supersedingRejects, writes, result);
}
@Override
@@ -294,14 +375,17 @@ public class BeginRecovery extends TxnRequest.WithUnsynced<BeginRecovery.Recover
LatestDeps deps = LatestDeps.merge(ok1.deps, ok2.deps);
Deps earlierNoWait = ok1.earlierNoWait.with(ok2.earlierNoWait);
Deps earlierWait = ok1.earlierWait.with(ok2.earlierWait)
- .without(earlierNoWait);
+ .without(earlierNoWait);
+ Deps laterNoWait = ok1.laterNoWait.with(ok2.laterNoWait);
+ Deps laterWait = ok1.laterWait.with(ok2.laterWait)
+ .without(laterNoWait);
Timestamp timestamp = ok1.status == PreAccepted ? Timestamp.max(ok1.executeAt, ok2.executeAt) : ok1.executeAt;
return new RecoverOk(
txnId, ok1.status, ok1.accepted, timestamp,
- deps, earlierWait, earlierNoWait,
+ deps, earlierWait, earlierNoWait, laterWait, laterNoWait,
ok1.selfAcceptsFastPath & ok2.selfAcceptsFastPath,
- ok1.supersedingRejects | ok2.supersedingRejects,
+ ok1.supersedingRejects | ok2.supersedingRejects,
ok1.writes, ok1.result
);
}
@@ -362,14 +446,16 @@ public class BeginRecovery extends TxnRequest.WithUnsynced<BeginRecovery.Recover
public final Ballot accepted;
public final Timestamp executeAt;
public final LatestDeps deps;
- public final Deps earlierWait; // wait for these to commit
- public final Deps earlierNoWait; // counter-point to earlierWait, can remove these from any set we wait on
+ public final Deps earlierWait, earlierNoWait;
+ public final Deps laterWait, laterNoWait;
public final boolean selfAcceptsFastPath;
public final boolean supersedingRejects;
public final Writes writes;
public final Result result;
- public RecoverOk(TxnId txnId, Status status, Ballot accepted, Timestamp executeAt, LatestDeps deps, Deps earlierWait, Deps earlierNoWait, boolean selfAcceptsFastPath, boolean supersedingRejects, Writes writes, Result result)
+ public RecoverOk(TxnId txnId, Status status, Ballot accepted, Timestamp executeAt, LatestDeps deps,
+ Deps earlierWait, Deps earlierNoWait, Deps laterWait, Deps laterNoWait,
+ boolean selfAcceptsFastPath, boolean supersedingRejects, Writes writes, Result result)
{
this.txnId = txnId;
this.accepted = accepted;
@@ -378,6 +464,8 @@ public class BeginRecovery extends TxnRequest.WithUnsynced<BeginRecovery.Recover
this.deps = deps;
this.earlierWait = earlierWait;
this.earlierNoWait = earlierNoWait;
+ this.laterWait = laterWait;
+ this.laterNoWait = laterNoWait;
this.selfAcceptsFastPath = selfAcceptsFastPath;
this.supersedingRejects = supersedingRejects;
this.writes = writes;
@@ -404,8 +492,10 @@ public class BeginRecovery extends TxnRequest.WithUnsynced<BeginRecovery.Recover
", accepted:" + accepted +
", executeAt:" + executeAt +
", deps:" + deps +
- ", earlierCommittedWitness:" + earlierNoWait +
- ", earlierAcceptedNoWitness:" + earlierWait +
+ ", earlierWait:" + earlierWait +
+ ", earlierNoWait:" + earlierNoWait +
+ ", laterWait:" + laterWait +
+ ", laterNoWait:" + laterNoWait +
", selfAcceptsFastPath:" + selfAcceptsFastPath +
", supersedingRejects:" + supersedingRejects +
", writes:" + writes +
diff --git a/accord-core/src/main/java/accord/messages/Callback.java b/accord-core/src/main/java/accord/messages/Callback.java
index 69a663f3..549c92e2 100644
--- a/accord-core/src/main/java/accord/messages/Callback.java
+++ b/accord-core/src/main/java/accord/messages/Callback.java
@@ -30,5 +30,6 @@ public interface Callback<T>
void onSuccess(Id from, T reply);
default void onSlowResponse(Id from) {}
void onFailure(Id from, Throwable failure);
- void onCallbackFailure(Id from, Throwable failure);
+ // return true if the failure was handled/propagated
+ boolean onCallbackFailure(Id from, Throwable failure);
}
diff --git a/accord-core/src/main/java/accord/messages/CheckStatus.java b/accord-core/src/main/java/accord/messages/CheckStatus.java
index de6fac0c..54afe2b8 100644
--- a/accord-core/src/main/java/accord/messages/CheckStatus.java
+++ b/accord-core/src/main/java/accord/messages/CheckStatus.java
@@ -174,18 +174,18 @@ public class CheckStatus extends AbstractRequest<CheckStatus.CheckStatusReply>
private KnownMap foundKnown(Command command, StoreParticipants query)
{
SaveStatus saveStatus = command.saveStatus();
- if (query.owns() == command.participants().stillTouches() || (!saveStatus.known.deps.hasProposedOrDecidedDeps() && saveStatus.known.definition != DefinitionKnown))
+ if (query.owns() == command.participants().stillTouches() || (!saveStatus.known.deps().hasProposedOrDecidedDeps() && saveStatus.known.definition() != DefinitionKnown))
return KnownMap.create(query.owns(), saveStatus.known);
Known known = saveStatus.known;
KnownMap result = KnownMap.EMPTY;
- if (known.deps.hasProposedOrDecidedDeps())
+ if (known.deps().hasProposedOrDecidedDeps())
{
Invariants.checkState(command.participants().touches().containsAll(command.partialDeps().covering));
- result = KnownMap.create(command.partialDeps().covering, Known.Nothing.with(saveStatus.known.deps));
+ result = KnownMap.create(command.partialDeps().covering, Known.Nothing.with(saveStatus.known.deps()));
known = known.with(DepsUnknown);
}
- if (known.definition == DefinitionKnown && !txnId.isSystemTxn())
+ if (known.definition() == DefinitionKnown && !txnId.isSystemTxn())
{
Participants<?> participants = command.partialTxn().keys().toParticipants();
Invariants.checkState(command.participants().owns().containsAll(participants));
@@ -307,7 +307,7 @@ public class CheckStatus extends AbstractRequest<CheckStatus.CheckStatusReply>
public Timestamp executeAtIfKnown()
{
- if (maxKnown().executeAt.isDecidedAndKnownToExecute())
+ if (maxKnown().executeAt().isDecidedAndKnownToExecute())
return executeAt;
return null;
}
@@ -391,8 +391,8 @@ public class CheckStatus extends AbstractRequest<CheckStatus.CheckStatusReply>
{
Known known = map.knownFor(owns, touches);
Invariants.checkState(!known.hasFullRoute() || Route.isFullRoute(route));
- Invariants.checkState(!known.outcome.isInvalidated() || (!maxKnowledgeSaveStatus.known.isDecidedToExecute() && !maxSaveStatus.known.isDecidedToExecute()));
- Invariants.checkState(!(maxSaveStatus.known.outcome.isInvalidated() || maxKnowledgeSaveStatus.known.outcome.isInvalidated()) || !known.isDecidedToExecute());
+ Invariants.checkState(!known.outcome().isInvalidated() || (!maxKnowledgeSaveStatus.known.isDecidedToExecute() && !maxSaveStatus.known.isDecidedToExecute()));
+ Invariants.checkState(!(maxSaveStatus.known.outcome().isInvalidated() || maxKnowledgeSaveStatus.known.outcome().isInvalidated()) || !known.isDecidedToExecute());
// TODO (desired): make sure these match identically, rather than only ensuring Route.isFullRoute (either by coercing it here or by ensuring it at callers)
return known;
}
@@ -448,7 +448,7 @@ public class CheckStatus extends AbstractRequest<CheckStatus.CheckStatusReply>
CheckStatusOk maxPromised = prefer.maxPromised.compareTo(defer.maxPromised) >= 0 ? prefer : defer;
CheckStatusOk maxAccepted = prefer.maxAcceptedOrCommitted.compareTo(defer.maxAcceptedOrCommitted) >= 0 ? prefer : defer;
CheckStatusOk maxHomeKey = prefer.homeKey != null || defer.homeKey == null ? prefer : defer;
- CheckStatusOk maxExecuteAt = prefer.maxKnown().executeAt.compareTo(defer.maxKnown().executeAt) >= 0 ? prefer : defer;
+ CheckStatusOk maxExecuteAt = prefer.maxKnown().executeAt().compareTo(defer.maxKnown().executeAt()) >= 0 ? prefer : defer;
Route<?> mergedRoute = Route.merge(prefer.route, (Route)defer.route);
Durability mergedDurability = Durability.merge(prefer.durability, defer.durability);
InvalidIf invalidIf = prefer.invalidIf.atLeast(defer.invalidIf);
diff --git a/accord-core/src/main/java/accord/messages/Commit.java b/accord-core/src/main/java/accord/messages/Commit.java
index 9191900f..cf22af55 100644
--- a/accord-core/src/main/java/accord/messages/Commit.java
+++ b/accord-core/src/main/java/accord/messages/Commit.java
@@ -44,14 +44,14 @@ import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.primitives.Unseekables;
import accord.topology.Topologies;
-import accord.topology.Topology;
import accord.utils.Invariants;
import accord.utils.SortedArrays.SortedArrayList;
-import accord.utils.TriFunction;
+import accord.utils.UnhandledEnum;
import accord.utils.async.Cancellable;
import org.agrona.collections.IntHashSet;
import static accord.messages.Commit.Kind.CommitWithTxn;
+import static accord.messages.Commit.Kind.StableFastPath;
import static accord.primitives.SaveStatus.Committed;
import static accord.messages.Commit.Kind.StableWithTxnAndDeps;
import static accord.messages.Commit.WithDeps.HasDeps;
@@ -64,23 +64,62 @@ public class Commit extends TxnRequest.WithUnsynced<CommitOrReadNack>
{
public static class SerializerSupport
{
- public static Commit create(TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch, Kind kind, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nullable ReadData readData)
+ public static Commit create(TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch, Kind kind, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute)
{
- return new Commit(kind, txnId, scope, waitForEpoch, minEpoch, ballot, executeAt, partialTxn, partialDeps, fullRoute, readData);
+ return new Commit(kind, txnId, scope, waitForEpoch, minEpoch, ballot, executeAt, partialTxn, partialDeps, fullRoute);
}
}
- public final Kind kind;
- public final Ballot ballot;
- public final Timestamp executeAt;
- // TODO (expected): share keys with partialTxn and partialDeps - in memory and on wire
- public final @Nullable PartialTxn partialTxn;
- public final @Nullable PartialDeps partialDeps;
- public final @Nullable FullRoute<?> route;
- public final @Nullable ReadData readData;
+ public enum WithTxn
+ {
+ NoTxn, HasNewlyOwnedTxnRanges, HasTxn;
+ public PartialTxn select(Txn txn, Participants<?> scope, Topologies topologies, TxnId txnId, Id to)
+ {
+ switch (this)
+ {
+ default: throw new UnhandledEnum(this);
+ case HasTxn: return txn.intersecting(scope, true);
+ case HasNewlyOwnedTxnRanges:
+ if (txnId.epoch() != topologies.currentEpoch())
+ {
+ Ranges coordinateRanges = topologies.getEpoch(txnId.epoch()).rangesForNode(to);
+ Ranges commitRanges = topologies.computeRangesForNode(to);
+ Ranges extraRanges = commitRanges.without(coordinateRanges);
+ if (!extraRanges.isEmpty())
+ return txn.intersecting(scope.without(coordinateRanges), true);
+ }
+ case NoTxn:
+ return null;
+ }
+ }
+
+ public FullRoute<?> select(FullRoute<?> route)
+ {
+ switch (this)
+ {
+ default: throw new UnhandledEnum(this);
+ case HasTxn: return route;
+ case NoTxn:
+ case HasNewlyOwnedTxnRanges:
+ return null;
+ }
+ }
+ }
- public enum WithTxn { NoTxn, HasNewlyOwnedTxnRanges, HasTxn }
- public enum WithDeps { NoDeps, HasDeps }
+ public enum WithDeps
+ {
+ NoDeps, HasDeps;
+
+ public PartialDeps select(Deps deps, Participants<?> scope)
+ {
+ switch (this)
+ {
+ default: throw new UnhandledEnum(this);
+ case HasDeps: return deps.intersecting(scope);
+ case NoDeps: return null;
+ }
+ }
+ }
public enum Kind
{
@@ -88,12 +127,13 @@ public class Commit extends TxnRequest.WithUnsynced<CommitOrReadNack>
CommitWithTxn ( HasTxn, HasDeps, Committed),
// We retain HasNewlyOwnedTxnRanges for the later eventuality where we permit fast path decisions if the fast quorum is valid for all topologies and everyone agrees on the execution timestamp.
StableFastPath( HasNewlyOwnedTxnRanges, HasDeps, SaveStatus.Stable),
+ StableMediumPath( NoTxn, NoDeps, SaveStatus.Stable),
StableSlowPath( NoTxn, NoDeps, SaveStatus.Stable),
StableWithTxnAndDeps(HasTxn, HasDeps, SaveStatus.Stable);
- final WithTxn withTxn;
- final WithDeps withDeps;
- final SaveStatus saveStatus;
+ public final WithTxn withTxn;
+ public final WithDeps withDeps;
+ public final SaveStatus saveStatus;
Kind(WithTxn withTxn, WithDeps withDeps, SaveStatus saveStatus)
{
@@ -103,51 +143,30 @@ public class Commit extends TxnRequest.WithUnsynced<CommitOrReadNack>
}
}
- // TODO (low priority, clarity): cleanup passing of topologies here - maybe fetch them afresh from Node?
- // Or perhaps introduce well-named classes to represent different topology combinations
+ public final Kind kind;
+ public final Ballot ballot;
+ public final Timestamp executeAt;
+ // TODO (expected): share keys with partialTxn and partialDeps - in memory and on wire
+ public final @Nullable PartialTxn partialTxn;
+ public final @Nullable PartialDeps partialDeps;
+ public final @Nullable FullRoute<?> route;
- // TODO (required): must send to prior epochs for recovery decisions in those epochs
- public Commit(Kind kind, Id to, Topology coordinateTopology, Topologies topologies, TxnId txnId, Txn txn, FullRoute<?> route, Ballot ballot, Timestamp executeAt, Deps deps, ReadData read)
- {
- this(kind, to, coordinateTopology, topologies, txnId, txn, route, ballot, executeAt, deps, read == null ? null : (u1, u2, u3) -> read);
- }
- public Commit(Kind kind, Id to, Topology coordinateTopology, Topologies topologies, TxnId txnId, Txn txn, FullRoute<?> route, Ballot ballot, Timestamp executeAt, Deps deps, @Nullable Participants<?> readScope)
- {
- this(kind, to, coordinateTopology, topologies, txnId, txn, route, ballot, executeAt, deps, readScope != null ? new ReadTxnData(to, topologies, txnId, readScope, executeAt.epoch()) : null);
- }
+ // TODO (low priority, clarity): cleanup passing of topologies here - maybe fetch them afresh from Node?
+ // Or perhaps introduce well-named classes to represent different topology combinations
- public Commit(Kind kind, Id to, Topology coordinateTopology, Topologies topologies, TxnId txnId, Txn txn, FullRoute<?> route, Ballot ballot, Timestamp executeAt, Deps deps, TriFunction<Txn, Route<?>, PartialDeps, ReadData> toExecuteFactory)
+ public Commit(Kind kind, Id to, Topologies topologies, TxnId txnId, Txn txn, FullRoute<?> route, Ballot ballot, Timestamp executeAt, Deps deps)
{
super(to, topologies, txnId, route);
this.ballot = ballot;
-
- FullRoute<?> sendRoute = null;
- PartialTxn partialTxn = null;
- if (kind.withTxn == HasTxn)
- {
- // TODO (desired): only includeQuery if isHome; this affects state eviction and is low priority given size in C*
- partialTxn = txn.intersecting(scope, true);
- sendRoute = route;
- }
- else if (kind.withTxn == HasNewlyOwnedTxnRanges && executeAt.epoch() != txnId.epoch())
- {
- Ranges coordinateRanges = coordinateTopology.rangesForNode(to);
- Ranges executeRanges = topologies.computeRangesForNode(to);
- Ranges extraRanges = executeRanges.without(coordinateRanges);
- if (!extraRanges.isEmpty())
- partialTxn = txn.intersecting(scope.without(coordinateRanges), coordinateRanges.contains(route.homeKey()));
- }
-
this.kind = kind;
this.executeAt = executeAt;
- this.partialTxn = partialTxn;
- this.partialDeps = deps.intersecting(scope);
- this.route = sendRoute;
- this.readData = toExecuteFactory == null ? null : toExecuteFactory.apply(partialTxn != null ? partialTxn : txn, scope, partialDeps);
+ this.partialTxn = kind.withTxn.select(txn, scope, topologies, txnId, to);
+ this.partialDeps = kind.withDeps.select(deps, scope);
+ this.route = kind.withTxn.select(route);
}
- protected Commit(Kind kind, TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nullable ReadData readData)
+ protected Commit(Kind kind, TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute)
{
super(txnId, scope, waitForEpoch, minEpoch);
this.kind = kind;
@@ -156,44 +175,37 @@ public class Commit extends TxnRequest.WithUnsynced<CommitOrReadNack>
this.partialTxn = partialTxn;
this.partialDeps = partialDeps;
... 6729 lines suppressed ...
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org