diff --git a/accord-core/src/main/java/accord/api/AsyncExecutor.java b/accord-core/src/main/java/accord/api/AsyncExecutor.java index 25a645068e..47a2d14acf 100644 --- a/accord-core/src/main/java/accord/api/AsyncExecutor.java +++ b/accord-core/src/main/java/accord/api/AsyncExecutor.java @@ -35,6 +35,11 @@ default Cancellable execute(RunOrFail run) return AsyncCallbacks.execute(this, run); } + default Cancellable executeContinuation(RunOrFail run) + { + return AsyncCallbacks.execute(this, run); + } + default boolean tryExecuteImmediately(Runnable run) { return false; } // Depending on this implementation this method may queue-jump, i.e. task submission order is not guaranteed. @@ -50,6 +55,11 @@ default boolean executeMaybeImmediately(Runnable run) } AsyncChain chain(Runnable run); + /** + * As {@link #chain(Runnable)}, but if the submitting task fails while running this should be cancelled, + * failing the chain. See {@link #executeContinuation}. + */ + AsyncChain continuationChain(Runnable run); AsyncChain chain(Callable call); AsyncChain flatChain(Callable> call); } diff --git a/accord-core/src/main/java/accord/api/AsyncExecutorFactory.java b/accord-core/src/main/java/accord/api/AsyncExecutorFactory.java index 2c2123951a..9312643763 100644 --- a/accord-core/src/main/java/accord/api/AsyncExecutorFactory.java +++ b/accord-core/src/main/java/accord/api/AsyncExecutorFactory.java @@ -18,10 +18,8 @@ package accord.api; -import accord.local.SequentialAsyncExecutor; - public interface AsyncExecutorFactory { AsyncExecutor someExecutor(); - SequentialAsyncExecutor someSequentialExecutor(); + ExclusiveAsyncExecutor someExclusiveExecutor(); } diff --git a/accord-core/src/main/java/accord/impl/SafeState.java b/accord-core/src/main/java/accord/api/ExclusiveAsyncExecutor.java similarity index 79% rename from accord-core/src/main/java/accord/impl/SafeState.java rename to accord-core/src/main/java/accord/api/ExclusiveAsyncExecutor.java index fa6bd71d81..5881306d2f 100644 --- a/accord-core/src/main/java/accord/impl/SafeState.java +++ b/accord-core/src/main/java/accord/api/ExclusiveAsyncExecutor.java @@ -16,17 +16,11 @@ * limitations under the License. */ -package accord.impl; +package accord.api; /** - * State scoped to a single request that references global state + * A single-threaded AsyncExecutor */ -public interface SafeState +public interface ExclusiveAsyncExecutor extends AsyncExecutor { - T current(); - - default boolean isUnset() - { - return current() == null; - } } diff --git a/accord-core/src/main/java/accord/api/ProtocolModifiers.java b/accord-core/src/main/java/accord/api/ProtocolModifiers.java index e0c6fb5fbf..09e03158b5 100644 --- a/accord-core/src/main/java/accord/api/ProtocolModifiers.java +++ b/accord-core/src/main/java/accord/api/ProtocolModifiers.java @@ -26,7 +26,7 @@ import accord.primitives.Ballot; import accord.primitives.Deps; -import accord.primitives.Routable; +import accord.primitives.Routable.Domain; import accord.primitives.SaveStatus; import accord.primitives.Timestamp; import accord.primitives.Txn; @@ -211,7 +211,7 @@ public static synchronized void setTransitiveDependenciesAreVisible(Txn.Kind ... public static void validate() { - Invariants.require(dataStoreDetectsFutureReads || fastWriteExecution != MAY_BYPASS_SAFESTORE && fastReadExecution != MAY_BYPASS_SAFESTORE, "MAY_BYPASS_SAFESTORE is only permitted when dataStoreDetectsFutureReads"); + Invariants.require(dataStoreDetectsFutureReads || (fastWriteExecution != MAY_BYPASS_SAFESTORE && fastReadExecution != MAY_BYPASS_SAFESTORE), "MAY_BYPASS_SAFESTORE is only permitted when dataStoreDetectsFutureReads"); Invariants.require(permitCoordinatorLocalExecution || (!permittedFastPaths.contains(PrivilegedCoordinatorWithDeps) && !permittedFastPaths.contains(PrivilegedCoordinatorWithoutDeps)), "Privileged coordinator optimisations require coordinator local execution"); } } @@ -311,8 +311,8 @@ public static InformOfDurability informOfDurability(TxnId txnId, @Nullable Deps } private static FastExecution fastReadExecution = Configure.fastReadExecution; - public static boolean fastReadsMayBypassSafeStore(TxnId txnId) { return fastReadExecution == MAY_BYPASS_SAFESTORE && (dataStoreDetectsFutureReads() || txnId.is(EphemeralRead)) && txnId.is(Routable.Domain.Key); } - public static boolean fastReadsMayBypassCommandsForKey(TxnId txnId) { return fastReadExecution != FastExecution.DISABLED && !txnId.is(Txn.Kind.Write) && txnId.is(Routable.Domain.Key); } + public static boolean fastReadsMayBypassSafeStore(TxnId txnId) { return fastReadExecution == MAY_BYPASS_SAFESTORE && (dataStoreDetectsFutureReads() || txnId.is(EphemeralRead)) && txnId.is(Domain.Key); } + public static boolean fastReadsMayBypassCommandsForKey(TxnId txnId) { return fastReadExecution != FastExecution.DISABLED && !txnId.is(Txn.Kind.Write) && txnId.is(Domain.Key); } private static final boolean fastReadExecutionMayResendTxn = Configure.fastReadExecMayResendTxn; public static boolean fastReadExecutionMayResendTxn() { return fastReadExecutionMayResendTxn; } diff --git a/accord-core/src/main/java/accord/coordinate/AbstractCoordinatePreAccept.java b/accord-core/src/main/java/accord/coordinate/AbstractCoordinatePreAccept.java index cd68ec3b59..c0bb0cfc1d 100644 --- a/accord-core/src/main/java/accord/coordinate/AbstractCoordinatePreAccept.java +++ b/accord-core/src/main/java/accord/coordinate/AbstractCoordinatePreAccept.java @@ -22,7 +22,7 @@ import javax.annotation.Nonnull; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Callback; import accord.primitives.FullRoute; import accord.primitives.TxnId; @@ -38,7 +38,7 @@ abstract class AbstractCoordinatePreAccept route, @Nonnull TxnId txnId, BiConsumer callback) + AbstractCoordinatePreAccept(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, FullRoute route, @Nonnull TxnId txnId, BiConsumer callback) { super(node, executor, txnId, route, topologies.nodes(), callback); this.topologies = topologies; diff --git a/accord-core/src/main/java/accord/coordinate/AbstractCoordination.java b/accord-core/src/main/java/accord/coordinate/AbstractCoordination.java index 626c64be70..0aaec1b061 100644 --- a/accord-core/src/main/java/accord/coordinate/AbstractCoordination.java +++ b/accord-core/src/main/java/accord/coordinate/AbstractCoordination.java @@ -32,7 +32,7 @@ import accord.coordinate.tracking.RequestStatus; import accord.local.MapReduceConsumeCommandStores; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Request; import accord.messages.Callback; import accord.primitives.Participants; @@ -65,9 +65,9 @@ public abstract class AbstractCoordination

, Result, Re private BiConsumer callback; private Object[] replyState; private int replyCount; - private boolean unsafeToReplyImmediately; + private boolean unsafeToReply; - protected AbstractCoordination(Node node, SequentialAsyncExecutor executor, TxnId txnId, P scope, SortedArrayList nodes, BiConsumer callback) + protected AbstractCoordination(Node node, ExclusiveAsyncExecutor executor, TxnId txnId, P scope, SortedArrayList nodes, BiConsumer callback) { super(node, executor, txnId, scope); this.nodes = nodes; @@ -198,38 +198,45 @@ void contact(Function request) void contact(Function request, @Nullable Predicate include) { executor.executeMaybeImmediately(() -> { - unsafeToReplyImmediately = true; - AbstractTracker tracker = tracker(); - Topologies topologies = tracker.topologies(); - if (tracing != null) - tracing.trace(null, "contacting %s", nodes); - - for (int i = 0; i < nodes.size() ; ++i) + unsafeToReply = true; + try { - Node.Id to = nodes.get(i); - if (include == null || include.test(to)) + + AbstractTracker tracker = tracker(); + Topologies topologies = tracker.topologies(); + if (tracing != null) + tracing.trace(null, "contacting %s", nodes); + + for (int i = 0; i < nodes.size() ; ++i) { - if (topologies.isFaulty(to)) + Node.Id to = nodes.get(i); + if (include == null || include.test(to)) { - if (tracing != null) - tracing.trace(null, "%s is considered faulty; recording failure instead", to); - if (RequestStatus.Failed == tracker.prerecordFailure(to)) + if (topologies.isFaulty(to)) { - finishOnExaustion(); - return; + if (tracing != null) + tracing.trace(null, "%s is considered faulty; recording failure instead", to); + if (RequestStatus.Failed == tracker.prerecordFailure(to)) + { + finishOnExaustion(); + return; + } + } + else + { + Invariants.require(replyState[i] == null); + expectingReply.set(i); + // TODO (expected): do not cancel PreAccept, Accept, Commit, Stable or Apply to self on done + replyState[i] = node.send(to, request.apply(to), executor, this, tracing); + Invariants.require(expectingReply.get(i) || replyState[i] == null); } - } - else - { - Invariants.require(replyState[i] == null); - expectingReply.set(i); - // TODO (expected): do not cancel PreAccept, Accept, Commit, Stable or Apply to self on done - replyState[i] = node.send(to, request.apply(to), executor, this, tracing); - Invariants.require(expectingReply.get(i) || replyState[i] == null); } } } - unsafeToReplyImmediately = false; + finally + { + unsafeToReply = false; + } }); } @@ -245,19 +252,19 @@ void recontact(Node.Id to, Request send) @Override public final void onSuccess(Node.Id from, Reply reply) { - CallbackExclusive.onSuccess(executor, unsafeToReplyImmediately, this, from, reply); + CallbackExclusive.onSuccess(executor, unsafeToReply, this, from, reply); } @Override public final void onSlow(Node.Id from) { - CallbackExclusive.onSlow(executor, unsafeToReplyImmediately, this, from); + CallbackExclusive.onSlow(executor, unsafeToReply, this, from); } @Override public final void onFailure(Node.Id from, Throwable failure) { - CallbackExclusive.onFailure(executor, unsafeToReplyImmediately, this, from, failure); + CallbackExclusive.onFailure(executor, unsafeToReply, this, from, failure); } @Override @@ -308,6 +315,7 @@ public void onFailureExclusive(Node.Id from, @Nullable Throwable failure) private int onReply(Node.Id from, Object reply, boolean isFinal) { + Invariants.require(!unsafeToReply); int fromIndex = nodes.find(from); if (isDoneWithReplies()) { diff --git a/accord-core/src/main/java/accord/coordinate/AbstractSimpleCoordination.java b/accord-core/src/main/java/accord/coordinate/AbstractSimpleCoordination.java index 22f836d6fe..066bb8543d 100644 --- a/accord-core/src/main/java/accord/coordinate/AbstractSimpleCoordination.java +++ b/accord-core/src/main/java/accord/coordinate/AbstractSimpleCoordination.java @@ -22,7 +22,7 @@ import accord.api.Tracing; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.primitives.Participants; import accord.primitives.TxnId; import accord.utils.Invariants; @@ -33,14 +33,14 @@ public abstract class AbstractSimpleCoordination

> impl { final long coordinationId; protected final Node node; - protected final SequentialAsyncExecutor executor; + protected final ExclusiveAsyncExecutor executor; protected final TxnId txnId; protected final P scope; protected final @Nullable Tracing tracing; private Throwable failure; private boolean isDoneWithReplies, isFinishing, isDone; - protected AbstractSimpleCoordination(Node node, SequentialAsyncExecutor executor, TxnId txnId, P scope) + protected AbstractSimpleCoordination(Node node, ExclusiveAsyncExecutor executor, TxnId txnId, P scope) { this.coordinationId = node.nextCoordinationId(); this.node = node; @@ -65,7 +65,7 @@ public final TxnId txnId() public final P scope() { return scope; } @Override - public final SequentialAsyncExecutor executor() + public final ExclusiveAsyncExecutor executor() { return executor; } diff --git a/accord-core/src/main/java/accord/coordinate/AsynchronousAwait.java b/accord-core/src/main/java/accord/coordinate/AsynchronousAwait.java index 3a53d9e4db..9f5e6a1a2b 100644 --- a/accord-core/src/main/java/accord/coordinate/AsynchronousAwait.java +++ b/accord-core/src/main/java/accord/coordinate/AsynchronousAwait.java @@ -26,7 +26,7 @@ import accord.coordinate.tracking.RequestStatus; import accord.local.Commands; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Await; import accord.messages.Await.AwaitOk; import accord.messages.Callback; @@ -74,7 +74,7 @@ public SynchronousResult(Unseekables ready, @Nullable Unseekables notReady final int asynchronousCallbackId; final boolean notifyProgressLog; - public AsynchronousAwait(Node node, SequentialAsyncExecutor executor, Participants contact, TxnId txnId, AwaitTracker tracker, Await.Until until, boolean notifyProgressLog, int asynchronousCallbackId, BiConsumer synchronousCallback) + public AsynchronousAwait(Node node, ExclusiveAsyncExecutor executor, Participants contact, TxnId txnId, AwaitTracker tracker, Await.Until until, boolean notifyProgressLog, int asynchronousCallbackId, BiConsumer synchronousCallback) { super(node, executor, txnId, contact, tracker.nodes(), synchronousCallback); this.tracker = tracker; @@ -85,14 +85,14 @@ public AsynchronousAwait(Node node, SequentialAsyncExecutor executor, Participan public static AsynchronousAwait awaitAny(Node node, Topologies topologies, TxnId txnId, Route contact, Await.Until until, int asynchronousCallbackId, BiConsumer synchronousCallback) { - return awaitAny(node, node.someSequentialExecutor(), topologies, txnId, contact, until, true, asynchronousCallbackId, synchronousCallback); + return awaitAny(node, node.someExclusiveExecutor(), topologies, txnId, contact, until, 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, SequentialAsyncExecutor executor, Topologies topologies, TxnId txnId, Route contact, Await.Until until, boolean notifyProgressLog, int asynchronousCallbackId, BiConsumer synchronousCallback) + public static AsynchronousAwait awaitAny(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, TxnId txnId, Route contact, Await.Until until, boolean notifyProgressLog, int asynchronousCallbackId, BiConsumer synchronousCallback) { Invariants.requireArgument(topologies.size() == 1); AwaitTracker tracker = new AwaitTracker(topologies); diff --git a/accord-core/src/main/java/accord/coordinate/CheckShards.java b/accord-core/src/main/java/accord/coordinate/CheckShards.java index a772f31a89..349751665d 100644 --- a/accord-core/src/main/java/accord/coordinate/CheckShards.java +++ b/accord-core/src/main/java/accord/coordinate/CheckShards.java @@ -23,7 +23,7 @@ import accord.local.Node; import accord.local.Node.Id; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.CheckStatus; import accord.messages.CheckStatus.CheckStatusOk; import accord.messages.CheckStatus.CheckStatusReply; @@ -57,12 +57,12 @@ public abstract class CheckShards> extends ReadCoor protected boolean truncated; // srcEpoch is either txnId.epoch() or executeAt.epoch() - protected CheckShards(Node node, SequentialAsyncExecutor executor, TxnId txnId, U query, IncludeInfo includeInfo, @Nullable Ballot bumpBallot, Infer.InvalidIf previouslyKnownToBeInvalidIf, BiConsumer callback) throws TopologyException + protected CheckShards(Node node, ExclusiveAsyncExecutor executor, TxnId txnId, U query, IncludeInfo includeInfo, @Nullable Ballot bumpBallot, Infer.InvalidIf previouslyKnownToBeInvalidIf, BiConsumer callback) throws TopologyException { this(node, executor, txnId, query, txnId.epoch(), includeInfo, bumpBallot, previouslyKnownToBeInvalidIf, callback); } - protected CheckShards(Node node, SequentialAsyncExecutor executor, TxnId txnId, U query, long srcEpoch, IncludeInfo includeInfo, @Nullable Ballot bumpBallot, Infer.InvalidIf previouslyKnownToBeInvalidIf, BiConsumer callback) throws TopologyException + protected CheckShards(Node node, ExclusiveAsyncExecutor executor, TxnId txnId, U query, long srcEpoch, IncludeInfo includeInfo, @Nullable Ballot bumpBallot, Infer.InvalidIf previouslyKnownToBeInvalidIf, BiConsumer callback) throws TopologyException { super(node, executor, topologyFor(node, txnId, query, srcEpoch), txnId, query, callback); this.sourceEpoch = srcEpoch; diff --git a/accord-core/src/main/java/accord/coordinate/CollectLatestDeps.java b/accord-core/src/main/java/accord/coordinate/CollectLatestDeps.java index 282fa9cb99..f02d22401e 100644 --- a/accord-core/src/main/java/accord/coordinate/CollectLatestDeps.java +++ b/accord-core/src/main/java/accord/coordinate/CollectLatestDeps.java @@ -55,7 +55,7 @@ public class CollectLatestDeps extends AbstractCoordination, List route, @Nullable Ballot ballot, Timestamp executeAt, BiConsumer, Throwable> callback) { - super(node, node.someSequentialExecutor(), txnId, route, topologies.nodes(), callback); + super(node, node.someExclusiveExecutor(), txnId, route, topologies.nodes(), callback); this.executeAt = executeAt; this.ballot = ballot; this.tracker = new QuorumTracker(topologies); diff --git a/accord-core/src/main/java/accord/coordinate/CoordinateEphemeralRead.java b/accord-core/src/main/java/accord/coordinate/CoordinateEphemeralRead.java index 5526a22806..1572b1c31f 100644 --- a/accord-core/src/main/java/accord/coordinate/CoordinateEphemeralRead.java +++ b/accord-core/src/main/java/accord/coordinate/CoordinateEphemeralRead.java @@ -29,7 +29,7 @@ import accord.coordinate.tracking.AbstractTracker; import accord.coordinate.tracking.QuorumTracker; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.GetEphemeralReadDeps; import accord.messages.GetEphemeralReadDeps.GetEphemeralReadDepsOk; import accord.primitives.Deps; @@ -86,7 +86,7 @@ public static void coordinate(Node node, TxnId txnId, Txn txn, BiConsumer route = node.computeRoute(txnId, txn.keys()); Topologies topologies = node.topology().active().withUnsyncedEpochs(route, txnId, txnId); - coordinate = new CoordinateEphemeralRead(node, node.someSequentialExecutor(), topologies, route, txnId, txn, callback); + coordinate = new CoordinateEphemeralRead(node, node.someExclusiveExecutor(), topologies, route, txnId, txn, callback); } catch (Throwable t) { @@ -102,7 +102,7 @@ public static void coordinate(Node node, TxnId txnId, Txn txn, BiConsumer route, TxnId txnId, Txn txn, BiConsumer callback) + CoordinateEphemeralRead(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, FullRoute route, TxnId txnId, Txn txn, BiConsumer callback) { super(node, executor, topologies, route, txnId, callback); this.txn = txn; diff --git a/accord-core/src/main/java/accord/coordinate/CoordinateMaxConflict.java b/accord-core/src/main/java/accord/coordinate/CoordinateMaxConflict.java index 35ce4338bb..0761a29cfc 100644 --- a/accord-core/src/main/java/accord/coordinate/CoordinateMaxConflict.java +++ b/accord-core/src/main/java/accord/coordinate/CoordinateMaxConflict.java @@ -27,7 +27,7 @@ import accord.coordinate.tracking.AbstractTracker; import accord.coordinate.tracking.QuorumTracker; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.GetMaxConflict; import accord.messages.GetMaxConflict.GetMaxConflictOk; import accord.primitives.FullRoute; @@ -56,7 +56,7 @@ public class CoordinateMaxConflict extends AbstractCoordinatePreAccept route, long executionEpoch, BiConsumer callback) + private CoordinateMaxConflict(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, FullRoute route, long executionEpoch, BiConsumer callback) { super(node, executor, topologies, route, TxnId.NONE, callback); this.maxConflict = Timestamp.NONE; @@ -86,7 +86,7 @@ public static void maxConflict(Node node, Routables keysOrRanges, BiConsumer< long epoch = active.maxEpoch(Long.MIN_VALUE, ActiveEpoch::all, keysOrRanges); FullRoute route = node.computeRoute(epoch, keysOrRanges, active); Topologies topologies = active.withUnsyncedEpochs(route, epoch, epoch, ALL); - coordinate = new CoordinateMaxConflict(node, node.someSequentialExecutor(), topologies, route, epoch, callback); + coordinate = new CoordinateMaxConflict(node, node.someExclusiveExecutor(), topologies, route, epoch, callback); } catch (Throwable t) { diff --git a/accord-core/src/main/java/accord/coordinate/CoordinatePreAccept.java b/accord-core/src/main/java/accord/coordinate/CoordinatePreAccept.java index e887c86b08..f1659d637b 100644 --- a/accord-core/src/main/java/accord/coordinate/CoordinatePreAccept.java +++ b/accord-core/src/main/java/accord/coordinate/CoordinatePreAccept.java @@ -28,7 +28,7 @@ import accord.coordinate.tracking.PreAcceptTracker; import accord.local.Node; import accord.local.Node.Id; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.PreAccept; import accord.messages.PreAccept.PreAcceptOk; import accord.messages.PreAccept.PreAcceptReply; @@ -57,12 +57,12 @@ abstract class CoordinatePreAccept extends AbstractCoordinatePreAccept route, TxnId txnId, Txn txn, BiConsumer callback) + CoordinatePreAccept(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, FullRoute route, TxnId txnId, Txn txn, BiConsumer callback) { this(node, executor, txnId, txn, route, topologies, FastPathTracker::new, callback); } - CoordinatePreAccept(Node node, SequentialAsyncExecutor executor, TxnId txnId, Txn txn, FullRoute route, Topologies topologies, BiFunction> trackerFactory, BiConsumer callback) + CoordinatePreAccept(Node node, ExclusiveAsyncExecutor executor, TxnId txnId, Txn txn, FullRoute route, Topologies topologies, BiFunction> trackerFactory, BiConsumer callback) { super(node, executor, topologies, route, txnId, callback); this.tracker = trackerFactory.apply(topologies, txnId); diff --git a/accord-core/src/main/java/accord/coordinate/CoordinateSyncPoint.java b/accord-core/src/main/java/accord/coordinate/CoordinateSyncPoint.java index fb6eec6ae0..3b0f50cc4d 100644 --- a/accord-core/src/main/java/accord/coordinate/CoordinateSyncPoint.java +++ b/accord-core/src/main/java/accord/coordinate/CoordinateSyncPoint.java @@ -32,7 +32,7 @@ import accord.coordinate.CoordinationAdapter.Adapters.SyncPointAdapter; import accord.coordinate.ExecuteFlag.ExecuteFlags; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Accept; import accord.messages.Apply; import accord.messages.PreAccept.PreAcceptOk; @@ -78,7 +78,7 @@ public class CoordinateSyncPoint extends CoordinatePreAccept final CoordinationAdapter adapter; - private CoordinateSyncPoint(Node node, SequentialAsyncExecutor executor, TxnId txnId, Topologies topologies, Txn txn, FullRoute route, SyncPointAdapter adapter, BiConsumer callback) + private CoordinateSyncPoint(Node node, ExclusiveAsyncExecutor executor, TxnId txnId, Topologies topologies, Txn txn, FullRoute route, SyncPointAdapter adapter, BiConsumer callback) { super(node, executor, txnId, txn, route, topologies, adapter.preacceptTrackerFactory, callback); this.adapter = adapter; @@ -117,7 +117,7 @@ private static AsyncChain coordinate(Node node, TxnId txnId, Ranges r FullRoute route = (FullRoute) node.computeRoute(txnId, ranges); Txn txn = node.agent().emptySystemTxn(txnId.kind(), txnId.domain()); Topologies topologies = adapter.forDecision(node, route, txnId, txnId); - coordinate = new CoordinateSyncPoint<>(node, node.someSequentialExecutor(), txnId, topologies, txn, route, adapter, callback); + coordinate = new CoordinateSyncPoint<>(node, node.someExclusiveExecutor(), txnId, topologies, txn, route, adapter, callback); } catch (Throwable t) { diff --git a/accord-core/src/main/java/accord/coordinate/CoordinateTransaction.java b/accord-core/src/main/java/accord/coordinate/CoordinateTransaction.java index 652b82d9a1..b4cf9ba492 100644 --- a/accord-core/src/main/java/accord/coordinate/CoordinateTransaction.java +++ b/accord-core/src/main/java/accord/coordinate/CoordinateTransaction.java @@ -34,7 +34,7 @@ import accord.local.LoadKeysFor; import accord.local.SafeCommand; import accord.local.SafeCommandStore; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.local.StoreParticipants; import accord.messages.PreAccept.PreAcceptNack; import accord.messages.PreAccept.PreAcceptReply; @@ -74,7 +74,7 @@ */ public class CoordinateTransaction extends CoordinatePreAccept { - private CoordinateTransaction(Node node, SequentialAsyncExecutor executor, Topologies topologies, FullRoute route, TxnId txnId, Txn txn, BiConsumer callback) + private CoordinateTransaction(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, FullRoute route, TxnId txnId, Txn txn, BiConsumer callback) { super(node, executor, topologies, route, txnId, txn, callback); } @@ -99,7 +99,7 @@ public static void coordinate(Node node, TxnId txnId, Txn txn, BiConsumer route = node.computeRoute(txnId, txn.keys()); Topologies topologies = node.topology().active().select(route, txnId, txnId, LIVE, ProtocolModifiers.QuorumEpochIntersections.preaccept.include); - coordinate = new CoordinateTransaction(node, node.someSequentialExecutor(), topologies, route, txnId, txn, callback); + coordinate = new CoordinateTransaction(node, node.someExclusiveExecutor(), topologies, route, txnId, txn, callback); } catch (Throwable t) { @@ -299,5 +299,10 @@ public LoadKeysFor loadKeysFor() { return LoadKeysFor.READ_WRITE; } + + public ExecutionKind executionKind() + { + return ExecutionKind.PREACCEPT; + } } } diff --git a/accord-core/src/main/java/accord/coordinate/Coordination.java b/accord-core/src/main/java/accord/coordinate/Coordination.java index be54f29778..0d4bc41d23 100644 --- a/accord-core/src/main/java/accord/coordinate/Coordination.java +++ b/accord-core/src/main/java/accord/coordinate/Coordination.java @@ -23,7 +23,7 @@ import accord.api.Tracing; import accord.coordinate.tracking.AbstractTracker; import accord.local.Node.Id; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.primitives.Ballot; import accord.primitives.Participants; import accord.primitives.TxnId; @@ -73,7 +73,7 @@ public static CoordinationKind forOrdinal(int ordinal) default @Nullable SortedListMap replies() { return null; } - SequentialAsyncExecutor executor(); + ExclusiveAsyncExecutor executor(); /** * Try to abort the coordination; must be invoked by {@link #executor} diff --git a/accord-core/src/main/java/accord/coordinate/CoordinationAdapter.java b/accord-core/src/main/java/accord/coordinate/CoordinationAdapter.java index 5244a7c485..e4da2e53c9 100644 --- a/accord-core/src/main/java/accord/coordinate/CoordinationAdapter.java +++ b/accord-core/src/main/java/accord/coordinate/CoordinationAdapter.java @@ -28,7 +28,7 @@ import accord.coordinate.tracking.PreAcceptExclusiveSyncPointTracker; import accord.coordinate.tracking.PreAcceptTracker; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.local.durability.DurabilityResult; import accord.local.durability.DurabilityLevel; import accord.messages.Accept; @@ -70,13 +70,13 @@ enum Kind { Standard, Recovery } CoordinationAdapter get(TxnId txnId, Kind kind); } - void propose(Node node, SequentialAsyncExecutor executor, @Nullable Topologies preaccept, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback); - void proposeOnly(Node node, SequentialAsyncExecutor executor, Route require, Route sendTo, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback); - void stabilise(Node node, SequentialAsyncExecutor executor, @Nullable Topologies any, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback); - void stabiliseOnly(Node node, SequentialAsyncExecutor executor, Route require, Route sendTo, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback); - void execute(Node node, SequentialAsyncExecutor executor, @Nullable Topologies any, FullRoute route, Ballot ballot, ExecutePath path, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps stableDeps, Deps sendDeps, BiConsumer callback); - void persist(Node node, SequentialAsyncExecutor executor, @Nullable Topologies any, Route require, Route sendTo, FullRoute route, Ballot ballot, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, boolean informDurableOnDone, BiConsumer callback); - default void persist(Node node, SequentialAsyncExecutor executor, @Nullable Topologies any, FullRoute route, Ballot ballot, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer callback) + void propose(Node node, ExclusiveAsyncExecutor executor, @Nullable Topologies preaccept, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback); + void proposeOnly(Node node, ExclusiveAsyncExecutor executor, Route require, Route sendTo, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback); + void stabilise(Node node, ExclusiveAsyncExecutor executor, @Nullable Topologies any, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback); + void stabiliseOnly(Node node, ExclusiveAsyncExecutor executor, Route require, Route sendTo, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback); + void execute(Node node, ExclusiveAsyncExecutor executor, @Nullable Topologies any, FullRoute route, Ballot ballot, ExecutePath path, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps stableDeps, Deps sendDeps, BiConsumer callback); + void persist(Node node, ExclusiveAsyncExecutor executor, @Nullable Topologies any, Route require, Route sendTo, FullRoute route, Ballot ballot, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, boolean informDurableOnDone, BiConsumer callback); + default void persist(Node node, ExclusiveAsyncExecutor executor, @Nullable Topologies any, FullRoute route, Ballot ballot, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer callback) { persist(node, executor, any, route, route, route, ballot, flags, txnId, txn, executeAt, deps, writes, result, true, callback); } @@ -137,7 +137,7 @@ public TxnAdapter(Apply.Kind applyKind) } @Override - public void propose(Node node, SequentialAsyncExecutor executor, @Nullable Topologies preacceptOrRecovery, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) + public void propose(Node node, ExclusiveAsyncExecutor executor, @Nullable Topologies preacceptOrRecovery, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) { ProposeTxn propose; try @@ -156,7 +156,7 @@ public void propose(Node node, SequentialAsyncExecutor executor, @Nullable Topol } @Override - public void proposeOnly(Node node, SequentialAsyncExecutor executor, Route require, Route sendTo, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) + public void proposeOnly(Node node, ExclusiveAsyncExecutor executor, Route require, Route sendTo, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) { ProposeOnly propose; try @@ -175,7 +175,7 @@ public void proposeOnly(Node node, SequentialAsyncExecutor executor, Route re } @Override - public void stabilise(Node node, SequentialAsyncExecutor executor, Topologies accept, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) + public void stabilise(Node node, ExclusiveAsyncExecutor executor, Topologies accept, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) { ActiveEpochs epochs = node.topology().active(); if (!epochs.hasAtLeastEpoch(executeAt.epoch())) @@ -211,7 +211,7 @@ public void stabilise(Node node, SequentialAsyncExecutor executor, Topologies ac } @Override - public void stabiliseOnly(Node node, SequentialAsyncExecutor executor, Route require, Route sendTo, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) + public void stabiliseOnly(Node node, ExclusiveAsyncExecutor executor, Route require, Route sendTo, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) { ActiveEpochs epochs = node.topology().active(); if (!epochs.hasAtLeastEpoch(executeAt.epoch())) @@ -241,7 +241,7 @@ public void stabiliseOnly(Node node, SequentialAsyncExecutor executor, Route } @Override - public void execute(Node node, SequentialAsyncExecutor executor, Topologies any, FullRoute route, Ballot ballot, ExecutePath path, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps stableDeps, Deps sendDeps, BiConsumer callback) + public void execute(Node node, ExclusiveAsyncExecutor executor, Topologies any, FullRoute route, Ballot ballot, ExecutePath path, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps stableDeps, Deps sendDeps, BiConsumer callback) { ExecuteTxn execute; try @@ -286,14 +286,14 @@ public void execute(Node node, SequentialAsyncExecutor executor, Topologies any, } @Override - public void persist(Node node, SequentialAsyncExecutor executor, Topologies any, Route require, Route sendTo, FullRoute route, Ballot ballot, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, boolean informDurableOnDone, BiConsumer callback) + public void persist(Node node, ExclusiveAsyncExecutor executor, Topologies any, Route require, Route sendTo, FullRoute route, Ballot ballot, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, boolean informDurableOnDone, BiConsumer callback) { if (callback != null) callback.accept(result, null); try { Topologies all = execution(node, any, sendTo, route, txnId, executeAt); - new PersistTxn(node, executor, all, txnId, ballot, require, txn, executeAt, deps, writes, result.toPersistable(), route, flags, informDurableOnDone, Apply.FACTORY, applyKind) + new PersistTxn(node, executor, all, txnId, ballot, require, txn, executeAt, deps, writes, result == null ? null : result.toPersistable(), route, flags, informDurableOnDone, Apply.FACTORY, applyKind) .start(); } catch (TopologyException e) @@ -325,7 +325,7 @@ protected SyncPointAdapter(BiFunction> pr abstract void invokeSuccess(Node node, FullRoute route, TxnId txnId, Timestamp executeAt, Txn txn, Deps deps, BiConsumer callback); @Override - public void propose(Node node, SequentialAsyncExecutor executor, Topologies any, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) + public void propose(Node node, ExclusiveAsyncExecutor executor, Topologies any, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) { ProposeSyncPoint propose; try @@ -342,7 +342,7 @@ public void propose(Node node, SequentialAsyncExecutor executor, Topologies any, } @Override - public void proposeOnly(Node node, SequentialAsyncExecutor executor, Route require, Route sendTo, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) + public void proposeOnly(Node node, ExclusiveAsyncExecutor executor, Route require, Route sendTo, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) { ProposeOnly propose; try @@ -359,7 +359,7 @@ public void proposeOnly(Node node, SequentialAsyncExecutor executor, Route re } @Override - public void stabilise(Node node, SequentialAsyncExecutor executor, Topologies any, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) + public void stabilise(Node node, ExclusiveAsyncExecutor executor, Topologies any, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) { StabiliseSyncPoint stabilise; try @@ -377,7 +377,7 @@ public void stabilise(Node node, SequentialAsyncExecutor executor, Topologies an } @Override - public void stabiliseOnly(Node node, SequentialAsyncExecutor executor, Route require, Route sendTo, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) + public void stabiliseOnly(Node node, ExclusiveAsyncExecutor executor, Route require, Route sendTo, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) { StabiliseOnly stabilise; try @@ -395,13 +395,13 @@ public void stabiliseOnly(Node node, SequentialAsyncExecutor executor, Route } @Override - public void execute(Node node, SequentialAsyncExecutor executor, Topologies any, FullRoute route, Ballot ballot, ExecutePath path, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps stableDeps, Deps sendDeps, BiConsumer callback) + public void execute(Node node, ExclusiveAsyncExecutor executor, Topologies any, FullRoute route, Ballot ballot, ExecutePath path, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps stableDeps, Deps sendDeps, BiConsumer callback) { persist(node, executor, null, route, ballot, flags, txnId, txn, executeAt, stableDeps, null, txn.result(txnId, executeAt, null), callback); } @Override - public void persist(Node node, SequentialAsyncExecutor executor, Topologies ignore, Route require, Route sendTo, FullRoute route, Ballot ballot, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, boolean informDurableOnDone, BiConsumer callback) + public void persist(Node node, ExclusiveAsyncExecutor executor, Topologies ignore, Route require, Route sendTo, FullRoute route, Ballot ballot, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, boolean informDurableOnDone, BiConsumer callback) { invokeSuccess(node, route, txnId, executeAt, txn, deps, callback); @@ -438,7 +438,7 @@ Topologies forExecution(Node node, Route route, TxnId txnId, Timestamp execut } @Override - public void execute(Node node, SequentialAsyncExecutor executor, Topologies any, FullRoute route, Ballot ballot, ExecutePath path, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps stableDeps, Deps sendDeps, BiConsumer callback) + public void execute(Node node, ExclusiveAsyncExecutor executor, Topologies any, FullRoute route, Ballot ballot, ExecutePath path, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps stableDeps, Deps sendDeps, BiConsumer callback) { // We cannot use the fast path for sync points as their visibility is asymmetric wrt other transactions, // so we could recover to include different transactions than those we fast path committed with. diff --git a/accord-core/src/main/java/accord/coordinate/ExecuteEphemeralRead.java b/accord-core/src/main/java/accord/coordinate/ExecuteEphemeralRead.java index 7ebb05e473..96f62e7570 100644 --- a/accord-core/src/main/java/accord/coordinate/ExecuteEphemeralRead.java +++ b/accord-core/src/main/java/accord/coordinate/ExecuteEphemeralRead.java @@ -29,7 +29,7 @@ import accord.coordinate.ExecuteFlag.CoordinationFlags; import accord.local.Node; import accord.local.Node.Id; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.local.SafeCommand; import accord.local.SafeCommandStore; import accord.local.StoreParticipants; @@ -73,7 +73,7 @@ public class ExecuteEphemeralRead extends ReadCoordinator final CoordinationFlags flags; private Data data; - ExecuteEphemeralRead(Node node, SequentialAsyncExecutor executor, Topologies topologies, FullRoute route, TxnId txnId, Txn txn, Deps deps, CoordinationFlags flags, BiConsumer callback) + ExecuteEphemeralRead(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, FullRoute route, TxnId txnId, Txn txn, Deps deps, CoordinationFlags flags, BiConsumer callback) { // we need to send Stable to the origin epoch as well as the execution epoch // TODO (desired): permit slicing Topologies by key (though unnecessary if we eliminate the concept of non-participating home keys) @@ -249,6 +249,11 @@ protected void reply(ReadReply reply, Throwable fail) @Override public ReadType kind() { throw new UnsupportedOperationException(); } + + public ExecutionKind executionKind() + { + return ExecutionKind.STABLE; + } } @Override diff --git a/accord-core/src/main/java/accord/coordinate/ExecuteSyncPoint.java b/accord-core/src/main/java/accord/coordinate/ExecuteSyncPoint.java index 1db7c7dfbc..96d69f3437 100644 --- a/accord-core/src/main/java/accord/coordinate/ExecuteSyncPoint.java +++ b/accord-core/src/main/java/accord/coordinate/ExecuteSyncPoint.java @@ -28,7 +28,7 @@ import accord.coordinate.tracking.DurabilityTracker; import accord.coordinate.tracking.RequestStatus; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.local.durability.DurabilityResult; import accord.local.durability.DurabilityService.SyncRemote; import accord.messages.ApplyThenWaitUntilApplied; @@ -97,12 +97,12 @@ public void accept(DurabilityResult success, Throwable failure) boolean reportedQuorum, reportedMinorityQuorum, knownToSelf; long retryInFutureEpoch; - protected ExecuteSyncPoint(Node node, SequentialAsyncExecutor executor, Topologies topologies, PartialSyncPoint syncPoint, int attempt, DurabilityResults callback) + protected ExecuteSyncPoint(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, PartialSyncPoint syncPoint, int attempt, DurabilityResults callback) { this(node, executor, topologies, syncPoint, syncPoint.route, attempt, null, callback); } - ExecuteSyncPoint(Node node, SequentialAsyncExecutor executor, Topologies topologies, PartialSyncPoint syncPoint, Route route, int attempt, DurabilityResult partialResult, DurabilityResults callback) + ExecuteSyncPoint(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, PartialSyncPoint syncPoint, Route route, int attempt, DurabilityResult partialResult, DurabilityResults callback) { super(node, executor, syncPoint.syncId, route, topologies.nodes(), callback); this.syncPoint = syncPoint; @@ -266,17 +266,17 @@ DurabilityResult current() return partialResult.min(cur); } - public static DurabilityResults coordinateIncluding(Node node, PartialSyncPoint syncPoint, SequentialAsyncExecutor executor, int attempt) + public static DurabilityResults coordinateIncluding(Node node, PartialSyncPoint syncPoint, ExclusiveAsyncExecutor executor, int attempt) { return coordinate(node, syncPoint, executor, attempt); } public static DurabilityResults coordinate(Node node, SyncPoint syncPoint, int attempt) { - return coordinate(node, syncPoint, node.someSequentialExecutor(), attempt); + return coordinate(node, syncPoint, node.someExclusiveExecutor(), attempt); } - public static DurabilityResults coordinate(Node node, PartialSyncPoint syncPoint, SequentialAsyncExecutor executor, int attempt) + public static DurabilityResults coordinate(Node node, PartialSyncPoint syncPoint, ExclusiveAsyncExecutor executor, int attempt) { DurabilityResults result = new DurabilityResults(); try diff --git a/accord-core/src/main/java/accord/coordinate/ExecuteTxn.java b/accord-core/src/main/java/accord/coordinate/ExecuteTxn.java index b042af079a..97c82df612 100644 --- a/accord-core/src/main/java/accord/coordinate/ExecuteTxn.java +++ b/accord-core/src/main/java/accord/coordinate/ExecuteTxn.java @@ -39,7 +39,7 @@ import accord.local.Node.Id; import accord.local.SafeCommand; import accord.local.SafeCommandStore; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.local.StoreParticipants; import accord.local.cfk.CommandsForKey; import accord.local.cfk.CommandsForKey.TxnInfo; @@ -201,7 +201,7 @@ void informStableOnceQuorum() private long uniqueHlc; private boolean isPrivilegedVoteCommitting; - ExecuteTxn(Node node, SequentialAsyncExecutor executor, Topologies topologies, FullRoute route, Ballot ballot, ExecutePath path, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps stableDeps, Deps sendDeps, BiConsumer callback) + ExecuteTxn(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, FullRoute route, Ballot ballot, ExecutePath path, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps stableDeps, Deps sendDeps, BiConsumer callback) { super(node, executor, topologies.forEpoch(executeAt.epoch()), txnId, route, callback); if (!ballot.equals(Ballot.ZERO)) @@ -513,7 +513,7 @@ protected CoordinationAdapter adapter() private void onExternalSuccess(Result result) { - executor.execute(() -> { + executor.executeMaybeImmediately(() -> { if (!trySetDone()) return; diff --git a/accord-core/src/main/java/accord/coordinate/FetchCoordinator.java b/accord-core/src/main/java/accord/coordinate/FetchCoordinator.java index e646f9efa4..0fbecba412 100644 --- a/accord-core/src/main/java/accord/coordinate/FetchCoordinator.java +++ b/accord-core/src/main/java/accord/coordinate/FetchCoordinator.java @@ -24,7 +24,7 @@ import accord.api.DataStore.StartingRangeFetch; import accord.api.DataStore.FetchRanges; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.primitives.SyncPoint; import accord.primitives.Ranges; import accord.primitives.Route; @@ -145,7 +145,7 @@ void fail() private Ranges needed; private int inflight; - protected FetchCoordinator(Node node, SequentialAsyncExecutor executor, Ranges ranges, SyncPoint syncPoint, FetchRanges fetchRanges) throws TopologyException + protected FetchCoordinator(Node node, ExclusiveAsyncExecutor executor, Ranges ranges, SyncPoint syncPoint, FetchRanges fetchRanges) throws TopologyException { super(node, executor, syncPoint.syncId, syncPoint.route); this.ranges = remaining = ranges; diff --git a/accord-core/src/main/java/accord/coordinate/FetchData.java b/accord-core/src/main/java/accord/coordinate/FetchData.java index d501b3507d..3c114175c9 100644 --- a/accord-core/src/main/java/accord/coordinate/FetchData.java +++ b/accord-core/src/main/java/accord/coordinate/FetchData.java @@ -24,7 +24,7 @@ import accord.coordinate.Infer.InvalidIf; import accord.local.CommandStores.LatentStoreSelector; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.primitives.Known; import accord.messages.CheckStatus; import accord.messages.CheckStatus.CheckStatusOkFull; @@ -70,7 +70,7 @@ public FetchResult(@Nonnull Known target, Unseekables achievedTarget, Known f // TODO (expected): separate keys we fetch deps and txns for public static class FetchRequest implements BiConsumer { - final SequentialAsyncExecutor executor; + final ExclusiveAsyncExecutor executor; final Known fetch; final TxnId txnId; final InvalidIf invalidIf; @@ -82,7 +82,7 @@ public static class FetchRequest implements BiConsumer final BiConsumer callback; final @Nullable Tracing tracing; - public FetchRequest(SequentialAsyncExecutor executor, Known fetch, TxnId txnId, InvalidIf invalidIf, @Nullable Timestamp executeAt, Participants contactable, LatentStoreSelector reportTo, BiConsumer callback, @Nullable Tracing tracing) + public FetchRequest(ExclusiveAsyncExecutor executor, Known fetch, TxnId txnId, InvalidIf invalidIf, @Nullable Timestamp executeAt, Participants contactable, LatentStoreSelector reportTo, BiConsumer callback, @Nullable Tracing tracing) { this.executor = executor; this.fetch = fetch; @@ -116,7 +116,7 @@ public static Object fetchSpecific(Known fetch, Node node, TxnId txnId, @Nullabl */ public static Object fetchSpecific(Known fetch, Node node, TxnId txnId, InvalidIf invalidIf, @Nullable Timestamp executeAt, Route query, Route maxRoute, LatentStoreSelector reportTo, BiConsumer callback) { - return fetchSpecific(node, query, maxRoute, new FetchRequest(node.someSequentialExecutor(), fetch, txnId, invalidIf, executeAt, maxRoute, reportTo, callback, null)); + return fetchSpecific(node, query, maxRoute, new FetchRequest(node.someExclusiveExecutor(), fetch, txnId, invalidIf, executeAt, maxRoute, reportTo, callback, null)); } public static Object fetchSpecific(Node node, Route query, Route maxRoute, FetchRequest request) @@ -151,7 +151,7 @@ private FetchData(Node node, Known target, TxnId txnId, InvalidIf invalidIf, Rou private FetchData(Node node, Known target, TxnId txnId, InvalidIf invalidIf, Route route, Route routeWithHomeKey, Route maxRoute, long sourceEpoch, LatentStoreSelector reportTo, BiConsumer callback) throws TopologyException { // TODO (desired, efficiency): restore behaviour of only collecting info if e.g. Committed or Executed - super(node, node.someSequentialExecutor(), txnId, routeWithHomeKey, sourceEpoch, CheckStatus.IncludeInfo.All, null, invalidIf, callback); + super(node, node.someExclusiveExecutor(), txnId, routeWithHomeKey, sourceEpoch, CheckStatus.IncludeInfo.All, null, invalidIf, callback); this.reportTo = reportTo; this.maxRoute = maxRoute; Invariants.requireArgument(routeWithHomeKey.contains(route.homeKey()), "route %s does not contain %s", routeWithHomeKey, route.homeKey()); diff --git a/accord-core/src/main/java/accord/coordinate/FetchDurableBefore.java b/accord-core/src/main/java/accord/coordinate/FetchDurableBefore.java index 6b301c2c59..c454700ada 100644 --- a/accord-core/src/main/java/accord/coordinate/FetchDurableBefore.java +++ b/accord-core/src/main/java/accord/coordinate/FetchDurableBefore.java @@ -44,7 +44,7 @@ public class FetchDurableBefore extends AbstractCoordination callback) { - super(node, node.someSequentialExecutor(), TxnId.NONE, topology.ranges(), topology.nodes(), callback); + super(node, node.someExclusiveExecutor(), TxnId.NONE, topology.ranges(), topology.nodes(), callback); this.tracker = new QuorumTracker(new Topologies.Single(node.topology().sorter(), topology)); } diff --git a/accord-core/src/main/java/accord/coordinate/FetchRoute.java b/accord-core/src/main/java/accord/coordinate/FetchRoute.java index 2121404b63..6d6368e713 100644 --- a/accord-core/src/main/java/accord/coordinate/FetchRoute.java +++ b/accord-core/src/main/java/accord/coordinate/FetchRoute.java @@ -53,7 +53,7 @@ public class FetchRoute extends CheckShards, Participants> FetchRoute(Node node, TxnId txnId, Infer.InvalidIf invalidIf, Participants contactable, LatentStoreSelector reportTo, BiConsumer, Throwable> callback) throws TopologyException { - super(node, node.someSequentialExecutor(), txnId, contactable, txnId.epoch(), IncludeInfo.Route, null, invalidIf, callback); + super(node, node.someExclusiveExecutor(), txnId, contactable, txnId.epoch(), IncludeInfo.Route, null, invalidIf, callback); this.reportTo = reportTo; } diff --git a/accord-core/src/main/java/accord/coordinate/Invalidate.java b/accord-core/src/main/java/accord/coordinate/Invalidate.java index 954c4b0448..1e65c755a7 100644 --- a/accord-core/src/main/java/accord/coordinate/Invalidate.java +++ b/accord-core/src/main/java/accord/coordinate/Invalidate.java @@ -20,6 +20,7 @@ import java.util.function.BiConsumer; +import accord.api.ExclusiveAsyncExecutor; import accord.coordinate.tracking.AbstractTracker; import accord.coordinate.tracking.InvalidationTracker; import accord.coordinate.tracking.InvalidationTracker.InvalidationShardTracker; @@ -58,7 +59,7 @@ public class Invalidate extends AbstractCoordination, Outcome, I private final InvalidationTracker tracker; private final LatentStoreSelector reportTo; - private Invalidate(Node node, SequentialAsyncExecutor executor, Topologies topologies, Ballot ballot, TxnId txnId, Participants invalidateWith, boolean transitivelyInvokedByPriorInvalidation, LatentStoreSelector reportTo, BiConsumer callback) + private Invalidate(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, Ballot ballot, TxnId txnId, Participants invalidateWith, boolean transitivelyInvokedByPriorInvalidation, LatentStoreSelector reportTo, BiConsumer callback) { super(node, executor, txnId, invalidateWith, topologies.nodes(), callback); Invariants.require(topologies.size() == 1); @@ -85,7 +86,7 @@ public static void invalidate(Node node, TxnId txnId, Participants invalidate try { Topologies topologies = node.topology().active().forEpoch(invalidateWith, txnId.epoch(), ALL); - invalidate = new Invalidate(node, node.someSequentialExecutor(), topologies, ballot, txnId, invalidateWith, transitivelyInvokedByPriorInvalidation, reportTo, callback); + invalidate = new Invalidate(node, node.someExclusiveExecutor(), topologies, ballot, txnId, invalidateWith, transitivelyInvokedByPriorInvalidation, reportTo, callback); } catch (Throwable t) { diff --git a/accord-core/src/main/java/accord/coordinate/KeyBarriers.java b/accord-core/src/main/java/accord/coordinate/KeyBarriers.java index e7a28d744a..3825fdbded 100644 --- a/accord-core/src/main/java/accord/coordinate/KeyBarriers.java +++ b/accord-core/src/main/java/accord/coordinate/KeyBarriers.java @@ -29,7 +29,7 @@ import accord.local.MapReduceConsumeCommandStores; import accord.local.Node; import accord.local.SafeCommandStore; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.local.durability.DurabilityService.SyncLocal; import accord.local.durability.DurabilityService.SyncRemote; import accord.messages.Await; @@ -197,7 +197,7 @@ protected void reply(AwaitOk reply, Throwable failure) }; } - public static AsyncChain await(Node node, SequentialAsyncExecutor executor, Found found, SyncLocal syncLocal, SyncRemote syncRemote) + public static AsyncChain await(Node node, ExclusiveAsyncExecutor executor, Found found, SyncLocal syncLocal, SyncRemote syncRemote) { if (found == null) return AsyncChains.success(false); @@ -212,7 +212,7 @@ public static AsyncChain await(Node node, SequentialAsyncExecutor execu return AsyncChains.success(true); } - public static AsyncChain awaitRemote(Node node, SequentialAsyncExecutor executor, Found found, SyncRemote syncRemote) + public static AsyncChain awaitRemote(Node node, ExclusiveAsyncExecutor executor, Found found, SyncRemote syncRemote) { if (found.knownRemote.compareTo(syncRemote) >= 0) return AsyncChains.success(true); @@ -220,7 +220,7 @@ public static AsyncChain awaitRemote(Node node, SequentialAsyncExecutor return awaitRemote(node, executor, found.txnId, found.key); } - public static AsyncChain awaitRemote(Node node, SequentialAsyncExecutor executor, TxnId txnId, RoutingKey key) + public static AsyncChain awaitRemote(Node node, ExclusiveAsyncExecutor executor, TxnId txnId, RoutingKey key) { RoutingKeys keys = RoutingKeys.of(key); return SynchronousAwait.awaitQuorum(node, executor, txnId, keys, IsApplied, true); diff --git a/accord-core/src/main/java/accord/coordinate/MaybeRecover.java b/accord-core/src/main/java/accord/coordinate/MaybeRecover.java index e5b49dd520..6ba4f5e44f 100644 --- a/accord-core/src/main/java/accord/coordinate/MaybeRecover.java +++ b/accord-core/src/main/java/accord/coordinate/MaybeRecover.java @@ -21,7 +21,7 @@ import java.util.function.BiConsumer; import accord.local.CommandStores.LatentStoreSelector; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.InformDurable; import accord.primitives.*; import accord.topology.TopologyException; @@ -47,7 +47,7 @@ public class MaybeRecover extends CheckShards> final boolean recoverIfAlreadyDurable; final LatentStoreSelector reportTo; - MaybeRecover(Node node, SequentialAsyncExecutor executor, TxnId txnId, Infer.InvalidIf invalidIf, Route someRoute, ProgressToken prevProgress, boolean recoverIfAlreadyDurable, LatentStoreSelector reportTo, BiConsumer callback) throws TopologyException + MaybeRecover(Node node, ExclusiveAsyncExecutor executor, TxnId txnId, Infer.InvalidIf invalidIf, Route someRoute, ProgressToken prevProgress, boolean recoverIfAlreadyDurable, LatentStoreSelector reportTo, BiConsumer callback) throws TopologyException { // we only want to enquire with the home shard, but we prefer maximal route information for running Invalidation against, if necessary super(node, executor, txnId, someRoute.withHomeKey(), IncludeInfo.Route, null, invalidIf, callback); @@ -60,7 +60,7 @@ public static Object maybeRecover(Node node, TxnId txnId, Infer.InvalidIf invali MaybeRecover maybeRecover; try { - maybeRecover = new MaybeRecover(node, node.someSequentialExecutor(), txnId, invalidIf, someRoute, prevProgress, recoverIfAlreadyDurable, reportTo, callback); + maybeRecover = new MaybeRecover(node, node.someExclusiveExecutor(), txnId, invalidIf, someRoute, prevProgress, recoverIfAlreadyDurable, reportTo, callback); } catch (Throwable t) { diff --git a/accord-core/src/main/java/accord/coordinate/Persist.java b/accord-core/src/main/java/accord/coordinate/Persist.java index 304b6af313..cfbb1f6206 100644 --- a/accord-core/src/main/java/accord/coordinate/Persist.java +++ b/accord-core/src/main/java/accord/coordinate/Persist.java @@ -32,7 +32,7 @@ import accord.coordinate.tracking.SimpleTracker; import accord.local.Node; import accord.local.Node.Id; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Apply; import accord.messages.Apply.ApplyReply; import accord.messages.InformDurable; @@ -69,12 +69,12 @@ public abstract class Persist extends AbstractCoordination, Void, A protected final Apply.Kind applyKind; protected final boolean informDurableOnDone; - protected Persist(Node node, SequentialAsyncExecutor executor, Topologies all, TxnId txnId, Ballot ballot, Route sendTo, Txn txn, Timestamp executeAt, Deps stableDeps, Writes writes, PersistableResult result, FullRoute route, CoordinationFlags flags, boolean informDurableOnDone, Apply.Factory factory, Apply.Kind applyKind) + protected Persist(Node node, ExclusiveAsyncExecutor executor, Topologies all, TxnId txnId, Ballot ballot, Route sendTo, Txn txn, Timestamp executeAt, Deps stableDeps, Writes writes, PersistableResult result, FullRoute route, CoordinationFlags flags, boolean informDurableOnDone, Apply.Factory factory, Apply.Kind applyKind) { this(node, executor, all, txnId, ballot, sendTo, txn, executeAt, stableDeps, writes, result, route, flags, informDurableOnDone, factory, applyKind, QuorumTracker::new, node.agent()); } - protected Persist(Node node, SequentialAsyncExecutor executor, Topologies all, TxnId txnId, Ballot ballot, Route sendTo, Txn txn, Timestamp executeAt, Deps stableDeps, Writes writes, PersistableResult result, FullRoute route, CoordinationFlags flags, boolean informDurableOnDone, Apply.Factory factory, Apply.Kind applyKind, Function> trackerFactory, BiConsumer callback) + protected Persist(Node node, ExclusiveAsyncExecutor executor, Topologies all, TxnId txnId, Ballot ballot, Route sendTo, Txn txn, Timestamp executeAt, Deps stableDeps, Writes writes, PersistableResult result, FullRoute route, CoordinationFlags flags, boolean informDurableOnDone, Apply.Factory factory, Apply.Kind applyKind, Function> trackerFactory, BiConsumer callback) { super(node, executor, txnId, route, all.nodes(), callback); this.ballot = ballot; diff --git a/accord-core/src/main/java/accord/coordinate/PersistSyncPoint.java b/accord-core/src/main/java/accord/coordinate/PersistSyncPoint.java index 4d786b8f0f..df75e7c4d0 100644 --- a/accord-core/src/main/java/accord/coordinate/PersistSyncPoint.java +++ b/accord-core/src/main/java/accord/coordinate/PersistSyncPoint.java @@ -21,7 +21,7 @@ import accord.api.Result.PersistableResult; import accord.coordinate.ExecuteFlag.CoordinationFlags; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Apply; import accord.primitives.Ballot; import accord.primitives.Deps; @@ -35,7 +35,7 @@ public class PersistSyncPoint extends Persist { - public PersistSyncPoint(Node node, SequentialAsyncExecutor executor, Topologies topologies, TxnId txnId, Ballot ballot, Route sendTo, Txn txn, Timestamp executeAt, Deps deps, Writes writes, PersistableResult result, boolean informDurableOnDone, FullRoute route, Apply.Kind applyKind) + public PersistSyncPoint(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, TxnId txnId, Ballot ballot, Route sendTo, Txn txn, Timestamp executeAt, Deps deps, Writes writes, PersistableResult result, boolean informDurableOnDone, FullRoute route, Apply.Kind applyKind) { super(node, executor, topologies, txnId, ballot, sendTo, txn, executeAt, deps, writes, result, route, CoordinationFlags.none(), informDurableOnDone, Apply.FACTORY, applyKind); } diff --git a/accord-core/src/main/java/accord/coordinate/PersistTxn.java b/accord-core/src/main/java/accord/coordinate/PersistTxn.java index dd765ba7e7..f59b8eb443 100644 --- a/accord-core/src/main/java/accord/coordinate/PersistTxn.java +++ b/accord-core/src/main/java/accord/coordinate/PersistTxn.java @@ -21,7 +21,7 @@ import accord.api.Result.PersistableResult; import accord.coordinate.ExecuteFlag.CoordinationFlags; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Apply; import accord.primitives.Ballot; import accord.primitives.Deps; @@ -36,7 +36,7 @@ public class PersistTxn extends Persist { // TODO (desired): standardise parameter order with CoordinationAdapter (and others) - public PersistTxn(Node node, SequentialAsyncExecutor executor, Topologies topologies, TxnId txnId, Ballot ballot, Route sendTo, Txn txn, Timestamp executeAt, Deps deps, Writes writes, PersistableResult result, FullRoute route, CoordinationFlags flags, boolean informDurableOnDone, Apply.Factory factory, Apply.Kind applyKind) + public PersistTxn(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, TxnId txnId, Ballot ballot, Route sendTo, Txn txn, Timestamp executeAt, Deps deps, Writes writes, PersistableResult result, FullRoute route, CoordinationFlags flags, boolean informDurableOnDone, Apply.Factory factory, Apply.Kind applyKind) { super(node, executor, topologies, txnId, ballot, sendTo, txn, executeAt, deps, writes, result, route, flags, informDurableOnDone, factory, applyKind); } diff --git a/accord-core/src/main/java/accord/coordinate/PrepareRecovery.java b/accord-core/src/main/java/accord/coordinate/PrepareRecovery.java index 8392a18ee0..3bfa64b53d 100644 --- a/accord-core/src/main/java/accord/coordinate/PrepareRecovery.java +++ b/accord-core/src/main/java/accord/coordinate/PrepareRecovery.java @@ -25,7 +25,7 @@ import accord.coordinate.ExecuteFlag.CoordinationFlags; import accord.local.Node; import accord.local.Node.Id; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.CheckStatus; import accord.messages.CheckStatus.CheckStatusOk; import accord.messages.CheckStatus.CheckStatusOkFull; @@ -67,7 +67,7 @@ public class PrepareRecovery extends CheckShards> final Status witnessedByInvalidation; final LatentStoreSelector reportTo; - private PrepareRecovery(Node node, SequentialAsyncExecutor executor, Topologies topologies, TxnId txnId, Infer.InvalidIf invalidIf, FullRoute route, Status witnessedByInvalidation, LatentStoreSelector reportTo, BiConsumer callback) throws TopologyException + private PrepareRecovery(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, TxnId txnId, Infer.InvalidIf invalidIf, FullRoute route, Status witnessedByInvalidation, LatentStoreSelector reportTo, BiConsumer callback) throws TopologyException { super(node, executor, txnId, route, IncludeInfo.All, node.uniqueTimestamp(Ballot::fromValues), invalidIf, callback); this.reportTo = reportTo; @@ -79,7 +79,7 @@ private PrepareRecovery(Node node, SequentialAsyncExecutor executor, Topologies assert topologies.oldestEpoch() == topologies.currentEpoch() && topologies.currentEpoch() == txnId.epoch(); } - public static void recover(Node node, SequentialAsyncExecutor executor, TxnId txnId, Infer.InvalidIf invalidIf, FullRoute route, @Nullable Status witnessedByInvalidation, LatentStoreSelector reportTo, BiConsumer callback) + public static void recover(Node node, ExclusiveAsyncExecutor executor, TxnId txnId, Infer.InvalidIf invalidIf, FullRoute route, @Nullable Status witnessedByInvalidation, LatentStoreSelector reportTo, BiConsumer callback) { PrepareRecovery recover; try diff --git a/accord-core/src/main/java/accord/coordinate/Propose.java b/accord-core/src/main/java/accord/coordinate/Propose.java index 69d68f5f18..6a3a8de02e 100644 --- a/accord-core/src/main/java/accord/coordinate/Propose.java +++ b/accord-core/src/main/java/accord/coordinate/Propose.java @@ -35,7 +35,7 @@ import accord.local.Commands.AcceptOutcome; import accord.local.Node; import accord.local.Node.Id; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Accept; import accord.messages.Accept.AcceptFlags; import accord.messages.Accept.AcceptReply; @@ -77,7 +77,7 @@ abstract class Propose extends AbstractCoordination, R, AcceptRe final PreAcceptTracker tracker; final int acceptFlags; - Propose(Node node, SequentialAsyncExecutor executor, Topologies topologies, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Route require, FullRoute route, Timestamp executeAt, Deps deps, BiConsumer callback) + Propose(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Route require, FullRoute route, Timestamp executeAt, Deps deps, BiConsumer callback) { super(node, executor, txnId, route, topologies.nodes(), callback); this.kind = kind; @@ -249,7 +249,7 @@ static class NotAccept extends AbstractCoordination, Void, Accep private final SimpleTracker tracker; - NotAccept(Node node, SequentialAsyncExecutor executor, Status status, Topologies topologies, Ballot ballot, TxnId txnId, Participants someParticipants, BiConsumer callback) + NotAccept(Node node, ExclusiveAsyncExecutor executor, Status status, Topologies topologies, Ballot ballot, TxnId txnId, Participants someParticipants, BiConsumer callback) { super(node, executor, txnId, someParticipants, topologies.nodes(), callback); this.status = status; @@ -265,12 +265,12 @@ void start() contact(to -> new Accept.NotAccept(status, ballot, txnId, scope)); } - public static void proposeInvalidate(Node node, SequentialAsyncExecutor executor, Ballot ballot, TxnId txnId, RoutingKey invalidateWithParticipant, BiConsumer callback) + public static void proposeInvalidate(Node node, ExclusiveAsyncExecutor executor, Ballot ballot, TxnId txnId, RoutingKey invalidateWithParticipant, BiConsumer callback) { proposeNotAccept(node, executor, AcceptedInvalidate, ballot, txnId, invalidateWithParticipant, callback); } - public static void proposeNotAccept(Node node, SequentialAsyncExecutor executor, Status status, Ballot ballot, TxnId txnId, RoutingKey participatingKey, BiConsumer callback) + public static void proposeNotAccept(Node node, ExclusiveAsyncExecutor executor, Status status, Ballot ballot, TxnId txnId, RoutingKey participatingKey, BiConsumer callback) { try { @@ -285,7 +285,7 @@ public static void proposeNotAccept(Node node, SequentialAsyncExecutor executor, } } - public static void proposeAndCommitInvalidate(Node node, SequentialAsyncExecutor executor, Ballot ballot, TxnId txnId, RoutingKey invalidateWithParticipant, Route commitInvalidationTo, Timestamp invalidateUntil, @Nullable Tracing tracing, BiConsumer callback) + public static void proposeAndCommitInvalidate(Node node, ExclusiveAsyncExecutor executor, Ballot ballot, TxnId txnId, RoutingKey invalidateWithParticipant, Route commitInvalidationTo, Timestamp invalidateUntil, @Nullable Tracing tracing, BiConsumer callback) { proposeInvalidate(node, executor, ballot, txnId, invalidateWithParticipant, (success, fail) -> { if (fail != null) diff --git a/accord-core/src/main/java/accord/coordinate/ProposeOnly.java b/accord-core/src/main/java/accord/coordinate/ProposeOnly.java index c143fbeca9..f11ccd50e9 100644 --- a/accord-core/src/main/java/accord/coordinate/ProposeOnly.java +++ b/accord-core/src/main/java/accord/coordinate/ProposeOnly.java @@ -24,7 +24,7 @@ import org.slf4j.LoggerFactory; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Accept; import accord.primitives.Ballot; import accord.primitives.Deps; @@ -40,7 +40,7 @@ public class ProposeOnly extends Propose @SuppressWarnings("unused") private static final Logger logger = LoggerFactory.getLogger(ProposeOnly.class); - ProposeOnly(Node node, SequentialAsyncExecutor executor, Topologies topologies, Route sendTo, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) + ProposeOnly(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, Route sendTo, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) { super(node, executor, topologies, kind, ballot, txnId, txn, sendTo, route, executeAt, deps, callback); } diff --git a/accord-core/src/main/java/accord/coordinate/ProposeSyncPoint.java b/accord-core/src/main/java/accord/coordinate/ProposeSyncPoint.java index 5fe414bc36..ebf9e75060 100644 --- a/accord-core/src/main/java/accord/coordinate/ProposeSyncPoint.java +++ b/accord-core/src/main/java/accord/coordinate/ProposeSyncPoint.java @@ -24,7 +24,7 @@ import org.slf4j.LoggerFactory; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Accept; import accord.primitives.Ballot; import accord.primitives.Deps; @@ -40,7 +40,7 @@ public class ProposeSyncPoint extends Propose private static final Logger logger = LoggerFactory.getLogger(ProposeSyncPoint.class); private final CoordinationAdapter adapter; - ProposeSyncPoint(CoordinationAdapter adapter, Node node, SequentialAsyncExecutor executor, Topologies topologies, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) + ProposeSyncPoint(CoordinationAdapter adapter, Node node, ExclusiveAsyncExecutor executor, Topologies topologies, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) { super(node, executor, topologies, kind, ballot, txnId, txn, route, route, executeAt, deps, callback); this.adapter = adapter; diff --git a/accord-core/src/main/java/accord/coordinate/ProposeTxn.java b/accord-core/src/main/java/accord/coordinate/ProposeTxn.java index 7e804388fc..d5499794f4 100644 --- a/accord-core/src/main/java/accord/coordinate/ProposeTxn.java +++ b/accord-core/src/main/java/accord/coordinate/ProposeTxn.java @@ -22,7 +22,7 @@ import accord.api.Result; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Accept; import accord.primitives.Ballot; import accord.primitives.Deps; @@ -36,7 +36,7 @@ class ProposeTxn extends Propose { - ProposeTxn(Node node, SequentialAsyncExecutor executor, Topologies topologies, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) + ProposeTxn(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, FullRoute route, Accept.Kind kind, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) { super(node, executor, topologies, kind, ballot, txnId, txn, route, route, executeAt, deps, callback); } diff --git a/accord-core/src/main/java/accord/coordinate/ReadCoordinator.java b/accord-core/src/main/java/accord/coordinate/ReadCoordinator.java index f1783b3378..84054a9de3 100644 --- a/accord-core/src/main/java/accord/coordinate/ReadCoordinator.java +++ b/accord-core/src/main/java/accord/coordinate/ReadCoordinator.java @@ -31,7 +31,7 @@ import accord.coordinate.tracking.RequestStatus; import accord.local.Node; import accord.local.Node.Id; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Callback; import accord.messages.Callback.CallbackExclusive; import accord.primitives.Participants; @@ -112,7 +112,7 @@ protected enum Success private final long coordinationId; protected final Node node; - protected final SequentialAsyncExecutor executor; + protected final ExclusiveAsyncExecutor executor; protected final TxnId txnId; protected final @Nullable Tracing tracing; private final DebugMap debug; @@ -122,7 +122,7 @@ protected enum Success private Throwable failure; boolean unsafeToReplyImmediately; - protected ReadCoordinator(Node node, SequentialAsyncExecutor executor, Topologies topologies, TxnId txnId, Participants participants, BiConsumer callback) + protected ReadCoordinator(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, TxnId txnId, Participants participants, BiConsumer callback) { super(topologies); this.coordinationId = node.nextCoordinationId(); @@ -453,7 +453,7 @@ public AbstractTracker tracker() } @Override - public SequentialAsyncExecutor executor() + public ExclusiveAsyncExecutor executor() { return executor; } diff --git a/accord-core/src/main/java/accord/coordinate/Recover.java b/accord-core/src/main/java/accord/coordinate/Recover.java index 53af7f7173..b8f26aa7a7 100644 --- a/accord-core/src/main/java/accord/coordinate/Recover.java +++ b/accord-core/src/main/java/accord/coordinate/Recover.java @@ -34,7 +34,7 @@ import accord.local.CommandStores.LatentStoreSelector; import accord.local.Node; import accord.local.Node.Id; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Accept; import accord.messages.Await; import accord.primitives.Range; @@ -83,6 +83,7 @@ import static accord.messages.Await.Until.CommittedOrNotFastPathCommit; import static accord.messages.Await.Until.HasCommittedDeps; import static accord.messages.Await.Until.HasDecidedExecuteAt; +import static accord.messages.Await.Until.HasStableDeps; import static accord.messages.BeginRecovery.RecoverOk.maxAccepted; import static accord.messages.BeginRecovery.RecoverOk.maxAcceptedNotTruncated; import static accord.messages.BeginRecovery.RecoveryFlags.FAST_PATH_DECIDED; @@ -124,7 +125,7 @@ public InferredFastPath merge(InferredFastPath that) private final RecoveryTracker tracker; - private Recover(Node node, SequentialAsyncExecutor executor, Topologies topologies, Ballot ballot, TxnId txnId, Txn txn, FullRoute route, + private Recover(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, Ballot ballot, TxnId txnId, Txn txn, FullRoute route, @Nullable Timestamp committedExecuteAt, boolean isFastPathDecided, LatentStoreSelector reportTo, BiConsumer callback) { @@ -188,7 +189,7 @@ private static void recover(Node node, @Nullable Topologies topologies, Ballot b { if (topologies == null || (committedExecuteAt != null && topologies.currentEpoch() != committedExecuteAt.epoch())) topologies = node.topology().active().select(route, txnId, committedExecuteAt == null ? txnId : committedExecuteAt, ALL, QuorumEpochIntersections.recover); - recover = new Recover(node, node.someSequentialExecutor(), topologies, ballot, txnId, txn, route, committedExecuteAt, isFastPathDecided, reportTo, callback); + recover = new Recover(node, node.someExclusiveExecutor(), topologies, ballot, txnId, txn, route, committedExecuteAt, isFastPathDecided, reportTo, callback); } catch (Throwable t) { @@ -478,7 +479,11 @@ private void recover() // 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 commit and recompute supersedingRejects for them. - awaitToFinish(AsyncChains.reduce(awaitSimple(node, simpleWait, HasCommittedDeps), + // TODO (required): we diverge from the paper by preferring phase over ballot, so that we do not move phase backwards; Fedor wonders if this might mean we should wait for Stable here, not Committed + // however, since in the protocol Recovery already prefers phase over ballot, the only discrepancy is that we reject more messages than the protocol + // BUT we also diverge from the latest protocol by accepting ANY replica's commit record as decisive which is almost certainly too permissive, whereas Stable is safe, + // so for now we use Stable for safety until we can check conformance + awaitToFinish(AsyncChains.reduce(awaitSimple(node, simpleWait, HasStableDeps), awaitSupersedingCoord(node, laterWitnessedCoordRejects, CommittedOrNotFastPathCommit, extraCoordVotes), InferredFastPath::merge) .invokeIfSuccess((inferred) -> { diff --git a/accord-core/src/main/java/accord/coordinate/Stabilise.java b/accord-core/src/main/java/accord/coordinate/Stabilise.java index bd8c7cd49e..2d5d6100b1 100644 --- a/accord-core/src/main/java/accord/coordinate/Stabilise.java +++ b/accord-core/src/main/java/accord/coordinate/Stabilise.java @@ -25,7 +25,7 @@ import accord.coordinate.tracking.QuorumTracker; import accord.coordinate.tracking.RequestStatus; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Callback; import accord.messages.Commit; import accord.messages.ReadData.CommitOrReadNack; @@ -60,7 +60,7 @@ public abstract class Stabilise extends AbstractCoordination, R, final QuorumTracker tracker; final Topologies allTopologies; - public Stabilise(Node node, SequentialAsyncExecutor executor, Topologies coordinates, Topologies allTopologies, Route sendTo, FullRoute route, TxnId txnId, Ballot ballot, Txn txn, Timestamp executeAt, Deps stabiliseDeps, BiConsumer callback) + public Stabilise(Node node, ExclusiveAsyncExecutor executor, Topologies coordinates, Topologies allTopologies, Route sendTo, FullRoute route, TxnId txnId, Ballot ballot, Txn txn, Timestamp executeAt, Deps stabiliseDeps, BiConsumer callback) { super(node, executor, txnId, route, coordinates.nodes(), callback); this.txn = txn; diff --git a/accord-core/src/main/java/accord/coordinate/StabiliseOnly.java b/accord-core/src/main/java/accord/coordinate/StabiliseOnly.java index 782fa5914a..07af67201e 100644 --- a/accord-core/src/main/java/accord/coordinate/StabiliseOnly.java +++ b/accord-core/src/main/java/accord/coordinate/StabiliseOnly.java @@ -21,7 +21,7 @@ import java.util.function.BiConsumer; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.primitives.Ballot; import accord.primitives.Deps; import accord.primitives.FullRoute; @@ -33,7 +33,7 @@ public class StabiliseOnly extends Stabilise { - StabiliseOnly(Node node, SequentialAsyncExecutor executor, Topologies coordinates, Topologies all, Route sendTo, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps unstableDeps, BiConsumer callback) + StabiliseOnly(Node node, ExclusiveAsyncExecutor executor, Topologies coordinates, Topologies all, Route sendTo, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps unstableDeps, BiConsumer callback) { super(node, executor, coordinates, all, sendTo, route, txnId, ballot, txn, executeAt, unstableDeps, callback); } diff --git a/accord-core/src/main/java/accord/coordinate/StabiliseSyncPoint.java b/accord-core/src/main/java/accord/coordinate/StabiliseSyncPoint.java index d52a285dd1..d16cb4dc42 100644 --- a/accord-core/src/main/java/accord/coordinate/StabiliseSyncPoint.java +++ b/accord-core/src/main/java/accord/coordinate/StabiliseSyncPoint.java @@ -21,7 +21,7 @@ import java.util.function.BiConsumer; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.primitives.Ballot; import accord.primitives.Deps; import accord.primitives.FullRoute; @@ -33,7 +33,7 @@ public class StabiliseSyncPoint extends Stabilise { final CoordinationAdapter adapter; - StabiliseSyncPoint(CoordinationAdapter adapter, Node node, SequentialAsyncExecutor executor, Topologies coordinates, Topologies all, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps unstableDeps, BiConsumer callback) + StabiliseSyncPoint(CoordinationAdapter adapter, Node node, ExclusiveAsyncExecutor executor, Topologies coordinates, Topologies all, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps unstableDeps, BiConsumer callback) { super(node, executor, coordinates, all, route, route, txnId, ballot, txn, executeAt, unstableDeps, callback); this.adapter = adapter; diff --git a/accord-core/src/main/java/accord/coordinate/StabiliseTxn.java b/accord-core/src/main/java/accord/coordinate/StabiliseTxn.java index b15aef9eb4..1d0171bbaa 100644 --- a/accord-core/src/main/java/accord/coordinate/StabiliseTxn.java +++ b/accord-core/src/main/java/accord/coordinate/StabiliseTxn.java @@ -22,7 +22,7 @@ import accord.api.Result; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.primitives.Ballot; import accord.primitives.Deps; import accord.primitives.FullRoute; @@ -35,7 +35,7 @@ public class StabiliseTxn extends Stabilise { - StabiliseTxn(Node node, SequentialAsyncExecutor executor, Topologies coordinates, Topologies all, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps unstableDeps, BiConsumer callback) + StabiliseTxn(Node node, ExclusiveAsyncExecutor executor, Topologies coordinates, Topologies all, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps unstableDeps, BiConsumer callback) { super(node, executor, coordinates, all, route, route, txnId, ballot, txn, executeAt, unstableDeps, callback); } diff --git a/accord-core/src/main/java/accord/coordinate/SynchronousAwait.java b/accord-core/src/main/java/accord/coordinate/SynchronousAwait.java index 80d2a49a8d..866e3497d1 100644 --- a/accord-core/src/main/java/accord/coordinate/SynchronousAwait.java +++ b/accord-core/src/main/java/accord/coordinate/SynchronousAwait.java @@ -27,7 +27,7 @@ import accord.coordinate.tracking.RequestStatus; import accord.coordinate.tracking.SimpleTracker; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Await; import accord.primitives.Participants; import accord.primitives.TxnId; @@ -49,7 +49,7 @@ public class SynchronousAwait extends AbstractCoordination, Bool final Await.Until until; final boolean notifyProgressLog; - public SynchronousAwait(Node node, SequentialAsyncExecutor executor, TxnId txnId, Participants participants, SimpleTracker tracker, Await.Until until, boolean notifyProgressLog, BiConsumer callback) + public SynchronousAwait(Node node, ExclusiveAsyncExecutor executor, TxnId txnId, Participants participants, SimpleTracker tracker, Await.Until until, boolean notifyProgressLog, BiConsumer callback) { super(node, executor, txnId, participants, tracker.nodes(), callback); this.until = until; @@ -64,7 +64,7 @@ void start() contact(to -> new Await(to, tracker.topologies(), txnId, scope, until, notifyProgressLog)); } - public static AsyncChain awaitQuorum(Node node, SequentialAsyncExecutor executor, TxnId txnId, Participants participants, Await.Until until, boolean notifyProgressLog) + public static AsyncChain awaitQuorum(Node node, ExclusiveAsyncExecutor executor, TxnId txnId, Participants participants, Await.Until until, boolean notifyProgressLog) { // TODO (expected): copy this pattern elsewhere; should also make it easier to share exception handling logic etc return new AsyncChains.Head<>() diff --git a/accord-core/src/main/java/accord/coordinate/SynchronousRecoverAwait.java b/accord-core/src/main/java/accord/coordinate/SynchronousRecoverAwait.java index 33e23c731b..93e428ba9b 100644 --- a/accord-core/src/main/java/accord/coordinate/SynchronousRecoverAwait.java +++ b/accord-core/src/main/java/accord/coordinate/SynchronousRecoverAwait.java @@ -25,7 +25,7 @@ import accord.coordinate.Recover.InferredFastPath; import accord.local.Node; import accord.local.Node.Id; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Await; import accord.messages.RecoverAwait; import accord.messages.RecoverAwait.RecoverAwaitOk; @@ -46,6 +46,9 @@ * 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. + * + * TODO (required): protocol has diverged: latest version does not use concept equivalent to recovery await, + * instead waits for some commit */ public class SynchronousRecoverAwait extends ReadCoordinator { @@ -56,7 +59,7 @@ public class SynchronousRecoverAwait extends ReadCoordinator waitingOn; - public SynchronousRecoverAwait(Node node, SequentialAsyncExecutor executor, Topologies topologies, TxnId txnId, Participants participants, Await.Until until, boolean notifyProgressLog, TxnId recoverId, BiConsumer callback) + public SynchronousRecoverAwait(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, TxnId txnId, Participants participants, Await.Until until, boolean notifyProgressLog, TxnId recoverId, BiConsumer callback) { super(node, executor, topologies, txnId, participants, callback); this.participants = participants; @@ -66,14 +69,14 @@ public SynchronousRecoverAwait(Node node, SequentialAsyncExecutor executor, Topo this.waitingOn = participants; } - public static SynchronousRecoverAwait awaitAny(Node node, SequentialAsyncExecutor executor, Topologies topologies, TxnId txnId, Await.Until until, boolean notifyProgressLog, Participants participants, TxnId recoverId, BiConsumer callback) + public static SynchronousRecoverAwait awaitAny(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, TxnId txnId, Await.Until until, boolean notifyProgressLog, Participants participants, TxnId recoverId, BiConsumer callback) { SynchronousRecoverAwait result = new SynchronousRecoverAwait(node, executor, topologies, txnId, participants, until, notifyProgressLog, recoverId, callback); result.start(); return result; } - public static AsyncChain awaitAny(Node node, SequentialAsyncExecutor executor, Topologies topologies, TxnId txnId, Await.Until until, boolean notifyProgressLog, Participants participants, TxnId recoverId) + public static AsyncChain awaitAny(Node node, ExclusiveAsyncExecutor executor, Topologies topologies, TxnId txnId, Await.Until until, boolean notifyProgressLog, Participants participants, TxnId recoverId) { return new AsyncChains.Head<>() { diff --git a/accord-core/src/main/java/accord/impl/AbstractAsyncExecutor.java b/accord-core/src/main/java/accord/impl/AbstractAsyncExecutor.java index ac2133aebf..1439fd279a 100644 --- a/accord-core/src/main/java/accord/impl/AbstractAsyncExecutor.java +++ b/accord-core/src/main/java/accord/impl/AbstractAsyncExecutor.java @@ -34,6 +34,12 @@ default AsyncChain chain(Runnable run) return AsyncChains.chain(this, run); } + @Override + default AsyncChain continuationChain(Runnable run) + { + return AsyncChains.continuationChain(this, run); + } + @Override default AsyncChain chain(Callable call) { diff --git a/accord-core/src/main/java/accord/impl/AbstractFetchCoordinator.java b/accord-core/src/main/java/accord/impl/AbstractFetchCoordinator.java index a418ac2c5d..d2e0954bdc 100644 --- a/accord-core/src/main/java/accord/impl/AbstractFetchCoordinator.java +++ b/accord-core/src/main/java/accord/impl/AbstractFetchCoordinator.java @@ -24,9 +24,8 @@ import java.util.Map; import accord.local.Command; -import accord.local.MaxConflicts; import accord.local.SafeCommandStore; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.messages.Callback.ConcreteCallbackExclusive; import accord.messages.ReadData; import accord.primitives.SyncPoint; @@ -117,7 +116,7 @@ public boolean equals(Object obj) final FetchResult result = new FetchResult(this); protected final List> persisting = new ArrayList<>(); - protected AbstractFetchCoordinator(Node node, SequentialAsyncExecutor executor, Ranges ranges, SyncPoint syncPoint, DataStore.FetchRanges fetchRanges, CommandStore commandStore) throws TopologyException + protected AbstractFetchCoordinator(Node node, ExclusiveAsyncExecutor executor, Ranges ranges, SyncPoint syncPoint, DataStore.FetchRanges fetchRanges, CommandStore commandStore) throws TopologyException { super(node, executor, ranges, syncPoint, fetchRanges); this.fetchRanges = fetchRanges; diff --git a/accord-core/src/main/java/accord/impl/AbstractReplayer.java b/accord-core/src/main/java/accord/impl/AbstractReplayer.java index b089dc6a4c..f92140aa8e 100644 --- a/accord-core/src/main/java/accord/impl/AbstractReplayer.java +++ b/accord-core/src/main/java/accord/impl/AbstractReplayer.java @@ -141,7 +141,7 @@ else if (command.saveStatus().compareTo(Applying) >= 0 && command.saveStatus().c { if (command.txnId().is(Write) && replay.includes(TO_DATA_STORE)) { - Commands.applyChain(safeStore, command) + Commands.applyChain(safeStore, command.asExecuted()) .begin(safeStore.agent()); } else Invariants.expect(command.hasBeen(Applied), "%s is Applying but is not a Write transaction", txnId); diff --git a/accord-core/src/main/java/accord/impl/AbstractSafeCommandStore.java b/accord-core/src/main/java/accord/impl/AbstractSafeCommandStore.java index 7d3c7fd60f..6fd9e368a1 100644 --- a/accord-core/src/main/java/accord/impl/AbstractSafeCommandStore.java +++ b/accord-core/src/main/java/accord/impl/AbstractSafeCommandStore.java @@ -23,9 +23,8 @@ import java.util.NavigableMap; import accord.api.RoutingKey; -import accord.local.CommandStore; import accord.local.LoadKeys; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.RedundantBefore; import accord.local.SafeCommand; import accord.local.SafeCommandStore; @@ -45,21 +44,12 @@ public abstract class AbstractSafeCommandStore> extends SafeCommandStore { - protected final PreLoadContext context; - - private final CommandStore commandStore; + protected final ExecutionContext context; private FieldUpdates fieldUpdates; - protected AbstractSafeCommandStore(PreLoadContext context, CommandStore commandStore) + protected AbstractSafeCommandStore(ExecutionContext context) { this.context = context; - this.commandStore = commandStore; - } - - @Override - public CommandStore commandStore() - { - return commandStore; } public interface CommandStoreCaches extends AutoCloseable @@ -75,7 +65,7 @@ public interface CommandStoreCaches extends AutoCloseable protected abstract CFK add(CFK safeCfk, Caches caches); @Override - public PreLoadContext canExecute(PreLoadContext with) + public ExecutionContext canExecute(ExecutionContext with) { if (with.isEmpty()) return with; if (with.keys().domain() == Routable.Domain.Range) @@ -84,7 +74,7 @@ public PreLoadContext canExecute(PreLoadContext with) LoadKeys require = with.loadKeys(); if (require != LoadKeys.NONE) { - PreLoadContext context = context(); + ExecutionContext context = context(); if (!context.loadKeys().satisfiesIfPresent(require)) return null; @@ -144,12 +134,12 @@ public PreLoadContext canExecute(PreLoadContext with) if (unavailable.size() == keys.size()) return null; - return PreLoadContext.contextFor(with.primaryTxnId(), with.additionalTxnId(), keys.without(RoutingKeys.ofSortedUnique(unavailable)), loadKeys, context.loadKeysFor(), context.reason()); + return ExecutionContext.contextFor(with.primaryTxnId(), with.additionalTxnId(), keys.without(RoutingKeys.ofSortedUnique(unavailable)), loadKeys, context.loadKeysFor(), context.reason()); } } @Override - public PreLoadContext context() + public ExecutionContext context() { return context; } @@ -265,10 +255,12 @@ public void setRangesForEpoch(RangesForEpoch rangesForEpoch) @Override public RangesForEpoch ranges() { + // TODO (expected): do we even need this? We should probably reflect this immediately in CommandStore, and revert if we fail + // if we remove it if (fieldUpdates != null && fieldUpdates.newRangesForEpoch != null) return fieldUpdates.newRangesForEpoch; - return commandStore.unsafeGetRangesForEpoch(); + return commandStore().unsafeGetRangesForEpoch(); } @Override diff --git a/accord-core/src/main/java/accord/impl/DefaultLocalListeners.java b/accord-core/src/main/java/accord/impl/DefaultLocalListeners.java index 02bd617b9d..88a2521a37 100644 --- a/accord-core/src/main/java/accord/impl/DefaultLocalListeners.java +++ b/accord-core/src/main/java/accord/impl/DefaultLocalListeners.java @@ -36,8 +36,9 @@ import accord.local.Command; import accord.local.CommandStore; import accord.local.Commands; +import accord.local.ExecutionContext.ExecutionSequence; import accord.local.Node; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; import accord.primitives.SaveStatus; @@ -103,6 +104,7 @@ public void notify(SafeCommandStore safeStore, SafeCommand safeCommand, TxnId li if (listener != null && safeStore.tryRecurse()) { try { Commands.listenerUpdate(safeStore, listener, safeCommand); } + catch (Throwable t) { safeStore.agent().onException(t); } finally { safeStore.unrecurse(); } } else @@ -110,7 +112,7 @@ public void notify(SafeCommandStore safeStore, SafeCommand safeCommand, TxnId li //noinspection SillyAssignment,ConstantConditions safeStore = safeStore; // prevent use in lambda TxnId updatedId = safeCommand.txnId(); - PreLoadContext context = PreLoadContext.contextFor(listenerId, updatedId, "Notify"); + ExecutionContext context = ExecutionContext.unsequenced(listenerId, updatedId, "Notify"); safeStore.commandStore().execute(context, safeStore0 -> { notify(safeStore0, listenerId, updatedId); }, safeStore.agent()); } } @@ -123,7 +125,8 @@ private static void notify(SafeCommandStore safeStore, TxnId listenerId, TxnId u @Override public boolean notify(SafeCommandStore safeStore, SafeCommand safeCommand, ComplexListener listener) { - return listener.notify(safeStore, safeCommand); + try { return listener.notify(safeStore, safeCommand); } + catch (Throwable t) { safeStore.agent().onException(t); return false; } } } @@ -133,7 +136,7 @@ public boolean notify(SafeCommandStore safeStore, SafeCommand safeCommand, Compl * - encoding SaveStatus as byte * - encoding listeners as any of: single TxnId, array of TxnId (for small size), btree for a large collection */ - static class TxnListeners extends TxnId implements PreLoadContext + static class TxnListeners extends TxnId implements ExecutionContext { final SaveStatus await; TxnId[] listeners = NO_TXNIDS; diff --git a/accord-core/src/main/java/accord/impl/InMemoryCommandStore.java b/accord-core/src/main/java/accord/impl/InMemoryCommandStore.java index 41be863dbe..12cca42886 100644 --- a/accord-core/src/main/java/accord/impl/InMemoryCommandStore.java +++ b/accord-core/src/main/java/accord/impl/InMemoryCommandStore.java @@ -70,8 +70,8 @@ import accord.local.LoadKeysFor; import accord.local.MaxDecidedRX; import accord.local.NodeCommandStoreService; -import accord.local.PreLoadContext; -import accord.local.PreLoadContext.Empty; +import accord.local.ExecutionContext; +import accord.local.ExecutionContext.Empty; import accord.local.RedundantBefore; import accord.local.SafeCommand; import accord.local.SafeCommandStore; @@ -87,7 +87,6 @@ import accord.utils.async.AsyncResult; import accord.utils.async.AsyncResults; import accord.utils.async.Cancellable; -import org.agrona.collections.ObjectHashSet; import static accord.api.ProtocolModifiers.isRangeEndInclusive; import static accord.api.ProtocolModifiers.isRangeStartInclusive; @@ -210,7 +209,7 @@ public void cancel() protected final NavigableMap commands = new TreeMap<>(); final NavigableMap commandsByExecuteAt = new TreeMap<>(); - private final NavigableMap commandsForKey = new TreeMap<>(); + final NavigableMap commandsForKey = new TreeMap<>(); protected final InMemoryRangeSummaryIndex commandsForRanges; @@ -325,23 +324,11 @@ private GlobalCommand newGlobalCommand(TxnId txnId) return globalCommand; } - public InMemorySafeCommand lazyReference(TxnId txnId) - { - GlobalCommand command = commands.get(txnId); - return command != null ? new InMemorySafeCommand(txnId, command) - : new InMemorySafeCommand(txnId, () -> command(txnId)); - } - public boolean hasCommand(TxnId txnId) { return commands.containsKey(txnId); } - public GlobalCommandsForKey commandsForKeyIfPresent(RoutingKey key) - { - return commandsForKey.get(key); - } - public GlobalCommandsForKey commandsForKey(RoutingKey key) { return commandsForKey.computeIfAbsent(key, GlobalCommandsForKey::new); @@ -374,40 +361,43 @@ protected void ensureDurable(Ranges ranges, RedundantBefore onCommandStoreDurabl @Override protected void upsertedRedundantBefore(SafeCommandStore safeStore, RedundantBefore added) { - InMemorySafeStore inMemorySafeStore = (InMemorySafeStore) safeStore; - for (int i = 0 ; i < added.size() ; ++i) - { - if (added.valueAt(i) != null) + safeStore = safeStore; + execute((Empty)() -> "Upsert RedundantBefore", safeStore0 -> { + for (int i = 0 ; i < added.size() ; ++i) { - commandsForKey.subMap(added.startAt(i), isRangeStartInclusive(), added.startAt(i + 1), isRangeEndInclusive()).forEach((forKey, forValue) -> { - if (!forValue.isEmpty()) - { - InMemorySafeCommandsForKey safeCfk = forValue.createSafeReference(); - inMemorySafeStore.commandsForKey.put(forKey, safeCfk); - safeCfk.refresh(safeStore); - } - }); + if (added.valueAt(i) != null) + { + commandsForKey.subMap(added.startAt(i), isRangeStartInclusive(), added.startAt(i + 1), isRangeEndInclusive()).forEach((forKey, forValue) -> { + if (!forValue.isEmpty()) + { + InMemorySafeCommandsForKey safeCfk = forValue.createSafeReference(); + safeCfk.preExecute(); + ((InMemorySafeStore)safeStore0).commandsForKey.put(forKey, safeCfk); + safeCfk.refresh(safeStore0); + } + }); + } } - } - TxnId clearProgressLogBefore = unsafeGetRedundantBefore().minShardAndLocallyAppliedBefore(); - if (progressLog instanceof DefaultProgressLog) - { - List clearing = ((DefaultProgressLog) progressLog).activeBefore(clearProgressLogBefore); - for (TxnId txnId : clearing) + TxnId clearProgressLogBefore = unsafeGetRedundantBefore().minShardAndLocallyAppliedBefore(); + if (progressLog instanceof DefaultProgressLog) { - GlobalCommand globalCommand = commands.get(txnId); - if (globalCommand == null) - continue; // now we restore contents from snapshot, we might repopulate older items - Command command = globalCommand.value(); - StoreParticipants participants = command.participants().filter(LOAD, safeStore, txnId, command.executeAtIfKnown()); - Cleanup cleanup = Cleanup.shouldCleanup(FULL, txnId, command.executeAtIfKnown(), command.saveStatus(), command.durability(), participants, unsafeGetRedundantBefore(), durableBefore()); - Invariants.require(command.hasBeen(Applied) - || cleanup.compareTo(Cleanup.TRUNCATE) >= 0 - || (durableBefore().min(txnId) != Universal && - ((command.participants().stillExecutes() != null && command.participants().stillExecutes().isEmpty()) - || !Route.isFullRoute(command.route())))); + List clearing = ((DefaultProgressLog) progressLog).activeBefore(clearProgressLogBefore); + for (TxnId txnId : clearing) + { + GlobalCommand globalCommand = commands.get(txnId); + if (globalCommand == null) + continue; // now we restore contents from snapshot, we might repopulate older items + Command command = globalCommand.value(); + StoreParticipants participants = command.participants().filter(LOAD, safeStore0, txnId, command.executeAtIfKnown()); + Cleanup cleanup = Cleanup.shouldCleanup(FULL, txnId, command.executeAtIfKnown(), command.saveStatus(), command.durability(), participants, unsafeGetRedundantBefore(), durableBefore()); + Invariants.require(command.hasBeen(Applied) + || cleanup.compareTo(Cleanup.TRUNCATE) >= 0 + || (durableBefore().min(txnId) != Universal && + ((command.participants().stillExecutes() != null && command.participants().stillExecutes().isEmpty()) + || !Route.isFullRoute(command.route())))); + } } - } + }); super.upsertedRedundantBefore(safeStore, added); } @@ -426,7 +416,7 @@ protected void markExclusiveSyncPointLocallyApplied(SafeCommandStore safeStore, commandsForRanges.prune(syncId, ranges, safeStore.redundantBefore()); } - protected InMemorySafeStore createSafeStore(PreLoadContext context, CommandsForRangeLoad cfrLoad, + protected InMemorySafeStore createSafeStore(ExecutionContext context, CommandsForRangeLoad cfrLoad, Map commands, Map commandsForKeys) { @@ -437,19 +427,22 @@ protected void onRead(Command current) {} protected void onWrite(Command current) {} protected void onRead(CommandsForKey current) {} - protected final InMemorySafeStore createSafeStore(PreLoadContext context, CommandsForRangeLoad cfrLoad) + protected final InMemorySafeStore createSafeStore(ExecutionContext context, CommandsForRangeLoad cfrLoad) { Map commands = new HashMap<>(); Map commandsForKey = new HashMap<>(); - context.forEachId(txnId -> commands.put(txnId, lazyReference(txnId))); + context.forEachId(txnId -> commands.put(txnId, new InMemorySafeCommand(txnId, command(txnId)))); if (context.loadKeys() != NONE) { Unseekables unseekables = context.keys(); if (unseekables.domain() == Key) { for (RoutingKey key : (AbstractUnseekableKeys)unseekables) - commandsForKey.put(key, commandsForKey(key).createSafeReference()); + { + InMemorySafeCommandsForKey safeCfk = commandsForKey(key).createSafeReference(); + commandsForKey.put(key, safeCfk); + } } else if (context.loadKeysFor() != WRITE) { @@ -464,7 +457,10 @@ else if (context.loadKeysFor() != WRITE) InMemorySafeCommandsForKey safeCfk = commandsForKey.get(global.key); if (safeCfk == null) - commandsForKey.put(global.key, global.createSafeReference()); + { + safeCfk = global.createSafeReference(); + commandsForKey.put(global.key, safeCfk); + } } } } @@ -472,7 +468,7 @@ else if (context.loadKeysFor() != WRITE) return createSafeStore(context, cfrLoad, commands, commandsForKey); } - public SafeCommandStore beginOperation(PreLoadContext context, @Nullable CommandsForRangeLoad cfrLoad) + public SafeCommandStore beginOperation(ExecutionContext context, @Nullable CommandsForRangeLoad cfrLoad) { if (current != null) throw illegalState("Another operation is in progress or it's store was not cleared"); @@ -499,9 +495,9 @@ public void completeOperation(SafeCommandStore store) } } - protected T executeInContext(InMemoryCommandStore commandStore, PreLoadContext preLoadContext, @Nullable CommandsForRangeLoad cfrLoad, Function function) + protected T executeInContext(InMemoryCommandStore commandStore, ExecutionContext executionContext, @Nullable CommandsForRangeLoad cfrLoad, Function function) { - SafeCommandStore safeStore = commandStore.beginOperation(preLoadContext, cfrLoad); + SafeCommandStore safeStore = commandStore.beginOperation(executionContext, cfrLoad); try { return function.apply(safeStore); @@ -513,7 +509,7 @@ protected T executeInContext(InMemoryCommandStore commandStore, PreLoadConte } } - protected void executeInContext(InMemoryCommandStore commandStore, PreLoadContext context, @Nullable CommandsForRangeLoad cfrLoad, Function function, BiConsumer callback) + protected void executeInContext(InMemoryCommandStore commandStore, ExecutionContext context, @Nullable CommandsForRangeLoad cfrLoad, Function function, BiConsumer callback) { try { @@ -558,6 +554,7 @@ void add(Ranges add) public static abstract class GlobalState { private V value; + Object lockedBy; public V value() { @@ -579,6 +576,24 @@ public String toString() { return value == null ? "null" : value.toString(); } + + public void lock(Object lockedBy) + { + Invariants.require(lockedBy != null); + Invariants.require(this.lockedBy == null); + this.lockedBy = lockedBy; + } + + public void unlock(Object lockedBy) + { + Invariants.require(this.lockedBy == lockedBy); + this.lockedBy = null; + } + + public boolean isLocked() + { + return lockedBy != null; + } } public static class GlobalCommand extends GlobalState @@ -594,12 +609,6 @@ public InMemorySafeCommand createSafeReference() { return new InMemorySafeCommand(txnId, this); } - - @Override - public GlobalState value(Command value) - { - return super.value(value); - } } public static class GlobalCommandsForKey extends GlobalState @@ -636,7 +645,7 @@ public void close() {} public InMemorySafeCommand acquireIfLoaded(TxnId txnId) { GlobalCommand command = commands.get(txnId); - if (command == null) + if (command == null || command.isLocked()) return null; return command.createSafeReference(); } @@ -645,7 +654,7 @@ public InMemorySafeCommand acquireIfLoaded(TxnId txnId) public InMemorySafeCommandsForKey acquireIfLoaded(RoutingKey key) { GlobalCommandsForKey cfk = commandsForKey.get(key); - if (cfk == null) + if (cfk == null || cfk.isLocked()) return null; return cfk.createSafeReference(); } @@ -653,31 +662,25 @@ public InMemorySafeCommandsForKey acquireIfLoaded(RoutingKey key) public static class InMemorySafeStore extends AbstractSafeCommandStore { + final InMemoryCommandStore commandStore; protected final Map commands; private final Map commandsForKey; private final CommandsForRangeLoad cfrLoad; - private final Set hasLoaded = new ObjectHashSet<>(); private ByTxnIdSnapshot commandsForRanges; public InMemorySafeStore(InMemoryCommandStore commandStore, - PreLoadContext context, + ExecutionContext context, CommandsForRangeLoad cfrLoad, Map commands, Map commandsForKey) { - super(context, commandStore); - + super(context); + this.commandStore = commandStore; this.commands = commands; this.commandsForKey = commandsForKey; this.cfrLoad = cfrLoad; - for (InMemorySafeCommand cmd : commands.values()) - { - if (cmd.isUnset()) cmd.uninitialised(); - } - for (InMemorySafeCommandsForKey cfk : commandsForKey.values()) - { - if (cfk.isUnset()) cfk.initialize(); - } + commands.values().forEach(InMemorySafeCommand::preExecute); + commandsForKey.values().forEach(InMemorySafeCommandsForKey::preExecute); if (cfrLoad != null) cfrLoad.cancel(); } @@ -691,7 +694,7 @@ protected InMemorySafeCommand getInternal(TxnId txnId) @Override public InMemoryCommandStore commandStore() { - return (InMemoryCommandStore) super.commandStore(); + return commandStore; } @Override @@ -706,7 +709,7 @@ protected InMemoryCommandStoreCaches tryGetCaches() protected SafeCommand maybeCleanup(SafeCommand safeCommand) { SafeCommand result = super.maybeCleanup(safeCommand); - if (!((InMemorySafeCommand)result).isModified() && hasLoaded.add(safeCommand.txnId())) + if (((InMemorySafeCommand)result).touch()) commandStore().onRead(result.current()); return result; } @@ -714,17 +717,20 @@ protected SafeCommand maybeCleanup(SafeCommand safeCommand) @Override protected SafeCommand maybeCleanup(SafeCommand safeCommand, @Nonnull StoreParticipants supplemental) { - SafeCommand result = super.maybeCleanup(safeCommand, supplemental); - if (!((InMemorySafeCommand)result).isModified() && hasLoaded.add(safeCommand.txnId())) + if (((InMemorySafeCommand)safeCommand).touch()) + { + SafeCommand result = super.maybeCleanup(safeCommand, safeCommand.current().participants()); commandStore().onRead(result.current()); - return result; + safeCommand = result; + } + return super.maybeCleanup(safeCommand, supplemental); } @Override protected SafeCommandsForKey maybeCleanup(SafeCommandsForKey safeCfk) { safeCfk = super.maybeCleanup(safeCfk); - if (hasLoaded.add(safeCfk.key())) + if (((InMemorySafeCommandsForKey)safeCfk).touch()) commandStore().onRead(safeCfk.current()); return safeCfk; } @@ -732,7 +738,7 @@ protected SafeCommandsForKey maybeCleanup(SafeCommandsForKey safeCfk) @Override protected InMemorySafeCommand add(InMemorySafeCommand command, InMemoryCommandStoreCaches caches) { - if (command.isUnset()) command.uninitialised(); + command.preExecute(); commands.put(command.txnId(), command); return command; } @@ -743,10 +749,11 @@ protected InMemorySafeCommandsForKey ifLoadedInternal(RoutingKey key) if (context.loadKeys() != NONE && context.keys().domain() == Range && context.keys().contains(key)) { GlobalCommandsForKey globalCfk = commandStore().commandsForKey.get(key); - if (globalCfk == null) + if (globalCfk == null || globalCfk.isLocked()) return null; InMemorySafeCommandsForKey safeCfk = globalCfk.createSafeReference(); + safeCfk.preExecute(); commandsForKey.put(key, safeCfk); return safeCfk; } @@ -763,7 +770,7 @@ protected InMemorySafeCommandsForKey getInternal(RoutingKey key) @Override protected InMemorySafeCommandsForKey add(InMemorySafeCommandsForKey cfk, InMemoryCommandStoreCaches caches) { - if (cfk.isUnset()) cfk.initialize(); + cfk.preExecute(); commandsForKey.put(cfk.key(), cfk); return cfk; } @@ -803,9 +810,6 @@ public void postExecute() commandStore().commandsForRanges.tryDrainPendingEdits(); super.postExecute(); commands.values().forEach(c -> { - if (c == null || c.current() == null) - return; - Timestamp executeAt = c.current().executeAtIfKnown(); if (executeAt != null) { @@ -813,16 +817,9 @@ public void postExecute() else commandStore().commandsByExecuteAt.put(executeAt, commandStore().command(c.txnId())); } - if (c.isUnset() || c.current().saveStatus().isUninitialised()) - commandStore().commands.remove(c.txnId()); - - c.markUnsafe(); - }); - commandsForKey.values().forEach(cfk -> { - if (cfk.isUnset()) - commandStore().commandsForKey.remove(cfk.key()); - cfk.invalidate(); + c.postExecute(commandStore); }); + commandsForKey.values().forEach(safeCfk -> safeCfk.postExecute(commandStore)); } CommandSummaries commandsForRanges() @@ -889,7 +886,8 @@ public void updateExclusiveSyncPoint(Command prev, Command updated, boolean forc Participants covering = updated.participants().touches(); for (Map.Entry entry : commandStore().commands.headMap(updated.txnId(), false).entrySet()) { - Command command = entry.getValue().value(); + GlobalCommand global = entry.getValue(); + Command command = global.isLocked() ? ((InMemorySafeCommand)global.lockedBy).unsafeCurrent() : global.value(); TxnId txnId = command.txnId(); if (!command.hasBeen(Committed)) continue; if (command.hasBeen(Applied)) continue; @@ -925,7 +923,7 @@ public void updateExclusiveSyncPoint(Command prev, Command updated, boolean forc } } - protected CommandsForRangeLoad cfrLoad(PreLoadContext context) + protected CommandsForRangeLoad cfrLoad(ExecutionContext context) { if (context.loadKeysFor() != LoadKeysFor.RECOVERY) return null; @@ -991,13 +989,13 @@ public boolean inStore() } @Override - public AsyncChain chain(PreLoadContext context, Consumer consumer) + public AsyncChain chain(ExecutionContext context, Consumer consumer) { return chain(context, i -> { consumer.accept(i); return null; }); } @Override - public AsyncChain chain(PreLoadContext context, Function function) + public AsyncChain chain(ExecutionContext context, Function function) { return new AsyncChains.Head() { @@ -1055,13 +1053,13 @@ public boolean inStore() } @Override - public AsyncChain chain(PreLoadContext context, Consumer consumer) + public AsyncChain chain(ExecutionContext context, Consumer consumer) { return chain(context, i -> { consumer.accept(i); return null; }); } @Override - public AsyncChain chain(PreLoadContext context, Function function) + public AsyncChain chain(ExecutionContext context, Function function) { // TODO (expected): must unregister if chain is cancelled; should also only register when start() called CommandsForRangeLoad cfrLoad = cfrLoad(context); @@ -1086,7 +1084,7 @@ public static class Debug extends SingleThread class DebugSafeStore extends InMemorySafeStore { public DebugSafeStore(InMemoryCommandStore commandStore, - PreLoadContext context, + ExecutionContext context, CommandsForRangeLoad cfrLoad, Map commands, Map commandsForKey) @@ -1115,7 +1113,7 @@ public Debug(int id, NodeCommandStoreService time, Agent agent, DataStore store, } @Override - protected InMemorySafeStore createSafeStore(PreLoadContext context, CommandsForRangeLoad cfrLoad, Map commands, Map commandsForKeys) + protected InMemorySafeStore createSafeStore(ExecutionContext context, CommandsForRangeLoad cfrLoad, Map commands, Map commandsForKeys) { return new DebugSafeStore(this, context, cfrLoad, commands, commandsForKeys); } @@ -1252,7 +1250,7 @@ private CommandReplayer(InMemoryCommandStore commandStore) private AsyncChain apply(Command command, Replay replay) { return AsyncChains.success(commandStore.executeInContext(commandStore, - PreLoadContext.contextFor(command.txnId(), "Replay"), + ExecutionContext.unsequenced(command.txnId(), "Replay"), null, (SafeCommandStore safeStore) -> { super.replay(safeStore, command.txnId(), replay); diff --git a/accord-core/src/main/java/accord/impl/InMemorySafeCommand.java b/accord-core/src/main/java/accord/impl/InMemorySafeCommand.java index 2903c5fbb9..1def1a458f 100644 --- a/accord-core/src/main/java/accord/impl/InMemorySafeCommand.java +++ b/accord-core/src/main/java/accord/impl/InMemorySafeCommand.java @@ -18,26 +18,18 @@ package accord.impl; -import java.util.Objects; -import java.util.function.Supplier; - import javax.annotation.Nullable; import accord.impl.InMemoryCommandStore.GlobalCommand; import accord.local.Command; import accord.local.SafeCommand; +import accord.primitives.SaveStatus; import accord.primitives.TxnId; -import static accord.utils.Invariants.illegalState; - -public class InMemorySafeCommand extends SafeCommand implements SafeState +public class InMemorySafeCommand extends SafeCommand { - private static final Object INIT = new Object(); - private static final Supplier INVALIDATED = () -> null; - - private Supplier lazy; - private Object original = INIT; - private GlobalCommand global; + private final GlobalCommand global; + private boolean touched; public InMemorySafeCommand(TxnId txnId, GlobalCommand global) { @@ -45,75 +37,39 @@ public InMemorySafeCommand(TxnId txnId, GlobalCommand global) this.global = global; } - public InMemorySafeCommand(TxnId txnId, Supplier global) - { - super(txnId); - this.lazy = global; - } - - @Override - public Command current() - { - touch(); - return global.value(); - } - - public boolean isModified() + protected boolean hasChanged(Command original, Command updated) { - return original != INIT && !Objects.equals(original, global.value()); + return original != updated && updated.saveStatus != SaveStatus.Uninitialised; } @Nullable public Command original() { - touch(); - if (!isModified()) - return global.value(); - return (Command) original; + return global == null ? null : global.value(); } - @Override - protected void set(Command update) + public final void preExecute() { - touch(); - if (original == INIT) - original = global.value(); - global.value(update); + requireUninitialised(); + current = global.value(); + if (current == null) + initialise(); + global.lock(this); + setSafe(); } - @Override - public void markUnsafe() + protected void postExecute(InMemoryCommandStore commandStore) { - lazy = INVALIDATED; - original = INIT; + if (isModified()) global.value(current); + else if (global.value() == null) commandStore.commands.remove(txnId); + global.unlock(this); + setReleased(); } - @Override - public boolean isUnsafe() - { - return lazy == INVALIDATED; - } - - private void touch() - { - if (isUnsafe()) - throw illegalState("Cannot access invalidated " + this); - if (lazy != null) - { - global = lazy.get(); - lazy = null; - } - } - - GlobalCommand global() - { - touch(); - return global; - } - - @Nullable - GlobalCommand unsafeGlobal() + protected boolean touch() { - return global; + if (touched) + return false; + return touched = true; } } diff --git a/accord-core/src/main/java/accord/impl/InMemorySafeCommandsForKey.java b/accord-core/src/main/java/accord/impl/InMemorySafeCommandsForKey.java index a180579f3a..9165f56532 100644 --- a/accord-core/src/main/java/accord/impl/InMemorySafeCommandsForKey.java +++ b/accord-core/src/main/java/accord/impl/InMemorySafeCommandsForKey.java @@ -20,14 +20,13 @@ import accord.api.RoutingKey; import accord.impl.InMemoryCommandStore.GlobalCommandsForKey; -import accord.local.cfk.CommandsForKey; import accord.local.cfk.NotifySink; import accord.local.cfk.SafeCommandsForKey; public class InMemorySafeCommandsForKey extends SafeCommandsForKey { - private boolean invalidated = false; private final GlobalCommandsForKey global; + private boolean touched; public InMemorySafeCommandsForKey(RoutingKey key, GlobalCommandsForKey global) { @@ -35,18 +34,6 @@ public InMemorySafeCommandsForKey(RoutingKey key, GlobalCommandsForKey global) this.global = global; } - @Override - public CommandsForKey current() - { - return global.value(); - } - - @Override - protected void set(CommandsForKey update) - { - global.value(update); - } - @Override public void overrideSink(NotifySink overrideSink) { @@ -59,13 +46,30 @@ public NotifySink overrideSink() return global.overrideSink; } - public void invalidate() + public final void preExecute() + { + requireUninitialised(); + current = global.value(); + if (current == null) + initialize(); + global.lock(this); + setSafe(); + } + + protected void postExecute(InMemoryCommandStore commandStore) { - invalidated = true; + if (isModified()) + global.value(current); + else if (global.isEmpty()) + commandStore.commandsForKey.remove(key); + global.unlock(this); + setReleased(); } - public boolean invalidated() + protected boolean touch() { - return invalidated; + if (touched) + return false; + return touched = true; } } diff --git a/accord-core/src/main/java/accord/impl/RequestCallbacks.java b/accord-core/src/main/java/accord/impl/RequestCallbacks.java index d0ed6ee2e2..454dae8be3 100644 --- a/accord-core/src/main/java/accord/impl/RequestCallbacks.java +++ b/accord-core/src/main/java/accord/impl/RequestCallbacks.java @@ -25,6 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import accord.api.AsyncExecutor; import accord.api.VisibleForImplementation; import accord.local.Node; import accord.local.TimeService; @@ -143,8 +144,6 @@ private void unsafeOnSlow(Object ignore)

void safeInvoke(BiConsumer, P> invoker, P param) { - // TODO (expected): have executor provide inStore() function so can invoke immediately - // BUT need to be careful no callers fail if we invok to refactor a little as we cannot safely invoke callbacks before we have marked them in-flight executor.execute(() -> { try { diff --git a/accord-core/src/main/java/accord/impl/progresslog/CallbackInvoker.java b/accord-core/src/main/java/accord/impl/progresslog/CallbackInvoker.java index 80d50fb9c1..baca4963fb 100644 --- a/accord-core/src/main/java/accord/impl/progresslog/CallbackInvoker.java +++ b/accord-core/src/main/java/accord/impl/progresslog/CallbackInvoker.java @@ -22,7 +22,7 @@ import javax.annotation.Nullable; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; import accord.primitives.TxnId; @@ -30,7 +30,7 @@ import static accord.impl.progresslog.TxnStateKind.Home; import static accord.impl.progresslog.TxnStateKind.Waiting; -final class CallbackInvoker extends DefaultProgressLog.PendingTask implements BiConsumer, PreLoadContext +final class CallbackInvoker extends DefaultProgressLog.PendingTask implements BiConsumer, ExecutionContext { static CallbackInvoker invokeWaitingCallback(DefaultProgressLog instance, TxnId txnId, P param, Callback callback) { 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 2c9b764e9f..1e77e4f523 100644 --- a/accord-core/src/main/java/accord/impl/progresslog/DefaultProgressLog.java +++ b/accord-core/src/main/java/accord/impl/progresslog/DefaultProgressLog.java @@ -40,7 +40,7 @@ import accord.local.Command; import accord.local.CommandStore; import accord.local.Node; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; import accord.primitives.SaveStatus; @@ -385,7 +385,7 @@ public void clearBefore(SafeCommandStore safeStore, TxnId clearWaitingBefore, Tx { // the command might be invalidated, which should be established on load, so simply load the command TxnId txnId = state.txnId; - safeStore.commandStore().execute(PreLoadContext.contextFor(txnId, "Clear Progress"), safeStore0 -> { + safeStore.commandStore().execute(ExecutionContext.unsequenced(txnId, "Clear Progress"), safeStore0 -> { safeStore0.unsafeGet(txnId); }, node.agent()); } @@ -631,7 +631,7 @@ private void rerunWithPendingEpoch() minEpoch = Math.min(awaitingEpochBuffer[i].run.txnId.epoch(), minEpoch); Invariants.requireArgument(minEpoch != Long.MAX_VALUE); isAwaitingEpoch = true; - node.withEpochAtLeast(minEpoch, commandStore, (success, fail) -> commandStore.execute((PreLoadContext.Empty) () -> "Run ProgressLog", ss -> { + node.withEpochAtLeast(minEpoch, commandStore, (success, fail) -> commandStore.execute((ExecutionContext.Empty) () -> "Run ProgressLog", ss -> { isAwaitingEpoch = false; accept(ss); }, node.agent())); @@ -765,7 +765,7 @@ RunInvoker invoker(TxnState run, TxnStateKind runKind) return invoker; } - static final class RunInvoker extends PendingTask implements PreLoadContext, Consumer + static final class RunInvoker extends PendingTask implements ExecutionContext, Consumer { final DefaultProgressLog owner; final TxnState run; @@ -924,7 +924,7 @@ public boolean isWaitingStateActive(TxnId txnId) @VisibleForImplementation public void setMode(ModeFlag flag) { - commandStore.execute((PreLoadContext.Empty)() -> "Set ProgressLog ModeFlag", safeStore -> { + commandStore.execute((ExecutionContext.Empty)() -> "Set ProgressLog ModeFlag", safeStore -> { setModeExclusive(safeStore, flag); }); } @@ -985,7 +985,7 @@ public void maybeNotify() { long now = node.recentElapsed(MICROSECONDS); if (timers.shouldWake(now)) - commandStore.execute((PreLoadContext.Empty) () -> "Run ProgressLog", this, node.agent()); + commandStore.execute((ExecutionContext.Empty) () -> "Run ProgressLog", this, node.agent()); } } 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 800b53d365..d39794e309 100644 --- a/accord-core/src/main/java/accord/impl/progresslog/TxnState.java +++ b/accord-core/src/main/java/accord/impl/progresslog/TxnState.java @@ -23,7 +23,7 @@ import com.google.common.primitives.Ints; import accord.api.ProgressLog.BlockedUntil; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.SafeCommandStore; import accord.primitives.TxnId; import accord.utils.Invariants; @@ -32,7 +32,7 @@ import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; -public final class TxnState extends WaitingState implements PreLoadContext +public final class TxnState extends WaitingState implements ExecutionContext { public static class SerializationSupport { diff --git a/accord-core/src/main/java/accord/local/Bootstrap.java b/accord-core/src/main/java/accord/local/Bootstrap.java index 30bec0b7ec..7ea1dfcf3d 100644 --- a/accord-core/src/main/java/accord/local/Bootstrap.java +++ b/accord-core/src/main/java/accord/local/Bootstrap.java @@ -104,7 +104,7 @@ TxnId start(SafeCommandStore safeStore) if (!node.topology().active().hasAtLeastEpoch(globalSyncId.epoch())) { // Ignore timeouts fetching the epoch, always keep trying to bootstrap - node.withEpochAtLeast(globalSyncId.epoch(), null, (ignored, failure) -> commandStore.execute((PreLoadContext.Empty) () -> "Start Bootstrap", (Consumer) Attempt.this::start, (ignored1, failure2) -> { + node.withEpochAtLeast(globalSyncId.epoch(), null, (ignored, failure) -> commandStore.execute((ExecutionContext.Empty) () -> "Start Bootstrap", (Consumer) Attempt.this::start, (ignored1, failure2) -> { if (failure2 != null) node.agent().acceptAndWrap(null, failure2); })); @@ -117,14 +117,14 @@ TxnId start(SafeCommandStore safeStore) safeStore = safeStore; CommandStore commandStore = safeStore.commandStore(); CoordinateSyncPoint.exclusive(node, globalSyncId, commitRanges) - .flatMap(success -> commandStore.chain((PreLoadContext.Empty) () -> "Mark Bootstrapping", safeStore0 -> { + .flatMap(success -> commandStore.chain((ExecutionContext.Empty) () -> "Mark Bootstrapping", safeStore0 -> { // we submit a separate execution so that we know markBootstrapping is durable before we initiate the fetch if (!valid.isEmpty()) commandStore.markBootstrapping(safeStore0, globalSyncId, valid); return success; })) - .flatMap(syncPoint -> node.withEpochAtLeast(epoch, null, () -> commandStore.chain((PreLoadContext.Empty) () -> "Start Bootstrap Fetch", safeStore1 -> { + .flatMap(syncPoint -> node.withEpochAtLeast(epoch, null, () -> commandStore.chain((ExecutionContext.Empty) () -> "Start Bootstrap Fetch", safeStore1 -> { if (valid.isEmpty()) // we've lost ownership of the range return AsyncResults.success(Ranges.EMPTY); return fetch = safeStore1.dataStore().fetch(node, safeStore1, valid, syncPoint, this, Image); @@ -139,7 +139,7 @@ public void onNewFailure(Throwable failure, Ranges newlyFailed) { Runnable retry = () -> { node.scheduler().selfRecurring(() -> { - commandStore.execute((PreLoadContext.Empty) () -> "Restart Bootstrap", safeStore -> { + commandStore.execute((ExecutionContext.Empty) () -> "Restart Bootstrap", safeStore -> { restart(safeStore, newlyFailed.slice(allValid, Minimal), attempt + 1); }, commandStore.agent()); }, 0L, TimeUnit.NANOSECONDS); @@ -174,7 +174,7 @@ protected void complete(Ranges missing) { Runnable retry = () -> { node.scheduler().selfRecurring(() -> { - commandStore.execute((PreLoadContext.Empty) () -> "Restart Bootstrap", safeStore -> { + commandStore.execute((ExecutionContext.Empty) () -> "Restart Bootstrap", safeStore -> { restart(safeStore, missing, attempt + 1); }, node.agent()); }, 0L, TimeUnit.NANOSECONDS); diff --git a/accord-core/src/main/java/accord/local/Catchup.java b/accord-core/src/main/java/accord/local/Catchup.java index 72de2c9bf0..8888cc158a 100644 --- a/accord-core/src/main/java/accord/local/Catchup.java +++ b/accord-core/src/main/java/accord/local/Catchup.java @@ -99,7 +99,7 @@ private static void markWaiting(SafeCommandStore safeStore, TxnId txnId, Range r { //noinspection DataFlowIssue safeStore = safeStore; - PreLoadContext ctx = PreLoadContext.contextFor(txnId, "Catchup"); + ExecutionContext ctx = ExecutionContext.unsequenced(txnId, "Catchup"); if (safeStore.canExecuteWith(ctx)) markWaiting(safeStore, safeStore.get(txnId), range); else safeStore.commandStore().execute(ctx, (Consumer) safeStore0 -> markWaiting(safeStore0, safeStore0.get(txnId), range), safeStore.agent()); } @@ -182,7 +182,7 @@ public static AsyncChain catchup(Node node, List commandStor List> chains = new ArrayList<>(); for (CommandStore commandStore : commandStores) { - chains.add(commandStore.chain((PreLoadContext.Empty)() -> "Catchup", safeStore -> { + chains.add(commandStore.chain((ExecutionContext.Empty)() -> "Catchup", safeStore -> { CommandStoreListener listener = new CommandStoreListener(durableBefore); if (listener.register(safeStore)) return listener; diff --git a/accord-core/src/main/java/accord/local/CatchupHard.java b/accord-core/src/main/java/accord/local/CatchupHard.java index 7697c3dd2c..f4ab99a1eb 100644 --- a/accord-core/src/main/java/accord/local/CatchupHard.java +++ b/accord-core/src/main/java/accord/local/CatchupHard.java @@ -31,7 +31,7 @@ import accord.api.DataStore.FetchRanges; import accord.coordinate.FetchDurableBefore; -import accord.local.PreLoadContext.Empty; +import accord.local.ExecutionContext.Empty; import accord.primitives.Range; import accord.primitives.Ranges; import accord.primitives.SaveStatus; @@ -142,7 +142,7 @@ private static AsyncChain maybeExecuteBounds(CommandStore commandStore, Co List> chains = new ArrayList<>(bounds.size()); for (TxnId txnId : bounds) { - chains.add(commandStore.chain(PreLoadContext.contextFor(txnId,"Mark CatchupHard bounds applied"), safeStore -> { + chains.add(commandStore.chain(ExecutionContext.unsequenced(txnId, "Mark CatchupHard bounds applied"), safeStore -> { SafeCommand safeCommand = safeStore.get(txnId); Command command = safeCommand.current(); if (command.saveStatus() == SaveStatus.PreApplied) diff --git a/accord-core/src/main/java/accord/local/CommandStore.java b/accord-core/src/main/java/accord/local/CommandStore.java index a07a65599b..c105a77be8 100644 --- a/accord-core/src/main/java/accord/local/CommandStore.java +++ b/accord-core/src/main/java/accord/local/CommandStore.java @@ -35,6 +35,7 @@ import java.util.function.Supplier; import javax.annotation.Nullable; +import accord.api.ExclusiveAsyncExecutor; import accord.impl.AbstractReplayer; import accord.primitives.*; import com.google.common.annotations.VisibleForTesting; @@ -54,7 +55,7 @@ import accord.local.CommandStores.BootstrapRangeAction; import accord.local.CommandStores.RangesForEpoch; import accord.local.Commands.NotifyWaitingOnPlus; -import accord.local.PreLoadContext.Empty; +import accord.local.ExecutionContext.Empty; import accord.local.RedundantBefore.Bounds; import accord.local.RedundantStatus.SomeStatus; import accord.primitives.Status.Durability.HasOutcome; @@ -99,7 +100,7 @@ /** * Single threaded internal shard of accord transaction metadata */ -public abstract class CommandStore implements AbstractAsyncExecutor, SequentialAsyncExecutor +public abstract class CommandStore implements AbstractAsyncExecutor, ExclusiveAsyncExecutor { private static final Logger logger = LoggerFactory.getLogger(CommandStore.class); @@ -271,35 +272,25 @@ public boolean tryExecuteImmediately(Runnable run) return true; } - public abstract AsyncChain chain(PreLoadContext context, Consumer consumer); - public abstract AsyncChain chain(PreLoadContext context, Function apply); + public abstract AsyncChain chain(ExecutionContext context, Consumer consumer); + public abstract AsyncChain chain(ExecutionContext context, Function apply); - public AsyncChain priorityChain(PreLoadContext context, Consumer consumer) - { - return chain(context, consumer); - } - - public AsyncChain priorityChain(PreLoadContext context, Function function) - { - return chain(context, function); - } - - public Cancellable execute(PreLoadContext context, Consumer consumer, BiConsumer callback) + public Cancellable execute(ExecutionContext context, Consumer consumer, BiConsumer callback) { return chain(context, consumer).begin(callback); } - public AsyncResult execute(PreLoadContext context, Consumer consumer) + public AsyncResult execute(ExecutionContext context, Consumer consumer) { return chain(context, consumer).beginAsResult(); } - public Cancellable execute(PreLoadContext context, Function apply, BiConsumer callback) + public Cancellable execute(ExecutionContext context, Function apply, BiConsumer callback) { return chain(context, apply).begin(callback); } - public AsyncResult submit(PreLoadContext context, Function apply) + public AsyncResult submit(ExecutionContext context, Function apply) { return chain(context, apply).beginAsResult(); } @@ -1096,7 +1087,7 @@ private void tryExecuteListening(SafeCommandStore safeStore, Iterator ite try { TxnId waitingOn = iterator.next(); - PreLoadContext context = PreLoadContext.contextFor(waitingOn, "Try Execute Listening"); + ExecutionContext context = ExecutionContext.unsequenced(waitingOn, "Try Execute Listening"); if (!safeStore.canExecuteWith(context) || !safeStore.tryRecurse()) { //noinspection DataFlowIssue diff --git a/accord-core/src/main/java/accord/local/CommandStores.java b/accord-core/src/main/java/accord/local/CommandStores.java index 168b7df42c..ef9ce1a441 100644 --- a/accord-core/src/main/java/accord/local/CommandStores.java +++ b/accord-core/src/main/java/accord/local/CommandStores.java @@ -41,6 +41,7 @@ import accord.api.Agent; import accord.api.AsyncExecutorFactory; import accord.api.AsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.api.VisibleForImplementation; import accord.topology.EpochReady; import accord.api.DataStore; @@ -967,7 +968,7 @@ private synchronized TopologyUpdate updateTopology(Node node, Snapshot prev, Top RangesForEpoch rangesForEpoch = new RangesForEpoch(epoch, addRanges); ShardHolder shard = new ShardHolder(supplier.create(nextId++, rangesForEpoch), previouslyOwned.regains(addRanges)); shard.ranges = rangesForEpoch; - bootstrapUpdates.add(() -> EpochReady.all(epoch, shard.store.execute((PreLoadContext.Empty)() -> "Saving RangesForEpoch to journal for " + shard.store, safeStore -> { + bootstrapUpdates.add(() -> EpochReady.all(epoch, shard.store.execute((ExecutionContext.Empty)() -> "Saving RangesForEpoch to journal for " + shard.store, safeStore -> { safeStore.setRangesForEpoch(rangesForEpoch); // to persist it }))); @@ -1278,11 +1279,11 @@ public void shutdown() @Override public AsyncExecutor someExecutor() { - return someSequentialExecutor(); + return someExclusiveExecutor(); } @Override - public SequentialAsyncExecutor someSequentialExecutor() + public ExclusiveAsyncExecutor someExclusiveExecutor() { return any(); } diff --git a/accord-core/src/main/java/accord/local/CommandSummaries.java b/accord-core/src/main/java/accord/local/CommandSummaries.java index cc1a160780..d1c1de9807 100644 --- a/accord-core/src/main/java/accord/local/CommandSummaries.java +++ b/accord-core/src/main/java/accord/local/CommandSummaries.java @@ -257,7 +257,7 @@ public interface Factory private TxnId maxRx = TxnId.MAX; // a cached summary of minVisitedFutureRX to avoid consulting the full collection // TODO (expected): provide executeAt to PreLoadContext so we can more aggressively filter what we load, esp. by Kind - public static SummaryLoader loader(RedundantBefore redundantBefore, MaxDecidedRX maxDecidedRX, PreLoadContext context) + public static SummaryLoader loader(RedundantBefore redundantBefore, MaxDecidedRX maxDecidedRX, ExecutionContext context) { return loader(redundantBefore, maxDecidedRX, context.primaryTxnId(), context.executeAt(), context.loadKeysFor(), context.keys()); } @@ -267,7 +267,7 @@ public static SummaryLoader loader(RedundantBefore redundantBefore, MaxDecidedRX return loader(redundantBefore, maxDecidedRX, primaryTxnId, executeAt, loadKeysFor, keysOrRanges, SummaryLoader::new); } - public static L loader(RedundantBefore redundantBefore, MaxDecidedRX maxDecidedRX, PreLoadContext context, Factory factory) + public static L loader(RedundantBefore redundantBefore, MaxDecidedRX maxDecidedRX, ExecutionContext context, Factory factory) { return loader(redundantBefore, maxDecidedRX, context.primaryTxnId(), context.executeAt(), context.loadKeysFor(), context.keys(), factory); } diff --git a/accord-core/src/main/java/accord/local/Commands.java b/accord-core/src/main/java/accord/local/Commands.java index 78fa9f5f21..136c8533ca 100644 --- a/accord-core/src/main/java/accord/local/Commands.java +++ b/accord-core/src/main/java/accord/local/Commands.java @@ -90,10 +90,8 @@ import static accord.local.Commands.Validated.UPDATE_TXN_KEEP_DEPS; import static accord.local.Commands.Validated.UPDATE_TXN_AND_DEPS; import static accord.local.Commands.Validated.UPDATE_TXN_MERGE_DEPS; -import static accord.local.LoadKeys.INCR; -import static accord.local.LoadKeys.SYNC; -import static accord.local.LoadKeysFor.WRITE; -import static accord.local.PreLoadContext.contextFor; +import static accord.local.ExecutionContext.unsequencedIncrementalWrite; +import static accord.local.LoadKeys.ASYNC; import static accord.local.RedundantStatus.Property.LOCALLY_APPLIED; import static accord.local.RedundantStatus.Property.LOCALLY_DEFUNCT; import static accord.local.RedundantStatus.Property.LOCALLY_REDUNDANT; @@ -681,7 +679,7 @@ public static void postApply(SafeCommandStore safeStore, TxnId txnId, boolean fo safeStore.notifyListeners(safeCommand, command); } - private static class PostApply extends AsyncChains.FlatMapLink implements Consumer, PreLoadContext + private static class PostApply extends AsyncChains.FlatMapLink implements Consumer, ExecutionContext { final CommandStore commandStore; final TxnId txnId; @@ -700,7 +698,7 @@ protected PostApply(Head head, CommandStore commandStore, TxnId txnId, Partic @Override public AsyncChain apply(V v) { - return commandStore.priorityChain(this, this); + return commandStore.chain(this, this); } @Override @@ -711,11 +709,12 @@ public void accept(SafeCommandStore safeStore) @Override public TxnId primaryTxnId() { return txnId; } @Override public Unseekables keys() { return participants; } - @Override public LoadKeys loadKeys() { return SYNC; } + @Override public LoadKeys loadKeys() { return ASYNC; } @Override public String reason() { return "Post Apply"; } + @Override public ExecutionKind executionKind() { return ExecutionKind.APPLY; } } - private static class PostFastApply extends AsyncChains.FlatMapLink implements Consumer, PreLoadContext + private static class PostFastApply extends AsyncChains.FlatMapLink implements Consumer, ExecutionContext { final CommandStore commandStore; final TxnId txnId; @@ -740,7 +739,7 @@ protected PostFastApply(Head head, CommandStore commandStore, TxnId txnId, Pa @Override public AsyncChain apply(V v) { - return commandStore.priorityChain(this, this); + return commandStore.chain(this, this); } @Override @@ -762,11 +761,12 @@ public void accept(SafeCommandStore safeStore) @Override public TxnId primaryTxnId() { return txnId; } @Override public Unseekables keys() { return participants; } - @Override public LoadKeys loadKeys() { return SYNC; } + @Override public LoadKeys loadKeys() { return ASYNC; } @Override public String reason() { return "Post Apply"; } + @Override public ExecutionKind executionKind() { return ExecutionKind.APPLY; } } - public static AsyncChain applyChain(SafeCommandStore safeStore, Command command) + public static AsyncChain applyChain(SafeCommandStore safeStore, Command.Executed command) { // TODO (required): make sure we are correctly handling (esp. C* side with validation logic) executing a transaction // that was pre-bootstrap for some range (so redundant and we may have gone ahead of), but had to be executed locally @@ -857,7 +857,11 @@ public static boolean maybeExecute(SafeCommandStore safeStore, SafeCommand safeC { default: throw UnhandledEnum.invalid(command.status()); case Stable: - if (executeAtReplica(txnId, command.partialTxn()) && !command.participants().executes().isEmpty() && safeStore.safeToReadAt(command.executeAt()).containsAll(command.participants().executes())) + if (executeAtReplica(txnId, command.partialTxn()) + && !command.participants().executes().isEmpty() + && safeStore.safeToReadAt(command.executeAt()).containsAll(command.participants().executes()) + && safeStore.commandStore().node().topology().epoch() >= command.executeAt.epoch() + ) { if (null == (command = replicaExecute(safeStore, safeCommand, command, txnId))) break; @@ -959,7 +963,7 @@ private static void replicaExecuteFastApply(CommandStore unsafeStore, Ballot bal private static void replicaExecuteSlowApply(CommandStore unsafeStore, Ballot ballot, TxnId txnId, Route route, PartialTxn txn, Data data, Timestamp applyAt, long stamp) { - unsafeStore.execute(PreLoadContext.contextFor(txnId, "Replica Apply"), safeStore -> { + unsafeStore.execute(ExecutionContext.unsequenced(txnId, "Replica Apply"), safeStore -> { SafeCommand safeCommand = safeStore.unsafeGet(txnId); Command command = safeCommand.current(); if (stamp != unsafeStore.node.currentStamp() && !safeStore.safeToReadAt(applyAt).containsAll(command.route())) @@ -983,7 +987,7 @@ private static void replicaExecuteSlowApply(CommandStore unsafeStore, Ballot bal private static void notifyAfterFailedFastApply(CommandStore unsafeStore, TxnId txnId) { - unsafeStore.execute(PreLoadContext.contextFor(txnId, "Mark ReadyToExecute after failure to fast apply"), safeStore -> { + unsafeStore.execute(ExecutionContext.unsequenced(txnId, "Mark ReadyToExecute after failure to fast apply"), safeStore -> { notifyAfterFailedFastApply(safeStore, txnId); }, unsafeStore.agent()); } @@ -1017,14 +1021,14 @@ protected static Update updateWaitingOn(SafeCommandStore safeStore, MinimalWithC // 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)); - if (dep == null || dep.isUnset() || !dep.current().hasBeen(PreCommitted)) + if (dep == null || !dep.current().hasBeen(PreCommitted)) return; updateWaitingOn(store, w, exec, upd, dep); }); initialise.forEachWaitingOnKey(safeStore, initialise, waiting, (store, upd, cmd, i) -> { SafeCommandsForKey safeCfk = store.ifLoadedAndInitialised(upd.keys.get(i)); - if (safeCfk == null || safeCfk.isUnset()) + if (safeCfk == null) return; if (safeCfk.current().hasUniqueHlcAndIsReadyToExecute(cmd.txnId(), cmd.executeAt(), cmd.partialDeps())) @@ -1342,8 +1346,8 @@ public static Command setDurability(SafeCommandStore safeStore, SafeCommand safe if (updates.compareTo(dependencyElision()) >= 0 && CommandsForKey.manages(txnId)) { AbstractUnseekableKeys keys = (AbstractUnseekableKeys)updated.participants().touches(); - PreLoadContext context = PreLoadContext.contextFor(keys, INCR, WRITE, "Set Durable"); - PreLoadContext execute = safeStore.canExecute(context); + ExecutionContext context = ExecutionContext.unsequencedIncrementalWrite(keys, "Set Durable"); + ExecutionContext execute = safeStore.canExecute(context); if (execute != null) { setDurable(safeStore, execute, txnId, newDurability); @@ -1351,7 +1355,7 @@ public static Command setDurability(SafeCommandStore safeStore, SafeCommand safe if (execute != context) { if (execute != null) - context = contextFor(keys.without(execute.keys()), INCR, WRITE, "Set Durable"); + context = unsequencedIncrementalWrite(keys.without(execute.keys()), "Set Durable"); Invariants.require(!context.keys().isEmpty()); safeStore = safeStore; // prevent accidental usage inside lambda @@ -1368,13 +1372,13 @@ public static Command setDurability(SafeCommandStore safeStore, SafeCommand safe return updated; } - private static void setDurable(SafeCommandStore safeStore, PreLoadContext context, TxnId txnId, Durability durability) + private static void setDurable(SafeCommandStore safeStore, ExecutionContext context, TxnId txnId, Durability durability) { for (RoutingKey key : (AbstractUnseekableKeys)context.keys()) safeStore.get(key).setDurable(txnId, durability); } - static class NotifyWaitingOn implements PreLoadContext, Consumer + static class NotifyWaitingOn implements ExecutionContext, Consumer { final TxnId waitingId; TxnId loadDepId; @@ -1610,6 +1614,12 @@ public TxnId additionalTxnId() { return loadDepId; } + + @Override + public ExecutionSequence executionSequence() + { + return ExecutionSequence.UNSEQUENCED; + } } public static class NotifyWaitingOnPlus extends NotifyWaitingOn implements MaybeExecuteAdapter diff --git a/accord-core/src/main/java/accord/local/PreLoadContext.java b/accord-core/src/main/java/accord/local/ExecutionContext.java similarity index 59% rename from accord-core/src/main/java/accord/local/PreLoadContext.java rename to accord-core/src/main/java/accord/local/ExecutionContext.java index 9d2d443121..e6a90c3651 100644 --- a/accord-core/src/main/java/accord/local/PreLoadContext.java +++ b/accord-core/src/main/java/accord/local/ExecutionContext.java @@ -18,13 +18,8 @@ package accord.local; -import accord.api.RoutingKey; -import accord.api.VisibleForImplementation; import accord.local.cfk.CommandsForKey; -import accord.primitives.AbstractUnseekableKeys; import accord.primitives.Ranges; -import accord.primitives.Routable; -import accord.primitives.Routables; import accord.primitives.Routables.Slice; import accord.primitives.RoutingKeys; import accord.primitives.Timestamp; @@ -39,8 +34,10 @@ import java.util.function.Consumer; import javax.annotation.Nullable; +import static accord.local.LoadKeys.INCR; import static accord.local.LoadKeys.NONE; import static accord.local.LoadKeys.SYNC; +import static accord.local.LoadKeysFor.READ_WRITE; import static accord.local.LoadKeysFor.WRITE; /** @@ -49,8 +46,38 @@ * * TODO (desired): rename to simply Context, or LoadContext */ -public interface PreLoadContext +public interface ExecutionContext { + enum ExecutionKind + { + PREACCEPT, + ACCEPT, + COMMIT, + STABLE, + APPLY, + OTHER, + } + + enum ExecutionSequence + { + /** + * The task may run as soon as it is ready, without any regard to ordering on other tasks on the same keys. + */ + UNSEQUENCED, + + /** + * The task is ordered with respect to other tasks' priorities, but if the task is INCR each batch may + * interleave with other work on those keys. + */ + BY_PRIORITY, + + /** + * Appears to be processed "atomically" with the task that submits it, with respect to other tasks. + * Meaningful only when submitted by an already running task. + */ + ATOMIC_CONSEQUENCE; + } + @Nullable TxnId primaryTxnId(); String reason(); @@ -61,9 +88,13 @@ public interface PreLoadContext * * TODO (expected): this is used for Apply, NotifyWaitingOn and listenerContexts; others only use a single txnId * The information we need in memory is super minimal for secondary transactions (mostly just SaveStatus?). + * + * NOTE: this currently can change during execution for NotifyWaitingOn. + * This should not be treated as readable after execution is started. */ default @Nullable TxnId additionalTxnId() { return null; } + // TODO (desired): minimise call-sites, or see if hotspot can optimise this effectively default List txnIds() { TxnId primaryTxnId = primaryTxnId(); @@ -96,7 +127,7 @@ default void forEachId(Consumer consumer) consumer.accept(additionalTxnId); } - default PreLoadContext slice(Ranges ranges, Slice slice) + default ExecutionContext slice(Ranges ranges, Slice slice) { Unseekables keys = keys(); int size = keys.size(); @@ -119,6 +150,10 @@ default PreLoadContext slice(Ranges ranges, Slice slice) default LoadKeysFor loadKeysFor() { return WRITE; } + default ExecutionKind executionKind() { return ExecutionKind.OTHER; } + + default ExecutionSequence executionSequence() { return ExecutionSequence.BY_PRIORITY; } + default Timestamp executeAt() { return primaryTxnId(); } default boolean isEmpty() @@ -134,7 +169,7 @@ default boolean isEmpty() * not whether a subset has been requested - that is, a superset with INCR or ASYNC key information * cannot be relied upon for serving INCR or ASYNC subsets in this calculation. */ - default boolean isSubsetOf(PreLoadContext superset) + default boolean isSubsetOf(ExecutionContext superset) { Unseekables keys = keys(); if (!keys.isEmpty()) @@ -155,19 +190,20 @@ default boolean isSubsetOf(PreLoadContext superset) return false; } - TxnId primaryId = primaryTxnId(); - TxnId additionalId = additionalTxnId(); - if (additionalId == null) - { - return primaryId == null || primaryId.equals(superset.primaryTxnId()) || primaryId.equals(superset.additionalTxnId()); - } - else - { - Invariants.require(primaryId != null); - TxnId supersetPrimaryId = superset.primaryTxnId(); - TxnId supersetAdditionalId = superset.additionalTxnId(); - return (primaryId.equals(supersetPrimaryId) || primaryId.equals(supersetAdditionalId)) && (additionalId.equals(supersetAdditionalId) || additionalId.equals(supersetPrimaryId)); - } + return isTxnIdSubsetOf(superset); + } + + default boolean isTxnIdSubsetOf(ExecutionContext txnIdSuperset) + { + TxnId primaryTxnId = primaryTxnId(); + if (primaryTxnId == null) + return true; + + if (!primaryTxnId.equals(txnIdSuperset.primaryTxnId())) + return false; + + TxnId additionalTxnId = additionalTxnId(); + return additionalTxnId == null || additionalTxnId.equals(txnIdSuperset.additionalTxnId()); } default String describe() @@ -177,16 +213,18 @@ default String describe() return reason() + (txnIds.isEmpty() ? "" : " for " + txnIds) + (keys.isEmpty() ? "" : (txnIds.isEmpty() ? " for " : " and ") + keys()); } - class Wrapped implements PreLoadContext + class Wrapped implements ExecutionContext { - final PreLoadContext wrapped; - public Wrapped(PreLoadContext wrapped) + final ExecutionContext wrapped; + public Wrapped(ExecutionContext wrapped) { this.wrapped = wrapped; } @Nullable @Override public TxnId primaryTxnId() { return wrapped.primaryTxnId(); } @Nullable @Override public TxnId additionalTxnId() { return wrapped.additionalTxnId(); } @Override public Unseekables keys() { return wrapped.keys(); } + @Override public ExecutionSequence executionSequence() { return wrapped.executionSequence(); } + @Override public ExecutionKind executionKind() { return wrapped.executionKind(); } @Override public LoadKeys loadKeys() { return wrapped.loadKeys(); } @Override public LoadKeysFor loadKeysFor() { return wrapped.loadKeysFor(); } @Override public Timestamp executeAt() { return wrapped.executeAt(); } @@ -197,7 +235,7 @@ public Wrapped(PreLoadContext wrapped) class OverrideKeys extends Wrapped { final Unseekables keys; - public OverrideKeys(PreLoadContext wrapped, Unseekables keys) + public OverrideKeys(ExecutionContext wrapped, Unseekables keys) { super(wrapped); this.keys = keys; @@ -206,10 +244,10 @@ public OverrideKeys(PreLoadContext wrapped, Unseekables keys) @Override public Unseekables keys() { return keys; } } - static PreLoadContext contextFor(@Nullable TxnId primary, @Nullable TxnId additional, Unseekables keys, LoadKeys loadKeys, LoadKeysFor loadKeysFor, String reason) + static ExecutionContext contextFor(@Nullable TxnId primary, @Nullable TxnId additional, Unseekables keys, LoadKeys loadKeys, LoadKeysFor loadKeysFor, String reason) { Invariants.require(primary == null ? additional == null : !primary.equals(additional)); - return new PreLoadContext() + return new ExecutionContext() { @Override public @Nullable TxnId primaryTxnId() { return primary; } @Override public @Nullable TxnId additionalTxnId() { return additional; } @@ -227,61 +265,109 @@ default boolean contains(TxnId txnId) return primaryTxnId != null && (txnId.equals(primaryTxnId) || txnId.equals(additionalTxnId())); } - static PreLoadContext contextFor(TxnId primary, TxnId additional, String reason) + static ExecutionContext unsequenced(TxnId primary, TxnId additional, String reason) { - return new PreLoadContext() + return new ExecutionContext() { @Override public @Nullable TxnId primaryTxnId() { return primary; } @Override public @Nullable TxnId additionalTxnId() { return additional; } + @Override public @Nullable ExecutionSequence executionSequence() { return ExecutionSequence.UNSEQUENCED; } @Override public String reason() { return reason; } @Override public String toString() { return describe(); } }; } - static PreLoadContext contextFor(TxnId primary, String reason) + static ExecutionContext unsequenced(TxnId primary, String reason) { - return new PreLoadContext() + return new ExecutionContext() { @Override public @Nullable TxnId primaryTxnId() { return primary; } @Override public String reason() { return reason; } + @Override public @Nullable ExecutionSequence executionSequence() { return ExecutionSequence.UNSEQUENCED; } @Override public String toString() { return describe(); } }; } - static PreLoadContext contextFor(TxnId txnId, Unseekables keys, LoadKeys loadKeys, LoadKeysFor loadKeysFor, String reason) + static ExecutionContext atomicIncrementalWrite(TxnId txnId, Unseekables keys, String reason) { - return new PreLoadContext() + return new ExecutionContext() { @Override public @Nullable TxnId primaryTxnId() { return txnId; } @Override public Unseekables keys() { return keys; } - @Override public LoadKeys loadKeys() { return loadKeys; } - @Override public LoadKeysFor loadKeysFor() { return loadKeysFor; } + @Override public LoadKeys loadKeys() { return INCR; } + @Override public ExecutionSequence executionSequence() { return ExecutionSequence.ATOMIC_CONSEQUENCE; } @Override public String reason() { return reason; } @Override public String toString() { return describe(); } }; } - static PreLoadContext contextFor(RoutingKey key, LoadKeys loadKeys, LoadKeysFor loadKeysFor, String describe) + static ExecutionContext incrementalWrite(TxnId txnId, Unseekables keys, String reason) { - return contextFor(RoutingKeys.of(key), loadKeys, loadKeysFor, describe); + return new ExecutionContext() + { + @Override public @Nullable TxnId primaryTxnId() { return txnId; } + @Override public Unseekables keys() { return keys; } + @Override public LoadKeys loadKeys() { return INCR; } + @Override public String reason() { return reason; } + @Override public String toString() { return describe(); } + }; } - // we don't currently permit range queries without an associated TxnId - static PreLoadContext contextFor(AbstractUnseekableKeys keys, LoadKeys loadKeys, LoadKeysFor loadKeysFor, String reason) + static ExecutionContext unsequencedIncrementalWrite(Unseekables keys, String reason) { - Invariants.require(keys.domain() == Routable.Domain.Key); - return new PreLoadContext() + return new ExecutionContext() { @Override public @Nullable TxnId primaryTxnId() { return null; } @Override public Unseekables keys() { return keys; } - @Override public LoadKeys loadKeys() { return loadKeys; } - @Override public LoadKeysFor loadKeysFor() { return loadKeysFor; } + @Override public LoadKeys loadKeys() { return INCR; } + @Override public ExecutionSequence executionSequence() { return ExecutionSequence.UNSEQUENCED; } + @Override public String reason() { return reason; } + @Override public String toString() { return describe(); } + }; + } + + static ExecutionContext unsequencedWrite(TxnId txnId, Unseekables keys, String reason) + { + return new ExecutionContext() + { + @Override public @Nullable TxnId primaryTxnId() { return txnId; } + @Override public Unseekables keys() { return keys; } + @Override public LoadKeys loadKeys() { return SYNC; } + @Override public ExecutionSequence executionSequence() { return ExecutionSequence.UNSEQUENCED; } + @Override public String reason() { return reason; } + @Override public String toString() { return describe(); } + }; + } + + static ExecutionContext unsequencedReadWrite(TxnId txnId, Unseekables keys, String reason) + { + return new ExecutionContext() + { + @Override public @Nullable TxnId primaryTxnId() { return txnId; } + @Override public Unseekables keys() { return keys; } + @Override public LoadKeys loadKeys() { return SYNC; } + @Override public LoadKeysFor loadKeysFor() { return READ_WRITE; } + @Override public ExecutionSequence executionSequence() { return ExecutionSequence.UNSEQUENCED; } + @Override public String reason() { return reason; } + @Override public String toString() { return describe(); } + }; + } + + static ExecutionContext unsequencedReadWrite(Unseekables keys, String reason) + { + return new ExecutionContext() + { + @Override public @Nullable TxnId primaryTxnId() { return null; } + @Override public Unseekables keys() { return keys; } + @Override public LoadKeys loadKeys() { return SYNC; } + @Override public LoadKeysFor loadKeysFor() { return READ_WRITE; } + @Override public ExecutionSequence executionSequence() { return ExecutionSequence.UNSEQUENCED; } @Override public String reason() { return reason; } @Override public String toString() { return describe(); } }; } - interface Empty extends PreLoadContext + interface Empty extends ExecutionContext { @Override default @Nullable TxnId primaryTxnId() { return null; } } diff --git a/accord-core/src/main/java/accord/local/LoadKeys.java b/accord-core/src/main/java/accord/local/LoadKeys.java index be1263aa93..8de1d782d4 100644 --- a/accord-core/src/main/java/accord/local/LoadKeys.java +++ b/accord-core/src/main/java/accord/local/LoadKeys.java @@ -36,8 +36,9 @@ public enum LoadKeys /** * Load and process the requested keys incrementally; the operation will be invoked multiples times - * as keys are loaded, until all the keys have been processed. The keys to be processed must be loaded - * into memory + * as keys are loaded, until all the keys have been processed. If submitted by an already running execution + * this task must declare a subset of the keys and txnIds declared by the originating task. + * It is not permitted to chain INCR tasks together; INCR may only be submitted by an ASYNC or SYNC task. */ INCR, diff --git a/accord-core/src/main/java/accord/local/MapReduceCommandStores.java b/accord-core/src/main/java/accord/local/MapReduceCommandStores.java index b9534764f0..008cd02e11 100644 --- a/accord-core/src/main/java/accord/local/MapReduceCommandStores.java +++ b/accord-core/src/main/java/accord/local/MapReduceCommandStores.java @@ -29,7 +29,7 @@ import static accord.primitives.Routables.Slice.Minimal; -public abstract class MapReduceCommandStores

, O> implements PreLoadContext, MapReduce +public abstract class MapReduceCommandStores

, O> implements ExecutionContext, MapReduce { public final P scope; private Tracing tracing; diff --git a/accord-core/src/main/java/accord/local/Node.java b/accord-core/src/main/java/accord/local/Node.java index 785fe9c7e6..e1e82e602b 100644 --- a/accord-core/src/main/java/accord/local/Node.java +++ b/accord-core/src/main/java/accord/local/Node.java @@ -27,7 +27,6 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -36,6 +35,7 @@ import accord.api.Agent; import accord.api.AsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.api.TopologyService; import accord.api.Tracing; import accord.coordinate.ExecuteTxn; @@ -71,7 +71,6 @@ import accord.coordinate.Outcome; import accord.coordinate.PrepareRecovery; import accord.local.CommandStores.LatentStoreSelector; -import accord.local.CommandStores.StoreSelector; import accord.local.cfk.CommandsForKey; import accord.local.durability.DurabilityService; import accord.messages.Callback; @@ -469,9 +468,9 @@ public AsyncExecutor someExecutor() } @Override - public SequentialAsyncExecutor someSequentialExecutor() + public ExclusiveAsyncExecutor someExclusiveExecutor() { - return commandStores.someSequentialExecutor(); + return commandStores.someExclusiveExecutor(); } public void shutdown() @@ -793,7 +792,7 @@ private RoutingKey selectHomeKey(ActiveEpoch e, Routables keysOrRanges) public AsyncChain recover(TxnId txnId, InvalidIf invalidIf, FullRoute route, LatentStoreSelector reportTo) { - SequentialAsyncExecutor executor = someSequentialExecutor(); + ExclusiveAsyncExecutor executor = someExclusiveExecutor(); return withEpochExact(txnId.epoch(), executor, () -> new AsyncChains.Head<>() { @Override diff --git a/accord-core/src/main/java/accord/local/SafeCommand.java b/accord-core/src/main/java/accord/local/SafeCommand.java index adee9b028d..3946b7a2d1 100644 --- a/accord-core/src/main/java/accord/local/SafeCommand.java +++ b/accord-core/src/main/java/accord/local/SafeCommand.java @@ -37,27 +37,16 @@ import static accord.local.StoreParticipants.Filter.LOAD; import static accord.primitives.Status.Stable; -public abstract class SafeCommand +public abstract class SafeCommand extends SafeState { - private final TxnId txnId; + public final TxnId txnId; public SafeCommand(TxnId txnId) { this.txnId = txnId; } - public abstract Command current(); - public abstract void markUnsafe(); - public abstract boolean isUnsafe(); - - public boolean isUnset() - { - return current() == null; - } - - protected abstract void set(Command command); - - public TxnId txnId() + public final TxnId txnId() { return txnId; } @@ -193,18 +182,10 @@ public Command.Executed applied(SafeCommandStore safeStore, @Nonnull StorePartic return update(safeStore, Command.applied(current(), participants, executeAt, partialTxn, partialDeps, waitingOn, writes, result)); } - public Command.NotDefined uninitialised() + protected void initialise() { - Invariants.requireArgument(current() == null); - return incidentalUpdate(Command.NotDefined.uninitialised(txnId)); - } - - public Command initialise() - { - Command current = current(); - if (!current.saveStatus().isUninitialised()) - return current; - return incidentalUpdate(Command.NotDefined.notDefined(current, current.promised())); + Invariants.require(isUninitialised()); + current = Command.NotDefined.uninitialised(txnId); } public static @Nullable Participants maxParticipants(@Nullable SafeCommand safeCommand) diff --git a/accord-core/src/main/java/accord/local/SafeCommandStore.java b/accord-core/src/main/java/accord/local/SafeCommandStore.java index c4de4314d7..d74860d4c9 100644 --- a/accord-core/src/main/java/accord/local/SafeCommandStore.java +++ b/accord-core/src/main/java/accord/local/SafeCommandStore.java @@ -57,9 +57,9 @@ import accord.utils.async.AsyncChain; import accord.utils.async.AsyncChains; -import static accord.local.LoadKeys.INCR; +import static accord.local.ExecutionContext.atomicIncrementalWrite; +import static accord.local.ExecutionContext.unsequencedIncrementalWrite; import static accord.local.LoadKeys.NONE; -import static accord.local.LoadKeysFor.WRITE; import static accord.local.RedundantStatus.Property.LOCALLY_APPLIED; import static accord.local.RedundantStatus.SomeStatus.LOCALLY_WITNESSED_ONLY; import static accord.local.RedundantStatus.Property.LOCALLY_REDUNDANT; @@ -228,7 +228,7 @@ public SafeCommand ifLoadedAndInitialised(TxnId txnId) return null; } - if (safeCommand.isUnset() || safeCommand.current().saveStatus() == Uninitialised) + if (safeCommand.isUninitialised() || safeCommand.current().saveStatus() == Uninitialised) return null; return maybeCleanup(safeCommand); @@ -251,12 +251,17 @@ protected SafeCommandsForKey maybeCleanup(SafeCommandsForKey safeCfk) public final SafeCommandsForKey ifLoadedAndInitialised(RoutingKey key) { SafeCommandsForKey safeCfk = getInternal(key); - if (safeCfk != null) - return safeCfk; - - safeCfk = ifLoadedInternal(key); if (safeCfk == null) + { + safeCfk = ifLoadedInternal(key); + if (safeCfk == null) + return null; + } + else if (safeCfk.isUninitialised()) + { return null; + } + return maybeCleanup(safeCfk); } @@ -264,33 +269,37 @@ public SafeCommandsForKey get(RoutingKey key) { SafeCommandsForKey safeCfk = getInternal(key); if (safeCfk != null) + { + if (safeCfk.isUninitialised()) + return null; return maybeCleanup(safeCfk); + } if (context().loadKeys() != NONE && context().keys().contains(key)) throw illegalState("%s was specified in %s but was not returned by getInternal(key)", key, context().keys()); else throw illegalArgument("%s was not specified in %s", key, context()); } - /** Get anything already referenced (should include anything in PreLoadContext). If returned, should be initialised. */ + /** Get anything already referenced (should include anything in ExecutionContext). If returned, should be initialised. */ protected abstract SafeCommand getInternal(TxnId txnId); /** Get if available */ protected abstract SafeCommand ifLoadedInternal(TxnId txnId); - /** Get anything already referenced (should include anything in PreLoadContext) */ + /** Get anything already referenced (should include anything in ExecutionContext) */ protected abstract SafeCommandsForKey getInternal(RoutingKey key); /** Get if available */ protected abstract SafeCommandsForKey ifLoadedInternal(RoutingKey key); - public final boolean canExecuteWith(PreLoadContext context) { return canExecute(context) == context; } + public final boolean canExecuteWith(ExecutionContext context) { return canExecute(context) == context; } /** * Attempt to ready the provided PreLoadContext; if this can only be achieved partially, a new PreLoadContext * will be returned containing the readily available data. If nothing is available, null will be returned. */ - public abstract @Nullable PreLoadContext canExecute(PreLoadContext context); + public abstract @Nullable ExecutionContext canExecute(ExecutionContext context); /** * The current PreLoadContext, excluding any upgrade. */ - public abstract PreLoadContext context(); + public abstract ExecutionContext context(); protected void update(Command prev, Command updated, boolean force) { @@ -402,28 +411,49 @@ private static void updateManagedCommandsForKey(SafeCommandStore safeStore, Comm return; // TODO (expected): we don't want to insert any dependencies for those we only touch; we just need to record them as decided/applied for execution - PreLoadContext context = PreLoadContext.contextFor(next.txnId(), update, INCR, WRITE, "Update CommandsForKey"); - PreLoadContext execute = safeStore.canExecute(context); + ExecutionContext context = atomicIncrementalWrite(next.txnId(), update, "Update CommandsForKey"); + ExecutionContext execute = safeStore.canExecute(context); if (execute != null) { updateManagedCommandsForKey(safeStore, execute.keys(), next.txnId(), forceNotify); } if (execute != context) { + Unseekables remainingKeys = update; if (execute != null) - context = PreLoadContext.contextFor(next.txnId(), update.without(execute.keys()), INCR, WRITE, "Update CommandsForKey"); - - Invariants.require(!context.keys().isEmpty()); - safeStore = safeStore; // prevent accidental usage inside lambda - safeStore.commandStore().execute(context, safeStore0 -> { - PreLoadContext ctx = safeStore0.context(); - TxnId txnId = ctx.primaryTxnId(); - Unseekables keys = ctx.keys(); - updateManagedCommandsForKey(safeStore0, keys, txnId, forceNotify); - }, safeStore.commandStore().agent); + remainingKeys = remainingKeys.without(execute.keys()); + + if (participants.hasTouched() != participants.touches()) + { + // we update no-longer touched keys asynchronously so we don't need to include them when loading + Unseekables asyncKeys = remainingKeys.without(participants.touches()).intersecting(participants.hasTouched(), Minimal); + if (!asyncKeys.isEmpty()) + { + ExecutionContext async = unsequencedIncrementalWrite(asyncKeys, "Update CommandsForKey"); + updateManagedCommandsForKeyIncremental(async, safeStore.commandStore(), forceNotify); + remainingKeys = remainingKeys.without(asyncKeys); + } + } + + if (!remainingKeys.isEmpty()) + { + if (remainingKeys != update) + context = atomicIncrementalWrite(next.txnId(), remainingKeys, "Update CommandsForKey"); + updateManagedCommandsForKeyIncremental(context, safeStore.commandStore(), forceNotify); + } } } + private static void updateManagedCommandsForKeyIncremental(ExecutionContext context, CommandStore commandStore, boolean forceNotify) + { + commandStore.execute(context, safeStore -> { + ExecutionContext ctx = safeStore.context(); + TxnId txnId = ctx.primaryTxnId(); + Unseekables keys = ctx.keys(); + updateManagedCommandsForKey(safeStore, keys, txnId, forceNotify); + }, commandStore.agent); + } + private static void updateManagedCommandsForKey(SafeCommandStore safeStore, Unseekables update, TxnId txnId, boolean forceNotify) { // TODO (expected): avoid reentrancy / recursion @@ -479,8 +509,8 @@ private static void updateUnmanagedCommandsForKey(SafeCommandStore safeStore, Co } // TODO (required): use StoreParticipants.executes() // TODO (required): consider how execution works for transactions that await future deps and where the command store inherits additional keys in execution epoch - PreLoadContext context = PreLoadContext.contextFor(txnId, keys, INCR, WRITE, "Update Unmanaged CommandsForKey"); - PreLoadContext execute = safeStore.canExecute(context); + ExecutionContext context = ExecutionContext.incrementalWrite(txnId, keys, "Update Unmanaged CommandsForKey"); + ExecutionContext execute = safeStore.canExecute(context); // TODO (expected): execute immediately for any keys we already have loaded, and save only those we haven't for later if (execute != null) { @@ -496,13 +526,13 @@ private static void updateUnmanagedCommandsForKey(SafeCommandStore safeStore, Co else { if (execute != null) - context = PreLoadContext.contextFor(txnId, keys.without(execute.keys()), INCR, WRITE, "Update Unmanaged CommandsForKey"); + context = ExecutionContext.unsequencedWrite(txnId, keys.without(execute.keys()), "Update Unmanaged CommandsForKey"); safeStore = safeStore; CommandStore unsafeStore = safeStore.commandStore(); AsyncChain submit = unsafeStore.chain(context, safeStore0 -> { updateUnmanagedCommandsForKey(safeStore0, safeStore0.context().keys() , txnId, mode); }); if (registerTransitive != null) - submit = submit.flatMap(success -> unsafeStore.chain(PreLoadContext.contextFor(txnId, "Register Transitive Dependencies"), registerTransitive)); + submit = submit.flatMap(success -> unsafeStore.chain(ExecutionContext.unsequenced(txnId, "Register Transitive Dependencies"), registerTransitive)); submit.begin(safeStore.commandStore().agent); } } @@ -532,7 +562,7 @@ private static Consumer registerTransitiveRangeDeps(CommandSto RangeDeps rangeDeps = syncCommand.partialDeps().rangeDeps; rangeDeps.forEachUniqueTxnId(waitingOn, null, (ignore, txnIdWithFlags) -> { TxnId txnId = txnIdWithFlags.withoutNonIdentityFlags(); - PreLoadContext context = PreLoadContext.contextFor(txnId, "Register Transitive Range Deps"); + ExecutionContext context = ExecutionContext.unsequenced(txnId, "Register Transitive Range Deps"); Ranges ranges = rangeDeps.ranges(txnId); if (safeStore.canExecuteWith(context)) registerTransitive(safeStore, txnId, ranges); else async.add(safeStore.commandStore().chain(context, safeStore0 -> { @@ -542,11 +572,11 @@ private static Consumer registerTransitiveRangeDeps(CommandSto AsyncChains.reduce(async, Reduce.toNull(), null) .begin((success, fail) -> { - if (fail == null) commandStore.execute((PreLoadContext.Empty)() -> "Mark Synced", (Consumer) safeStore0 -> commandStore.markVisible(safeStore0, syncId, waitingOn), commandStore.agent()); + if (fail == null) commandStore.execute((ExecutionContext.Empty)() -> "Mark Synced", (Consumer) safeStore0 -> commandStore.markVisible(safeStore0, syncId, waitingOn), commandStore.agent()); else { // TODO (required): reset ensureReadyToCoordinate state - commandStore.execute((PreLoadContext.Empty)() -> "Unmark Syncing", (Consumer) safeStore0 -> commandStore.cancelMarkingVisible(syncId, waitingOn), commandStore.agent); + commandStore.execute((ExecutionContext.Empty)() -> "Unmark Syncing", (Consumer) safeStore0 -> commandStore.cancelMarkingVisible(syncId, waitingOn), commandStore.agent); } }); }; diff --git a/accord-core/src/main/java/accord/local/SafeState.java b/accord-core/src/main/java/accord/local/SafeState.java new file mode 100644 index 0000000000..5ef369432a --- /dev/null +++ b/accord-core/src/main/java/accord/local/SafeState.java @@ -0,0 +1,140 @@ +/* + * 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 static accord.utils.Invariants.require; + +/** + * State scoped to a single request that references global state + */ +public abstract class SafeState +{ + private static final byte ABANDONED_UNINITIALISED = -1; + private static final byte UNINITIALISED = 0; + private static final byte SAFE = 1; + private static final byte ABANDONED_SAFE = 2; + private static final byte RELEASED = 3; + + private byte status; + private boolean modified; + private byte extensionByte; + protected V current; + + protected boolean hasChanged(V original, V updated) { return original != updated; } + + public final V current() + { + requireSafe(); + return current; + } + + public final V unsafeCurrent() + { + return current; + } + + public final void set(V value) + { + requireSafe(); + modified |= hasChanged(current, value); + current = value; + } + + public final boolean isUninitialised() + { + return status == UNINITIALISED; + } + + public final boolean isSafe() + { + return status == SAFE; + } + + public final boolean isModified() + { + return modified; + } + + public final boolean isReleased() + { + return status == RELEASED; + } + + public final void requireUninitialised() + { + require(isUninitialised()); + } + + public final void requireSafe() + { + require(isSafe()); + } + + protected final void setSafe() + { + requireUninitialised(); + require(current != null); + status = SAFE; + } + + public final void setAbandoned() + { + require(status <= SAFE); + modified = false; + current = null; + status = status == UNINITIALISED ? ABANDONED_UNINITIALISED : ABANDONED_SAFE; + } + + public final boolean setReleased() + { + require(!isReleased()); + boolean wasLocked = status >= SAFE; + current = null; + status = RELEASED; + return wasLocked; + } + + protected final byte status() + { + return status; + } + + public final byte extensionByte() + { + return extensionByte; + } + + public final void setExtensionByte(byte extensionByte) + { + this.extensionByte = extensionByte; + } + + public final String statusString() + { + switch (status) + { + default: return "UNKNOWN"; + case UNINITIALISED: return "UNINITIALISED"; + case SAFE: return "SAFE"; + case ABANDONED_SAFE: return "ABANDONED_SAFE"; + case ABANDONED_UNINITIALISED: return "ABANDONED_UNINITIALISED"; + case RELEASED: return "RELEASED"; + } + } +} diff --git a/accord-core/src/main/java/accord/local/StoreParticipants.java b/accord-core/src/main/java/accord/local/StoreParticipants.java index 313cf066ae..49dff19f6b 100644 --- a/accord-core/src/main/java/accord/local/StoreParticipants.java +++ b/accord-core/src/main/java/accord/local/StoreParticipants.java @@ -66,8 +66,8 @@ static class FullStoreParticipants extends StoreParticipants { Invariants.requireArgument(route != null || (!Route.isRoute(owns) && !Route.isRoute(executes) && !Route.isRoute(waitsOn) && !Route.isRoute(touches) && !Route.isRoute(hasTouched))); Routable.Domain domain = owns.domain(); - Invariants.paranoid(route == null || route.containsAll(owns)); - Invariants.paranoid(touches.containsAll(owns)); + Invariants.requireArgument(route == null || route.containsAll(owns)); + Invariants.require(touches.containsAll(owns)); Invariants.requireArgument(route == null || domain == route.domain()); Invariants.requireArgument(domain == touches.domain()); Invariants.requireArgument(domain == hasTouched.domain()); 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 79e33df9bd..61fbc1fe03 100644 --- a/accord-core/src/main/java/accord/local/cfk/CommandsForKey.java +++ b/accord-core/src/main/java/accord/local/cfk/CommandsForKey.java @@ -2234,6 +2234,15 @@ public boolean equals(Object o) return Objects.equals(key, that.key) && equalContents(that); } + /** + * A quick check to see if anything we need to save may have been changed; + * ignores in-place updates that would not be serialized, and changes to QuickBounds + */ + public boolean hasChanges(CommandsForKey cfk) + { + return byId != cfk.byId || unmanageds != cfk.unmanageds; + } + @Override public int hashCode() { diff --git a/accord-core/src/main/java/accord/local/cfk/ExecuteTxnBacklog.java b/accord-core/src/main/java/accord/local/cfk/ExecuteTxnBacklog.java index f0502d61d0..64b1dd339c 100644 --- a/accord-core/src/main/java/accord/local/cfk/ExecuteTxnBacklog.java +++ b/accord-core/src/main/java/accord/local/cfk/ExecuteTxnBacklog.java @@ -26,7 +26,7 @@ import accord.local.Command; import accord.local.CommandStore; import accord.local.Node; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; import accord.local.cfk.CommandsForKey.TxnInfo; @@ -64,7 +64,7 @@ public void notWaiting(SafeCommandStore safeStore, TxnId txnId, RoutingKey key, private void execute(CommandStore commandStore, TxnId txnId) { - commandStore.execute(PreLoadContext.contextFor(txnId, "Load for ExecuteBacklog"), safeStore -> { + commandStore.execute(ExecutionContext.unsequenced(txnId, "Load for ExecuteBacklog"), safeStore -> { SafeCommand safeCommand = safeStore.unsafeGet(txnId); Command command = safeCommand.current(); if (command.saveStatus() != ReadyToExecute || command.participants().stillExecutes().isEmpty()) @@ -82,7 +82,7 @@ private void execute(CommandStore commandStore, TxnId txnId) node.withEpochAtLeast(executeAt.epoch(), null, node.agent(), () -> { node.agent().coordinatorEvents().onRecoveryStarted(txnId, ballot); - Adapters.standard().execute(node, node.someSequentialExecutor(), null, route, command.acceptedOrCommitted(), path, CoordinationFlags.none(), txnId, txn, executeAt, deps, deps, (result, fail) -> { + Adapters.standard().execute(node, node.someExclusiveExecutor(), null, route, command.acceptedOrCommitted(), path, CoordinationFlags.none(), txnId, txn, executeAt, deps, deps, (result, fail) -> { if (fail == null) node.reportLocalExecution(txnId, route, ballot, null, null, result); else node.agent().onException(fail); }); 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 e819af6d1f..bc680d4087 100644 --- a/accord-core/src/main/java/accord/local/cfk/NotifySink.java +++ b/accord-core/src/main/java/accord/local/cfk/NotifySink.java @@ -22,7 +22,7 @@ import accord.api.RoutingKey; import accord.local.Command; import accord.local.Commands; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; import accord.primitives.SaveStatus; @@ -59,7 +59,7 @@ public void notWaiting(SafeCommandStore safeStore, TxnId txnId, RoutingKey key, } else { - safeStore.commandStore().execute(PreLoadContext.contextFor(txnId, "Notify"), safeStore0 -> { + safeStore.commandStore().execute(ExecutionContext.unsequenced(txnId, "Notify"), safeStore0 -> { notWaiting(safeStore0, safeStore0.unsafeGet(txnId), key, uniqueHlc); }, safeStore.agent()); } @@ -75,7 +75,7 @@ public void waitingOn(SafeCommandStore safeStore, TxnInfo notify, RoutingKey key { TxnId txnId = notify.plainTxnId(); - PreLoadContext context = PreLoadContext.contextFor(txnId, "Key Waiting On"); + ExecutionContext context = ExecutionContext.unsequenced(txnId, "Key Waiting On"); if (safeStore.canExecuteWith(context) && safeStore.tryRecurse()) { try { doNotifyWaitingOn(safeStore, txnId, key, waitingOnStatus, blockedUntil, notifyCfk); } @@ -139,7 +139,7 @@ private void doNotifyAlreadyReady(SafeCommandStore safeStore, TxnId txnId, Routi RoutingKeys keys = RoutingKeys.of(key); //noinspection ConstantConditions,SillyAssignment safeStore = safeStore; // prevent use in lambda - safeStore.commandStore().execute(PreLoadContext.contextFor(txnId, keys, SYNC, WRITE, "Notify"), safeStore0 -> { + safeStore.commandStore().execute(ExecutionContext.unsequencedWrite(txnId, keys, "Notify"), safeStore0 -> { doNotifyAlreadyReady(safeStore0, txnId, key); }, safeStore.agent()); } 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 d961c571c6..20bb7a5c1e 100644 --- a/accord-core/src/main/java/accord/local/cfk/PostProcess.java +++ b/accord-core/src/main/java/accord/local/cfk/PostProcess.java @@ -44,9 +44,7 @@ import accord.utils.btree.BTree; import static accord.local.CommandSummaries.SummaryStatus.APPLIED; -import static accord.local.LoadKeys.SYNC; -import static accord.local.LoadKeysFor.WRITE; -import static accord.local.PreLoadContext.contextFor; +import static accord.local.ExecutionContext.unsequencedWrite; import static accord.local.cfk.CommandsForKey.InternalStatus.INVALIDATED; import static accord.local.cfk.CommandsForKey.InternalStatus.STABLE; import static accord.local.cfk.CommandsForKey.Unmanaged.Pending.APPLY; @@ -103,7 +101,7 @@ void doNotify(SafeCommandStore safeStore, RoutingKey key, NotifySink notifySink) try { load(safeStore, safeCommand, safeCfk, notifySink); } finally { safeStore.unrecurse(); } } - else safeStore.commandStore().execute(contextFor(txnId, RoutingKeys.of(key), SYNC, WRITE, "Load Pruned CommandsForKey"), safeStore0 -> { + else safeStore.commandStore().execute(unsequencedWrite(txnId, RoutingKeys.of(key), "Load Pruned CommandsForKey"), safeStore0 -> { load(safeStore0, safeStore0.unsafeGet(txnId), safeStore0.get(key), notifySink); }, safeStore.agent()); } 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 4fa0b78ec3..5f260affde 100644 --- a/accord-core/src/main/java/accord/local/cfk/SafeCommandsForKey.java +++ b/accord-core/src/main/java/accord/local/cfk/SafeCommandsForKey.java @@ -23,7 +23,7 @@ import accord.api.Agent; import accord.api.ProgressLog; import accord.api.RoutingKey; -import accord.impl.SafeState; +import accord.local.SafeState; import accord.local.Command; import accord.local.RedundantBefore; import accord.local.SafeCommand; @@ -33,8 +33,9 @@ import accord.primitives.Status; import accord.primitives.Status.Durability; import accord.primitives.TxnId; +import accord.utils.Invariants; -public abstract class SafeCommandsForKey implements SafeState +public abstract class SafeCommandsForKey extends SafeState { public static class RecordingNotifySink implements NotifySink { @@ -69,16 +70,13 @@ public void waitingOn(SafeCommandStore safeStore, CommandsForKey.TxnInfo txn, Ro } } - private final RoutingKey key; - + public final RoutingKey key; public SafeCommandsForKey(RoutingKey key) { this.key = key; } - protected abstract void set(CommandsForKey update); - - public RoutingKey key() + public final RoutingKey key() { return key; } @@ -148,7 +146,8 @@ public void updateRedundantBefore(SafeCommandStore safeStore, RedundantBefore.Bo public void initialize() { - set(new CommandsForKey(key)); + Invariants.require(isUninitialised()); + current = new CommandsForKey(key); } public void refresh(SafeCommandStore safeStore) @@ -160,4 +159,13 @@ public void setDurable(TxnId txnId, Durability durability) { set(current().setDurable(txnId, durability)); } + + @Override + protected final boolean hasChanged(CommandsForKey original, CommandsForKey updated) + { + if (original == null) + return !updated.isEmpty(); + + return original != updated && updated.hasChanges(original); + } } 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 01b8b28f83..eb6df29ce7 100644 --- a/accord-core/src/main/java/accord/local/cfk/Updating.java +++ b/accord-core/src/main/java/accord/local/cfk/Updating.java @@ -32,7 +32,7 @@ import accord.api.RoutingKey; import accord.local.Command; import accord.local.CommandStore; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.RedundantBefore.QuickBounds; import accord.local.SafeCommand; import accord.local.SafeCommandStore; @@ -872,7 +872,7 @@ private static long updateMaxUniqueHlc(CommandsForKey cfk, TxnInfo newInfo, Comm static void updateUnmanagedAsync(CommandStore commandStore, TxnId txnId, RoutingKey key, NotifySink notifySink) { - PreLoadContext context = PreLoadContext.contextFor(txnId, RoutingKeys.of(key), SYNC, WRITE, "Update unmanaged CommandsForKey"); + ExecutionContext context = ExecutionContext.unsequencedWrite(txnId, RoutingKeys.of(key), "Update unmanaged CommandsForKey"); commandStore.execute(context, safeStore -> { SafeCommandsForKey safeCommandsForKey = safeStore.get(key); CommandsForKey cur = safeCommandsForKey.current(); diff --git a/accord-core/src/main/java/accord/local/durability/DurabilityQueue.java b/accord-core/src/main/java/accord/local/durability/DurabilityQueue.java index 8c488172db..181c59cfba 100644 --- a/accord-core/src/main/java/accord/local/durability/DurabilityQueue.java +++ b/accord-core/src/main/java/accord/local/durability/DurabilityQueue.java @@ -48,6 +48,7 @@ import accord.primitives.TxnId; import accord.topology.Topology; import accord.topology.TopologyRetiredException; +import accord.utils.IntrusiveHeapNode; import accord.utils.IntrusivePriorityHeap; import accord.utils.Invariants; import accord.utils.SortedArrays.SortedArrayList; @@ -144,7 +145,7 @@ public void retry(DurabilityRequest request, PartialSyncPoint syncPoint) @Override public DurabilityResults execute(PartialSyncPoint syncPoint, int attempt) { - return coordinateIncluding(node, syncPoint, node.someSequentialExecutor(), attempt); + return coordinateIncluding(node, syncPoint, node.someExclusiveExecutor(), attempt); } } @@ -197,7 +198,7 @@ public String toString() enum Status { QUEUED, ACTIVE, COMPLETING, RESTARTING, ABANDONED, DONE } - static class Pending extends IntrusivePriorityHeap.Node + static class Pending extends IntrusiveHeapNode { final @Nullable DurabilityRequest request; PartialSyncPoint syncPoint; @@ -291,8 +292,8 @@ static final class PendingQueue extends IntrusivePriorityHeap Pending poll() { heapify(); return super.pollNode(); } Pending peek() { heapify(); return super.peekNode(); } @Override public int compare(Pending o1, Pending o2) { return comparator.compare(o1, o2); } - @Override protected void append(Pending node) { super.append(node); } - @Override protected void remove(Pending node) { super.remove(node); } + void append(Pending node) { super.appendNode(node); } + void remove(Pending node) { super.removeNode(node); } @Override protected void clear() { super.clear(); } } diff --git a/accord-core/src/main/java/accord/messages/AbstractRequest.java b/accord-core/src/main/java/accord/messages/AbstractRequest.java index 96c2702da8..d2d734de0e 100644 --- a/accord-core/src/main/java/accord/messages/AbstractRequest.java +++ b/accord-core/src/main/java/accord/messages/AbstractRequest.java @@ -26,8 +26,8 @@ import accord.primitives.TxnId; // TODO (expected): merge cancel/timeout logic here from NoWaitRequest and ReadRequest -// TODO (expected): migrate to SequentialExecutor approach used by Coordinator logic, rather than synchronized -// TODO (desired): allow a task to be associated with more than one SequentialExecutor, and only commit a thread when both are ready to schedule it +// TODO (expected): migrate to ExclusiveExecutor approach used by Coordinator logic, rather than synchronized +// TODO (desired): allow a task to be associated with more than one ExclusiveExecutor, and only commit a thread when both are ready to schedule it public abstract class AbstractRequest

, R> extends MapReduceConsumeCommandStores implements Request { protected transient Node node; diff --git a/accord-core/src/main/java/accord/messages/Accept.java b/accord-core/src/main/java/accord/messages/Accept.java index c58b386072..851860627f 100644 --- a/accord-core/src/main/java/accord/messages/Accept.java +++ b/accord-core/src/main/java/accord/messages/Accept.java @@ -286,6 +286,11 @@ public LoadKeysFor loadKeysFor() return calculateDeps() ? LoadKeysFor.READ_WRITE : LoadKeysFor.WRITE; } + public ExecutionKind executionKind() + { + return ExecutionKind.ACCEPT; + } + @Override public MessageType type() { diff --git a/accord-core/src/main/java/accord/messages/Apply.java b/accord-core/src/main/java/accord/messages/Apply.java index 48845d8e91..57b0f5c52f 100644 --- a/accord-core/src/main/java/accord/messages/Apply.java +++ b/accord-core/src/main/java/accord/messages/Apply.java @@ -247,24 +247,21 @@ public static ApplyReply apply(SaveStatus newSaveStatus, SafeCommandStore safeSt } @Override - public Unseekables keys() + public ApplyReply reduce(ApplyReply a, ApplyReply b) { - if (flags.contains(READY_TO_EXECUTE) && fastWritesMayBypassCommandsForKey()) - return RoutingKeys.EMPTY; - return super.keys(); + return ApplyReply.reduce(a, b); } @Override - public ApplyReply reduce(ApplyReply a, ApplyReply b) + public LoadKeys loadKeys() { - return ApplyReply.reduce(a, b); + return LoadKeys.ASYNC; } @Override - public LoadKeys loadKeys() + public ExecutionKind executionKind() { - // TODO (expected): need to guarantee execution order then can make this ASYNC - return LoadKeys.SYNC; + return ExecutionKind.APPLY; } @Override diff --git a/accord-core/src/main/java/accord/messages/ApplyThenWaitUntilApplied.java b/accord-core/src/main/java/accord/messages/ApplyThenWaitUntilApplied.java index bc9364bb2e..c85bf90344 100644 --- a/accord-core/src/main/java/accord/messages/ApplyThenWaitUntilApplied.java +++ b/accord-core/src/main/java/accord/messages/ApplyThenWaitUntilApplied.java @@ -153,6 +153,11 @@ public MessageType type() return APPLY_THEN_WAIT_UNTIL_APPLIED_REQ; } + public ExecutionKind executionKind() + { + return ExecutionKind.APPLY; + } + @Override public String toString() { diff --git a/accord-core/src/main/java/accord/messages/BeginInvalidation.java b/accord-core/src/main/java/accord/messages/BeginInvalidation.java index 0418ef3356..fb0f141e6c 100644 --- a/accord-core/src/main/java/accord/messages/BeginInvalidation.java +++ b/accord-core/src/main/java/accord/messages/BeginInvalidation.java @@ -37,7 +37,7 @@ import static accord.primitives.Route.isFullRoute; import static accord.utils.Functions.mapReduceNonNull; -public class BeginInvalidation extends ParticipantsRequest, BeginInvalidation.InvalidateReply> implements Request, PreLoadContext +public class BeginInvalidation extends ParticipantsRequest, BeginInvalidation.InvalidateReply> implements Request, ExecutionContext { public final Ballot ballot; diff --git a/accord-core/src/main/java/accord/messages/BeginRecovery.java b/accord-core/src/main/java/accord/messages/BeginRecovery.java index 435a932b43..65c2da32da 100644 --- a/accord-core/src/main/java/accord/messages/BeginRecovery.java +++ b/accord-core/src/main/java/accord/messages/BeginRecovery.java @@ -266,6 +266,12 @@ public LoadKeys loadKeys() return LoadKeys.SYNC; } + @Override + public ExecutionKind executionKind() + { + return ExecutionKind.PREACCEPT; + } + @Override public LoadKeysFor loadKeysFor() { diff --git a/accord-core/src/main/java/accord/messages/Callback.java b/accord-core/src/main/java/accord/messages/Callback.java index ca9b52304b..c8522b9f97 100644 --- a/accord-core/src/main/java/accord/messages/Callback.java +++ b/accord-core/src/main/java/accord/messages/Callback.java @@ -88,9 +88,9 @@ static Runnable runOnSlow(CallbackExclusive callback, Node.Id from) }; } - static void onSlow(AsyncExecutor executor, boolean unsafeToReplyImmediately, CallbackExclusive callback, Node.Id from) + static void onSlow(AsyncExecutor executor, boolean doNotReplyImmediately, CallbackExclusive callback, Node.Id from) { - replyMaybeImmediately(executor, unsafeToReplyImmediately, runOnSlow(callback, from)); + replyMaybeImmediately(executor, doNotReplyImmediately, runOnSlow(callback, from)); } void onSuccessExclusive(Node.Id from, R reply); diff --git a/accord-core/src/main/java/accord/messages/CheckStatus.java b/accord-core/src/main/java/accord/messages/CheckStatus.java index 3c03569cdd..656d9c09fe 100644 --- a/accord-core/src/main/java/accord/messages/CheckStatus.java +++ b/accord-core/src/main/java/accord/messages/CheckStatus.java @@ -26,7 +26,7 @@ import accord.local.Command; import accord.local.Commands; import accord.local.Node.Id; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; import accord.primitives.KnownMap.MinAndMaxKnown; @@ -85,7 +85,7 @@ import static accord.primitives.Route.isRoute; public class CheckStatus extends ParticipantsRequest, CheckStatus.CheckStatusReply> - implements Request, PreLoadContext, MapReduceConsume + implements Request, ExecutionContext, MapReduceConsume { public static class SerializationSupport { diff --git a/accord-core/src/main/java/accord/messages/Commit.java b/accord-core/src/main/java/accord/messages/Commit.java index 6a18814f88..9425dc8d95 100644 --- a/accord-core/src/main/java/accord/messages/Commit.java +++ b/accord-core/src/main/java/accord/messages/Commit.java @@ -193,8 +193,13 @@ protected Commit(Kind kind, TxnId txnId, Route scope, long waitForEpoch, long @Override public LoadKeys loadKeys() { - // TODO (expected): need to guarantee execution order then can make this ASYNC - return LoadKeys.SYNC; + return LoadKeys.ASYNC; + } + + @Override + public ExecutionKind executionKind() + { + return ExecutionKind.COMMIT; } @Override diff --git a/accord-core/src/main/java/accord/messages/GetDurableBefore.java b/accord-core/src/main/java/accord/messages/GetDurableBefore.java index 97e2008ae2..27f5d3724f 100644 --- a/accord-core/src/main/java/accord/messages/GetDurableBefore.java +++ b/accord-core/src/main/java/accord/messages/GetDurableBefore.java @@ -21,7 +21,7 @@ import javax.annotation.Nullable; import accord.local.Node; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.DurableBefore; import accord.primitives.TxnId; import accord.utils.async.Cancellable; @@ -29,7 +29,7 @@ import static accord.messages.MessageType.StandardMessage.GET_DURABLE_BEFORE_REQ; import static accord.messages.MessageType.StandardMessage.GET_DURABLE_BEFORE_RSP; -public class GetDurableBefore implements Request, PreLoadContext +public class GetDurableBefore implements Request, ExecutionContext { public GetDurableBefore() { diff --git a/accord-core/src/main/java/accord/messages/InformDurable.java b/accord-core/src/main/java/accord/messages/InformDurable.java index d8168b4117..e354a74b33 100644 --- a/accord-core/src/main/java/accord/messages/InformDurable.java +++ b/accord-core/src/main/java/accord/messages/InformDurable.java @@ -26,7 +26,7 @@ import accord.local.LoadKeys; import accord.local.Node; import accord.local.Node.Id; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; import accord.primitives.Ballot; @@ -50,7 +50,7 @@ import static accord.messages.MessageType.StandardMessage.INFORM_DURABLE_REQ; import static accord.messages.SimpleReply.Ok; -public class InformDurable extends RouteRequest implements PreLoadContext +public class InformDurable extends RouteRequest implements ExecutionContext { public static class SerializationSupport { diff --git a/accord-core/src/main/java/accord/messages/PreAccept.java b/accord-core/src/main/java/accord/messages/PreAccept.java index 007d60ab64..c2e8851057 100644 --- a/accord-core/src/main/java/accord/messages/PreAccept.java +++ b/accord-core/src/main/java/accord/messages/PreAccept.java @@ -28,6 +28,7 @@ import accord.local.Command; import accord.local.Commands; import accord.local.DepsCalculator; +import accord.local.ExecutionContext; import accord.local.LoadKeys; import accord.local.LoadKeysFor; import accord.local.Node.Id; @@ -107,6 +108,12 @@ public LoadKeysFor loadKeysFor() return LoadKeysFor.READ_WRITE; } + @Override + public ExecutionKind executionKind() + { + return ExecutionKind.PREACCEPT; + } + @Override protected Cancellable submit() { diff --git a/accord-core/src/main/java/accord/messages/ReadData.java b/accord-core/src/main/java/accord/messages/ReadData.java index fc54af28c6..3e970da29c 100644 --- a/accord-core/src/main/java/accord/messages/ReadData.java +++ b/accord-core/src/main/java/accord/messages/ReadData.java @@ -211,6 +211,11 @@ protected long minEpoch() return executeAtEpoch; } + public ExecutionKind executionKind() + { + return ExecutionKind.STABLE; + } + @Override public final Cancellable process(Node on, Node.Id replyTo, ReplyContext replyContext) { diff --git a/accord-core/src/main/java/accord/messages/ReadEphemeralTxnData.java b/accord-core/src/main/java/accord/messages/ReadEphemeralTxnData.java index 2c63cab6bd..b3de26809c 100644 --- a/accord-core/src/main/java/accord/messages/ReadEphemeralTxnData.java +++ b/accord-core/src/main/java/accord/messages/ReadEphemeralTxnData.java @@ -24,7 +24,7 @@ import accord.local.Command; import accord.local.Commands; import accord.local.Node.Id; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; import accord.local.StoreParticipants; @@ -186,7 +186,7 @@ protected boolean cancel() while (iter.hasNext()) { node.commandStores().forId(iter.nextValue()) - .execute((PreLoadContext.Empty) () -> "Timeout Ephemeral Read", safeStore -> { + .execute((ExecutionContext.Empty) () -> "Timeout Ephemeral Read", safeStore -> { eraseEphemeralRead(safeStore, txnId); }, node.agent()); } diff --git a/accord-core/src/main/java/accord/messages/SetGloballyDurable.java b/accord-core/src/main/java/accord/messages/SetGloballyDurable.java index 3276ead242..6caef16520 100644 --- a/accord-core/src/main/java/accord/messages/SetGloballyDurable.java +++ b/accord-core/src/main/java/accord/messages/SetGloballyDurable.java @@ -22,14 +22,14 @@ import accord.local.DurableBefore; import accord.local.Node; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.primitives.TxnId; import accord.utils.async.Cancellable; import static accord.messages.MessageType.StandardMessage.SET_GLOBALLY_DURABLE_REQ; import static accord.messages.SimpleReply.Ok; -public class SetGloballyDurable implements Request, PreLoadContext +public class SetGloballyDurable implements Request, ExecutionContext { public final DurableBefore durableBefore; diff --git a/accord-core/src/main/java/accord/primitives/KeyDeps.java b/accord-core/src/main/java/accord/primitives/KeyDeps.java index 1f0ad1d94c..9ed4d2efce 100644 --- a/accord-core/src/main/java/accord/primitives/KeyDeps.java +++ b/accord-core/src/main/java/accord/primitives/KeyDeps.java @@ -21,6 +21,7 @@ import accord.api.RoutingKey; import accord.primitives.Deps.DepRelationList; import accord.utils.ArrayBuffers; +import accord.utils.Functions; import accord.utils.IndexedBiConsumer; import accord.utils.IndexedConsumer; import accord.utils.IndexedFunction; @@ -250,7 +251,7 @@ KeyDeps withTxnIds(TxnId[] txnIds) if (start == end) return min; return TxnId.nonNullOrMin(min, txnIds[start]); - }, (TxnId)null); + }, (TxnId)null, Functions.alwaysFalse()); } private KeyDeps select(RoutingKeys select) diff --git a/accord-core/src/main/java/accord/primitives/LatestDeps.java b/accord-core/src/main/java/accord/primitives/LatestDeps.java index 6242103e94..5a62e63442 100644 --- a/accord-core/src/main/java/accord/primitives/LatestDeps.java +++ b/accord-core/src/main/java/accord/primitives/LatestDeps.java @@ -36,7 +36,7 @@ import accord.coordinate.CollectLatestDeps; import accord.coordinate.CoordinationAdapter; import accord.local.Node; -import accord.local.SequentialAsyncExecutor; +import accord.api.ExclusiveAsyncExecutor; import accord.primitives.Known.KnownDeps; import accord.topology.SelectShards; import accord.utils.Invariants; @@ -66,7 +66,7 @@ public static LatestDeps create(RoutingKey[] starts, LatestEntry[] values) } } - public static void withCommitted(CoordinationAdapter adapter, Node node, SequentialAsyncExecutor executor, Merge merge, FullRoute route, Ballot ballot, TxnId txnId, Timestamp executeAt, Txn txn, BiConsumer failureCallback, Consumer withDeps) + public static void withCommitted(CoordinationAdapter adapter, Node node, ExclusiveAsyncExecutor executor, Merge merge, FullRoute route, Ballot ballot, TxnId txnId, Timestamp executeAt, Txn txn, BiConsumer failureCallback, Consumer withDeps) { if (!node.topology().active().hasAtLeastEpoch(executeAt.epoch())) { @@ -93,7 +93,7 @@ public static void withCommitted(CoordinationAdapter adapter, Node node, Sequ } } - public static void withStable(CoordinationAdapter adapter, Node node, SequentialAsyncExecutor executor, Merge merge, Deps alreadyStableDeps, Route require, @Nullable Route sendTo, @Nullable SelectShards selectSendTo, FullRoute route, Ballot ballot, TxnId txnId, Timestamp executeAt, Txn txn, BiConsumer failureCallback, Consumer withDeps) + public static void withStable(CoordinationAdapter adapter, Node node, ExclusiveAsyncExecutor executor, Merge merge, Deps alreadyStableDeps, Route require, @Nullable Route sendTo, @Nullable SelectShards selectSendTo, FullRoute route, Ballot ballot, TxnId txnId, Timestamp executeAt, Txn txn, BiConsumer failureCallback, Consumer withDeps) { Invariants.require(sendTo == null || selectSendTo != null); if (!node.topology().active().hasAtLeastEpoch(executeAt.epoch())) @@ -133,7 +133,7 @@ public static void withStable(CoordinationAdapter adapter, Node node, Sequent } } - public static void stabilise(CoordinationAdapter adapter, Node node, SequentialAsyncExecutor executor, Deps deps, Route stabilise, @Nullable Route sendTo, SelectShards selectSendTo, FullRoute route, Ballot ballot, TxnId txnId, Timestamp executeAt, Txn txn, BiConsumer failureCallback, Consumer withDeps) + public static void stabilise(CoordinationAdapter adapter, Node node, ExclusiveAsyncExecutor executor, Deps deps, Route stabilise, @Nullable Route sendTo, SelectShards selectSendTo, FullRoute route, Ballot ballot, TxnId txnId, Timestamp executeAt, Txn txn, BiConsumer failureCallback, Consumer withDeps) { Invariants.require(sendTo == null || selectSendTo != null); adapter.stabiliseOnly(node, executor, stabilise, sendTo == null ? stabilise : sendTo, route, ballot, txnId, txn, executeAt, deps, (success, fail) -> { @@ -142,7 +142,7 @@ public static void stabilise(CoordinationAdapter adapter, Node node, Sequenti }); } - public static void withStable(CoordinationAdapter adapter, Node node, SequentialAsyncExecutor executor, TxnId txnId, Timestamp executeAt, Txn txn, Deps alreadyStableDeps, Route require, @Nullable Route sendTo, SelectShards selectSendTo, FullRoute route, BiConsumer failureCallback, Consumer withDeps) + public static void withStable(CoordinationAdapter adapter, Node node, ExclusiveAsyncExecutor executor, TxnId txnId, Timestamp executeAt, Txn txn, Deps alreadyStableDeps, Route require, @Nullable Route sendTo, SelectShards selectSendTo, FullRoute route, BiConsumer failureCallback, Consumer withDeps) { Invariants.require(sendTo == null || selectSendTo != null); if (!node.topology().active().hasAtLeastEpoch(executeAt.epoch())) diff --git a/accord-core/src/main/java/accord/primitives/Routables.java b/accord-core/src/main/java/accord/primitives/Routables.java index 426de10ae3..75d8db6e78 100644 --- a/accord-core/src/main/java/accord/primitives/Routables.java +++ b/accord-core/src/main/java/accord/primitives/Routables.java @@ -203,7 +203,7 @@ default long findNextIntersection(int thisIndex, Routables with, int withInde @Inline static T foldl(Routables inputs, AbstractRanges matching, IndexedFold fold, T initialValue) { - return Helper.foldl(Routables::findNextIntersection, Helper::findLimit, inputs, matching, fold, initialValue); + return Helper.foldl(Routables::findNextIntersection, Helper::findLimit, inputs, matching, fold, initialValue, Functions.alwaysFalse()); } /** @@ -213,7 +213,7 @@ static T foldl(Routables inputs, AbstractRang @Inline static T foldl(AbstractKeys inputs, AbstractRanges matching, IndexedFold fold, T initialValue) { - return Helper.foldl(AbstractKeys::findNextIntersection, Helper::findLimit, inputs, matching, fold, initialValue); + return Helper.foldl(AbstractKeys::findNextIntersection, Helper::findLimit, inputs, matching, fold, initialValue, Functions.alwaysFalse()); } /** @@ -221,13 +221,28 @@ static T foldl(AbstractKeys inputs, Abstra * Terminate once we hit {@code terminalValue}. */ @Inline - static T foldl(AbstractUnseekableKeys inputs, Unseekables matching, IndexedFold fold, T initialValue) + static T foldl(AbstractUnseekableKeys inputs, Unseekables matching, IndexedFold fold, T initialValue, Predicate terminate) { switch (matching.domain()) { default: throw new AssertionError(); - case Key: return Helper.foldl(AbstractUnseekableKeys::findNextSameKindIntersection, Helper::findLimit, inputs, (AbstractUnseekableKeys)matching, fold, initialValue); - case Range: return Helper.foldl(AbstractUnseekableKeys::findNextIntersection, Helper::findLimit, inputs, (AbstractRanges)matching, fold, initialValue); + case Key: return Helper.foldl(AbstractUnseekableKeys::findNextSameKindIntersection, Helper::findLimit, inputs, (AbstractUnseekableKeys)matching, fold, initialValue, terminate); + case Range: return Helper.foldl(AbstractUnseekableKeys::findNextIntersection, Helper::findLimit, inputs, (AbstractRanges)matching, fold, initialValue, terminate); + } + } + + /** + * Fold-left over the {@code inputs} that intersect with {@code matching} in ascending order. + * Terminate once we hit {@code terminalValue}. + */ + @Inline + static T foldl(AbstractUnseekableKeys inputs, Unseekables matching, IndexedTriFold fold, P1 p1, P2 p2, T initialValue, Predicate terminate) + { + switch (matching.domain()) + { + default: throw new AssertionError(); + case Key: return Helper.foldl(AbstractUnseekableKeys::findNextSameKindIntersection, Helper::findLimit, inputs, (AbstractUnseekableKeys)matching, fold, p1, p2, initialValue, terminate); + case Range: return Helper.foldl(AbstractUnseekableKeys::findNextIntersection, Helper::findLimit, inputs, (AbstractRanges)matching, fold, p1, p2, initialValue, terminate); } } @@ -335,7 +350,7 @@ static T foldlMinimal(Seekables is, AbstractRanges ms, IndexedFold, Matches extends Routables, T> T foldl(SetIntersections setIntersections, ValueIntersections valueIntersections, - Inputs is, Matches ms, IndexedFold fold, T accumulator) + Inputs is, Matches ms, IndexedFold fold, T accumulator, Predicate terminate) { int i = 0, m = 0; while (true) @@ -351,6 +366,8 @@ T foldl(SetIntersections setIntersections, ValueIntersections es) return true; } + @Override + public void sort(Comparator c) + { + Arrays.sort(buffer, 0, size, (Comparator) c); + } + public void close() { if (buffer == null) return; diff --git a/accord-core/src/main/java/accord/utils/IntrusiveHeapNode.java b/accord-core/src/main/java/accord/utils/IntrusiveHeapNode.java new file mode 100644 index 0000000000..8e60a7b90c --- /dev/null +++ b/accord-core/src/main/java/accord/utils/IntrusiveHeapNode.java @@ -0,0 +1,39 @@ +/* + * 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.utils; + +public abstract class IntrusiveHeapNode +{ + int heapIndex = -1; + + protected boolean isInHeap() + { + return heapIndex >= 0; + } + + final void setHeapIndex(int heapIndex) + { + this.heapIndex = heapIndex; + } + + protected final int heapIndex() + { + return heapIndex; + } +} diff --git a/accord-core/src/main/java/accord/utils/IntrusivePriorityHeap.java b/accord-core/src/main/java/accord/utils/IntrusivePriorityHeap.java index de000b4817..b4b5745d49 100644 --- a/accord-core/src/main/java/accord/utils/IntrusivePriorityHeap.java +++ b/accord-core/src/main/java/accord/utils/IntrusivePriorityHeap.java @@ -30,34 +30,14 @@ * and removed without an intervening poll/peek incurs only constant time costs. * @param */ -public abstract class IntrusivePriorityHeap implements Comparator +public abstract class IntrusivePriorityHeap implements Comparator { private static final int NORMAL_MIN_SIZE = 8; private static final int MAX_EMPTY_SIZE = 1024; - private static final Node[] EMPTY = new Node[0]; - private static final Node[] TINY_EMPTY = new Node[0]; + private static final IntrusiveHeapNode[] EMPTY = new IntrusiveHeapNode[0]; + private static final IntrusiveHeapNode[] TINY_EMPTY = new IntrusiveHeapNode[0]; - public static abstract class Node - { - private int heapIndex = -1; - - protected boolean isInHeap() - { - return heapIndex >= 0; - } - - final void setHeapIndex(int heapIndex) - { - this.heapIndex = heapIndex; - } - - protected final int heapIndex() - { - return heapIndex; - } - } - - Node[] heap = EMPTY; + IntrusiveHeapNode[] heap = EMPTY; int heapifiedSize; int size; @@ -75,13 +55,13 @@ public IntrusivePriorityHeap(boolean tiny) /** * insert unsorted; can be used as a simple list */ - protected void append(N node) + protected final void appendNode(N node) { Invariants.require(node.heapIndex() < 0); if (size == heap.length) { if (heap.length >= NORMAL_MIN_SIZE) heap = Arrays.copyOf(heap, size * 2); - else if (heap == EMPTY) heap = new Node[NORMAL_MIN_SIZE]; + else if (heap == EMPTY) heap = new IntrusiveHeapNode[NORMAL_MIN_SIZE]; else heap = Arrays.copyOf(heap, Math.max(size + 2, size * 2)); } @@ -90,45 +70,67 @@ protected void append(N node) } /** - * insert unsorted; can be used as a simple list + * Maintains heap property ONLY IF ALREADY IMPOSED + * Returns -1 if we were empty, and 1 if our head was heapified and we updated it; otherwise returns 0 */ - protected void update(N node) + protected final int insertNode(N node) + { + boolean wasHeapified = isHeapified(); + appendNode(node); + if (size == 1) + return -1; + + if (!wasHeapified) + return 0; + + int result = compare(node, (N)heap[0]) < 0 ? 1 : 0; + heapify(); + return result; + } + + /** + * Update the ordering of a node; if the node is in the heapified portion, sift it to its correct position. + * Return the previous index of the node; if this was unheapified this will be returned as a negative integer, + * but if it was heapified it will be a positive integer. + */ + protected final int updateNode(N node) { int index = node.heapIndex(); Invariants.require(heap[index] == node); if (index >= heapifiedSize) - return; + return -index; if (index == 0 || compare((N)heap[(index-1)/2], node) <= 0) siftDown(node, index); else siftUp(node, index); + return index; } - protected boolean contains(N node) + protected final boolean containsNode(N node) { int i = node.heapIndex(); return i >= 0 && i < size && heap[i] == node; } - protected boolean removeIfContains(N node) + protected final boolean removeNodeIfContains(N node) { int i = node.heapIndex(); if (i < 0 || i >= heap.length || heap[i] != node) return false; - removeInternal(i, node); + removeNode(i, node); return true; } /** * remove; can be used as a simple list */ - protected void remove(N node) + protected final void removeNode(N node) { int i = node.heapIndex(); Invariants.requireArgument(i >= 0 && i < heap.length && heap[i] == node); - removeInternal(i, node); + removeNode(i, node); } - private void removeInternal(int i, N node) + private void removeNode(int i, N node) { if (size > 1) { @@ -157,16 +159,16 @@ private void removeInternal(int i, N node) node.setHeapIndex(-1); } - protected N peekNode() + protected final N peekNode() { if (size == 0) return null; - Invariants.require(heapifiedSize == size); + Invariants.require(isHeapified()); return (N) heap[0]; } - protected N pollNode() + protected final N pollNode() { if (size == 0) return null; @@ -186,7 +188,7 @@ private void replace(N replacing, N with, int i) else siftDown(with, i); } - protected void replaceHead() + private void replaceHead() { --size; --heapifiedSize; @@ -204,17 +206,24 @@ protected void replaceHead() private boolean maybeShrink() { - if (heap.length <= MAX_EMPTY_SIZE) + IntrusiveHeapNode[] shrink = maybeShrink(heap); + if (shrink == heap) return false; - - heap = new Node[MAX_EMPTY_SIZE]; + heap = shrink; return true; } + protected IntrusiveHeapNode[] maybeShrink(IntrusiveHeapNode[] current) + { + if (current.length <= MAX_EMPTY_SIZE) + return current; + return new IntrusiveHeapNode[MAX_EMPTY_SIZE]; + } + /** * {@code i} is a free position in the heap, siftDown must be safely inserted at a position >= i */ - protected void siftDown(N siftDown, int i) + private void siftDown(N siftDown, int i) { while (true) { @@ -288,17 +297,22 @@ protected void heapify() siftUp((N)heap[heapifiedSize], heapifiedSize++); } - protected N get(int i) + protected final N getNode(int i) { return (N) heap[i]; } - public int size() + public final int size() { return size; } - public boolean isEmpty() + protected final int heapifiedSize() + { + return heapifiedSize; + } + + protected final boolean isEmptyInternal() { return size == 0; } @@ -309,7 +323,7 @@ protected void ensureHeapified() heapify(); } - protected boolean isHeapified() + protected final boolean isHeapified() { return heapifiedSize == size; } @@ -321,7 +335,12 @@ protected void clear() maybeShrink(); } - protected

void drain(P param, BiConsumer consumer) + protected final int heapIndex(N node) + { + return node.heapIndex; + } + + protected final

void drain(P param, BiConsumer consumer) { for (int i = 0 ; i < size ; ++i) { @@ -336,10 +355,10 @@ protected

void drain(P param, BiConsumer consumer) /** * Note that this heap immediately passes ownership of any removed node to the caller; - * if the Node is not inserted into another heap then {@link Node#setHeapIndex(-1)} + * if the Node is not inserted into another heap then {@link IntrusiveHeapNode#setHeapIndex(-1)} * should be invoked. */ - protected

void filterUnheapified(P param, BiPredicate remove) + protected final

void filterUnheapified(P param, BiPredicate remove) { int removedCount = 0; for (int i = heapifiedSize ; i < size ; ++i) @@ -351,7 +370,7 @@ protected

void filterUnheapified(P param, BiPredicate remove) } else if (removedCount > 0) { - Node n = heap[i]; + IntrusiveHeapNode n = heap[i]; heap[i - removedCount] = n; n.heapIndex = i - removedCount; } diff --git a/accord-core/src/main/java/accord/utils/Invariants.java b/accord-core/src/main/java/accord/utils/Invariants.java index 6f9cf6b113..5bb2da2823 100644 --- a/accord-core/src/main/java/accord/utils/Invariants.java +++ b/accord-core/src/main/java/accord/utils/Invariants.java @@ -76,6 +76,11 @@ public static IllegalStateException createIllegalState(String msg) return new IllegalStateException(msg); } + public static IllegalStateException createIllegalState(String fmt, Object... args) + { + return createIllegalState(format(fmt, args)); + } + public static IllegalStateException illegalState(String msg) { throw createIllegalState(msg); @@ -136,6 +141,12 @@ public static void paranoid(boolean condition) throw illegalState(); } + public static void paranoidLinearCost(boolean condition) + { + if (isParanoid() && testParanoia(Paranoia.LINEAR, Paranoia.LINEAR, ParanoiaCostFactor.LOW) && !condition) + throw illegalState(); + } + public static boolean expect(boolean condition) { if (!condition) @@ -358,6 +369,12 @@ public static T nonNull(T param) return param; } + public static void requireNull(Object param) + { + if (param != null) + throw illegalState("Expected to be null: " + param); + } + public static T nonNull(T param, String message) { if (param == null) diff --git a/accord-core/src/main/java/accord/utils/LogGroupTimers.java b/accord-core/src/main/java/accord/utils/LogGroupTimers.java index 8c117e88d2..088a44727a 100644 --- a/accord-core/src/main/java/accord/utils/LogGroupTimers.java +++ b/accord-core/src/main/java/accord/utils/LogGroupTimers.java @@ -57,7 +57,7 @@ @SuppressWarnings({ "rawtypes", "unchecked" }) public class LogGroupTimers { - public static class Timer extends IntrusivePriorityHeap.Node + public static class Timer extends IntrusiveHeapNode { private long deadline; protected final long deadline() @@ -86,6 +86,11 @@ protected void heapify() super.heapify(); } + final boolean isEmpty() + { + return isEmptyInternal(); + } + void setSpan(long newSpan) { this.span = newSpan; @@ -108,18 +113,16 @@ private boolean maybeRedistribute(T timer) return true; } - @Override protected void append(T timer) { Invariants.require(epoch + span > timer.deadline()); - super.append(timer); + appendNode(timer); } - @Override protected void update(T timer) { Invariants.require(epoch + span > timer.deadline()); - super.update(timer); + updateNode(timer); } @Override @@ -302,7 +305,7 @@ public void update(long deadline, T timer) } else { - bucket.remove(timer); + bucket.removeNode(timer); addInternal(deadline, timer); } refreshWakeAt(prevDeadline, deadline); @@ -328,7 +331,7 @@ public void remove(T timer) long prevDeadline = t.deadline; Bucket bucket = findBucket(t.deadline); Invariants.require(bucket != null); - bucket.remove(timer); + bucket.removeNode(timer); --timerCount; refreshWakeAt(prevDeadline, Long.MAX_VALUE); } diff --git a/accord-core/src/main/java/accord/utils/SortedArrays.java b/accord-core/src/main/java/accord/utils/SortedArrays.java index be6894a0c0..9ffb0bd762 100644 --- a/accord-core/src/main/java/accord/utils/SortedArrays.java +++ b/accord-core/src/main/java/accord/utils/SortedArrays.java @@ -526,9 +526,9 @@ public static int[] linearIntersection(int[] left, int leftStart, int leftEnd, i { if (!hasMatch) return left.length == 0 ? left : NO_INTS; - if (leftStart == 0 && leftEnd == left.length) + if (leftStart == 0 && leftIdx == left.length) return left; - return Arrays.copyOfRange(left, leftStart, leftEnd); + return Arrays.copyOfRange(left, leftStart, leftIdx); } } diff --git a/accord-core/src/main/java/accord/utils/TinyEnumSet.java b/accord-core/src/main/java/accord/utils/TinyEnumSet.java index ad540222e5..4541d03c36 100644 --- a/accord-core/src/main/java/accord/utils/TinyEnumSet.java +++ b/accord-core/src/main/java/accord/utils/TinyEnumSet.java @@ -67,13 +67,17 @@ public static > TinyEnumSet of(Enum ... values) return new TinyEnumSet<>(encode(values)); } + public static > int encode(Enum v1, Enum v2) + { + return encode(v1) | encode(v2); + } public static > int encode(Enum ... values) { int bitset = 0; for (Enum v : values) { Invariants.requireArgument(v.ordinal() < 32); - bitset |= 1 << v.ordinal(); + bitset |= encode(v); } return bitset; } diff --git a/accord-core/src/main/java/accord/utils/UnhandledEnum.java b/accord-core/src/main/java/accord/utils/UnhandledEnum.java index 05b4a42e56..70c4318e98 100644 --- a/accord-core/src/main/java/accord/utils/UnhandledEnum.java +++ b/accord-core/src/main/java/accord/utils/UnhandledEnum.java @@ -32,11 +32,21 @@ private UnhandledEnum(String prefix, @Nonnull Enum value) super(prefix + value.getClass().getSimpleName() + ": " + value); } + private UnhandledEnum(String prefix, @Nonnull Enum value, String explain) + { + super(prefix + value.getClass().getSimpleName() + ": " + value + ". " + explain + '.'); + } + public static UnhandledEnum invalid(@Nonnull Enum value) { return new UnhandledEnum("Invalid ", value); } + public static UnhandledEnum invalid(@Nonnull Enum value, String explain) + { + return new UnhandledEnum("Invalid ", value, explain); + } + public static UnhandledEnum unknown(@Nonnull Enum value) { return new UnhandledEnum("Unknown ", value); diff --git a/accord-core/src/main/java/accord/utils/async/AsyncCallbacks.java b/accord-core/src/main/java/accord/utils/async/AsyncCallbacks.java index c7ca2a60f5..a572064cf1 100644 --- a/accord-core/src/main/java/accord/utils/async/AsyncCallbacks.java +++ b/accord-core/src/main/java/accord/utils/async/AsyncCallbacks.java @@ -30,9 +30,23 @@ public class AsyncCallbacks // a runnable interface that may be directly failed public interface RunOrFail extends Runnable { - // run should not throw any exception - void run(); + /** + * Does not throw exceptions ordinarily; failure should be reported to any callback it carries + * and only a boolean indicating success/failure is returned to the caller. + * Exceptions are thrown to the caller only if there was a problem reporting the outcome to any callback + */ + boolean runMayThrow(); + + /** + * Notify any callback that the task will not be run due to the provided exception + */ void fail(Throwable fail); + + @Override + default void run() + { + runMayThrow(); + } } public static class RunAndCallback implements RunOrFail @@ -47,9 +61,9 @@ public RunAndCallback(Runnable run, BiConsumer callback } @Override - public void run() + public boolean runMayThrow() { - runAndCallback(run, callback); + return runAndCallback(run, callback); } @Override @@ -77,9 +91,9 @@ public CallAndCallback(Callable call, BiConsumer> call, BiConsumer BiConsumer inExecutor(BiConsumer callback, Executor executor) { return (success, fail) -> { @@ -159,7 +174,7 @@ public static BiConsumer ifSuccess(Consumer consumer) }; } - public static void runAndCallback(Runnable run, BiConsumer receiver) + public static boolean runAndCallback(Runnable run, BiConsumer receiver) { try { @@ -168,12 +183,13 @@ public static void runAndCallback(Runnable run, BiConsumer void callAndCallback(Callable call, BiConsumer receiver) + public static boolean callAndCallback(Callable call, BiConsumer receiver) { V v; try @@ -183,12 +199,13 @@ public static void callAndCallback(Callable call, BiConsumer void flatCallAndCallback(Callable> call, BiConsumer receiver) + public static boolean flatCallAndCallback(Callable> call, BiConsumer receiver) { AsyncChain v; try @@ -198,9 +215,10 @@ public static void flatCallAndCallback(Callable> cal catch (Throwable t) { receiver.accept(null, t); - return; + return false; } v.begin(receiver); + return true; } public static Cancellable execute(Executor executor, RunOrFail runOrFail) diff --git a/accord-core/src/main/java/accord/utils/async/AsyncChains.java b/accord-core/src/main/java/accord/utils/async/AsyncChains.java index c5ed93edcf..855fac6c43 100644 --- a/accord-core/src/main/java/accord/utils/async/AsyncChains.java +++ b/accord-core/src/main/java/accord/utils/async/AsyncChains.java @@ -595,6 +595,18 @@ public static AsyncChain chain(AsyncExecutor executor, Runnable run) }; } + public static AsyncChain continuationChain(AsyncExecutor executor, Runnable run) + { + return new AsyncChains.Head<>() + { + @Override + protected @Nullable Cancellable start(BiConsumer callback) + { + return executor.executeContinuation(new AsyncCallbacks.RunAndCallback(run, callback)); + } + }; + } + public static AsyncChain chain(AsyncExecutor executor, Callable call) { return new AsyncChains.Head<>() diff --git a/accord-core/src/test/java/accord/impl/LocalListenersTest.java b/accord-core/src/test/java/accord/impl/LocalListenersTest.java index 8ac0a0bbdc..27ade1f6d2 100644 --- a/accord-core/src/test/java/accord/impl/LocalListenersTest.java +++ b/accord-core/src/test/java/accord/impl/LocalListenersTest.java @@ -400,27 +400,11 @@ public String toString() static class TestSafeCommand extends SafeCommand { - Command current; public TestSafeCommand(TxnId txnId, SaveStatus saveStatus, Durability durability) { super(txnId); current = new TestCommand(txnId, saveStatus, durability); } - - @Override - public Command current() { return current; } - - @Override - public void markUnsafe() {} - - @Override - public boolean isUnsafe() { return false; } - - @Override - protected void set(Command command) - { - current = command; - } } static class TestCommand extends Command diff --git a/accord-core/src/test/java/accord/impl/MessageListener.java b/accord-core/src/test/java/accord/impl/MessageListener.java index f20c4e23c3..5e0cbb0813 100644 --- a/accord-core/src/test/java/accord/impl/MessageListener.java +++ b/accord-core/src/test/java/accord/impl/MessageListener.java @@ -20,7 +20,7 @@ import accord.impl.basic.NodeSink; import accord.local.Node; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.messages.Message; import accord.messages.ReadData.ReadOk; import accord.messages.Request; @@ -202,8 +202,8 @@ private static boolean containsAny(Request message) if (message instanceof RouteRequest) return txnIdFilter.contains(((RouteRequest) message).txnId); // this includes txn that depend on the txn, should this limit for the first txnId? - if (message instanceof PreLoadContext) - return ((PreLoadContext) message).txnIds().stream().anyMatch(txnIdFilter::contains); + if (message instanceof ExecutionContext) + return ((ExecutionContext) message).txnIds().stream().anyMatch(txnIdFilter::contains); return false; } diff --git a/accord-core/src/test/java/accord/impl/RemoteListenersTest.java b/accord-core/src/test/java/accord/impl/RemoteListenersTest.java index 85ccedc32a..b555c7b795 100644 --- a/accord-core/src/test/java/accord/impl/RemoteListenersTest.java +++ b/accord-core/src/test/java/accord/impl/RemoteListenersTest.java @@ -48,7 +48,7 @@ import accord.local.CommandStores; import accord.local.Node; import accord.local.NodeCommandStoreService; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.RedundantBefore; import accord.local.SafeCommand; import accord.local.SafeCommandStore; @@ -65,7 +65,10 @@ import accord.utils.AccordGens; import accord.utils.RandomSource; import accord.utils.RandomTestRunner; +import accord.utils.async.AsyncCallbacks; import accord.utils.async.AsyncChain; +import accord.utils.async.Cancellable; + import org.agrona.collections.IntHashSet; import org.agrona.collections.ObjectHashSet; @@ -411,11 +414,12 @@ public Journal.Replayer replayer(AbstractReplayer.Mode mode) @Override protected void ensureDurable(Ranges ranges, RedundantBefore onCommandStoreDurable) {} @Override public boolean inStore() { return true; } - @Override public AsyncChain chain(PreLoadContext context, Consumer consumer) { return null; } - @Override public AsyncChain chain(PreLoadContext context, Function apply) { return null; } + @Override public AsyncChain chain(ExecutionContext context, Consumer consumer) { return null; } + @Override public AsyncChain chain(ExecutionContext context, Function apply) { return null; } @Override public void shutdown() {} @Override public AsyncChain chain(Callable call) { return null; } @Override public void execute(Runnable run) { throw new UnsupportedOperationException(); } + @Override public Cancellable executeContinuation(AsyncCallbacks.RunOrFail run) { throw new UnsupportedOperationException(); } } static class TestSafeCommandStore extends SafeCommandStore @@ -434,8 +438,8 @@ static class TestSafeCommandStore extends SafeCommandStore @Override protected SafeCommandsForKey getInternal(RoutingKey key) { return null;} @Override protected SafeCommandsForKey ifLoadedInternal(RoutingKey key) { return null;} - @Override public PreLoadContext canExecute(PreLoadContext context) { return null;} - @Override public PreLoadContext context() { return null; } + @Override public ExecutionContext canExecute(ExecutionContext context) { return null;} + @Override public ExecutionContext context() { return null; } @Override protected void persistFieldUpdates() {} @Override diff --git a/accord-core/src/test/java/accord/impl/basic/Cluster.java b/accord-core/src/test/java/accord/impl/basic/Cluster.java index 43cf74c888..2e3ef26c65 100644 --- a/accord-core/src/test/java/accord/impl/basic/Cluster.java +++ b/accord-core/src/test/java/accord/impl/basic/Cluster.java @@ -561,8 +561,11 @@ public void validate(CommandStore commandStore, Command command, boolean isWrite return; List> diff = ReflectionUtils.recursiveEquals(command, reconstructed); - if (!diff.isEmpty() && command.saveStatus().compareTo(SaveStatus.Erased) >= 0) - diff.removeIf(v -> v.path.equals(".participants.")); + if (!diff.isEmpty()) + { + if (command.saveStatus().compareTo(SaveStatus.Erased) >= 0 || command.participants.equals(reconstructed.participants)) + diff.removeIf(v -> v.path.equals(".participants.")); + } Invariants.require(diff.isEmpty(), "Commands did not match: expected %s, given %s on %s, diff %s", command, reconstructed, commandStore, new LazyToString(() -> String.join("\n", Iterables.transform(diff, Object::toString)))); } diff --git a/accord-core/src/test/java/accord/impl/basic/DelayedCommandStores.java b/accord-core/src/test/java/accord/impl/basic/DelayedCommandStores.java index bdc52ee070..a703e7d202 100644 --- a/accord-core/src/test/java/accord/impl/basic/DelayedCommandStores.java +++ b/accord-core/src/test/java/accord/impl/basic/DelayedCommandStores.java @@ -51,7 +51,7 @@ import accord.local.CommandStore; import accord.local.CommandStores; import accord.local.NodeCommandStoreService; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.RedundantBefore; import accord.local.SafeCommandStore; import accord.local.ShardDistributor; @@ -321,13 +321,13 @@ public boolean inStore() } @Override - public AsyncChain chain(PreLoadContext context, Consumer consumer) + public AsyncChain chain(ExecutionContext context, Consumer consumer) { return chain(context, i -> { consumer.accept(i); return null; }); } @Override - public AsyncChain chain(PreLoadContext context, Function function) + public AsyncChain chain(ExecutionContext context, Function function) { return submit(newTask(context, cfrLoad(context), function)); } @@ -356,7 +356,7 @@ private void execute(DelayedTask task) runNextTask(); } - private DelayedTask newTask(PreLoadContext context, @Nullable CommandsForRangeLoad cfrLoad, Function function) + private DelayedTask newTask(ExecutionContext context, @Nullable CommandsForRangeLoad cfrLoad, Function function) { Pending origin = Pending.Global.activeOrigin(); if (RecurringPendingRunnable.isRecurring(origin) && context.primaryTxnId() != null && !context.primaryTxnId().isSystemTxn()) @@ -421,7 +421,7 @@ public void shutdown() } @Override - protected InMemorySafeStore createSafeStore(PreLoadContext context, CommandsForRangeLoad cfrLoad, Map commands, Map commandsForKeys) + protected InMemorySafeStore createSafeStore(ExecutionContext context, CommandsForRangeLoad cfrLoad, Map commands, Map commandsForKeys) { return new DelayedSafeStore(this, context, cfrLoad, commands, commandsForKeys, cacheLoading); } @@ -434,7 +434,7 @@ public static class DelayedSafeStore extends InMemoryCommandStore.InMemorySafeSt private final CacheLoading cacheLoading; public DelayedSafeStore(DelayedCommandStore commandStore, - PreLoadContext context, + ExecutionContext context, CommandsForRangeLoad cfrLoad, Map commands, Map commandsForKey, diff --git a/accord-core/src/test/java/accord/impl/basic/RandomDelayQueue.java b/accord-core/src/test/java/accord/impl/basic/RandomDelayQueue.java index 793247fa69..b448714f12 100644 --- a/accord-core/src/test/java/accord/impl/basic/RandomDelayQueue.java +++ b/accord-core/src/test/java/accord/impl/basic/RandomDelayQueue.java @@ -35,6 +35,7 @@ import accord.burn.random.FrequentLargeRange; import accord.impl.basic.DelayedCommandStores.DelayedCommandStore.DelayedTask; +import accord.utils.IntrusiveHeapNode; import accord.utils.IntrusivePriorityHeap; import accord.utils.Invariants; import accord.utils.RandomSource; @@ -62,7 +63,7 @@ public RandomDelayQueue get() } } - static class Item extends IntrusivePriorityHeap.Node implements Comparable + static class Item extends IntrusiveHeapNode implements Comparable { long time; int seq; @@ -115,9 +116,9 @@ public String toString() static class Queue extends IntrusivePriorityHeap { @Override public int compare(Item o1, Item o2) { return o1.compareTo(o2); } - @Override protected void append(Item node) { super.append(node); } - @Override protected void remove(Item node) { super.remove(node); } - @Override protected boolean contains(Item node) { return super.contains(node); } + void append(Item node) { super.appendNode(node); } + void remove(Item node) { super.removeNode(node); } + boolean contains(Item node) { return super.containsNode(node); } @Override protected void clear() { super.clear(); } @Override protected Stream stream() { return super.stream(); } Item poll() diff --git a/accord-core/src/test/java/accord/impl/basic/SimulatedDelayedExecutorService.java b/accord-core/src/test/java/accord/impl/basic/SimulatedDelayedExecutorService.java index 26e8031c21..218d40c1d3 100644 --- a/accord-core/src/test/java/accord/impl/basic/SimulatedDelayedExecutorService.java +++ b/accord-core/src/test/java/accord/impl/basic/SimulatedDelayedExecutorService.java @@ -42,6 +42,12 @@ public AsyncChain chain(Runnable run) return AsyncChains.chain(this, run); } + @Override + public AsyncChain continuationChain(Runnable run) + { + return AsyncChains.continuationChain(this, run); + } + @Override public AsyncChain chain(Callable call) { diff --git a/accord-core/src/test/java/accord/impl/list/ListAgent.java b/accord-core/src/test/java/accord/impl/list/ListAgent.java index 182a0d8a07..b7f0742ffa 100644 --- a/accord-core/src/test/java/accord/impl/list/ListAgent.java +++ b/accord-core/src/test/java/accord/impl/list/ListAgent.java @@ -301,7 +301,7 @@ public long selfExpiresAt(TxnId txnId, MessageType messageType, TimeUnit unit) public AsyncResult snapshot(InMemoryCommandStore commandStore) { Snapshotter snapshotter = snapshotters.computeIfAbsent(commandStore.id(), ignore -> new Snapshotter<>(scheduler, rnd)); - return commandStore.submit((PreLoadContext.Empty)() -> "Snapshot", safeStore -> snapshotter.snapshot(false, Snapshot.snapshot(commandStore))) + return commandStore.submit((ExecutionContext.Empty)() -> "Snapshot", safeStore -> snapshotter.snapshot(false, Snapshot.snapshot(commandStore))) .flatMap(Function.identity()); } diff --git a/accord-core/src/test/java/accord/impl/list/ListFetchCoordinator.java b/accord-core/src/test/java/accord/impl/list/ListFetchCoordinator.java index f77359640f..70c9edeb24 100644 --- a/accord-core/src/test/java/accord/impl/list/ListFetchCoordinator.java +++ b/accord-core/src/test/java/accord/impl/list/ListFetchCoordinator.java @@ -27,8 +27,9 @@ import accord.coordinate.tracking.AbstractTracker; import accord.impl.AbstractFetchCoordinator; import accord.local.CommandStore; +import accord.local.ExecutionContext.Empty; import accord.local.Node; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.SafeCommandStore; import accord.primitives.SyncPoint; import accord.primitives.PartialDeps; @@ -47,7 +48,7 @@ public class ListFetchCoordinator extends AbstractFetchCoordinator public ListFetchCoordinator(Node node, Ranges ranges, SyncPoint syncPoint, DataStore.FetchRanges fetchRanges, CommandStore commandStore, ListStore listStore) throws TopologyException { - super(node, node.someSequentialExecutor(), ranges, syncPoint, fetchRanges, commandStore); + super(node, node.someExclusiveExecutor(), ranges, syncPoint, fetchRanges, commandStore); this.listStore = listStore; } @@ -64,7 +65,7 @@ protected void onReadOk(Node.Id from, CommandStore commandStore, Data data, Rang return; ListData listData = (ListData) data; - persisting.add(commandStore.chain((PreLoadContext.Empty) () -> "List Fetch", safeStore -> { + persisting.add(commandStore.chain((Empty) () -> "List Fetch", safeStore -> { listData.forEach((key, value) -> listStore.writeUnsafe(key, value)); }).flatMapResult(ignore -> listStore.snapshot(true)).invoke((success, fail) -> { if (fail == null) success(from, received); diff --git a/accord-core/src/test/java/accord/impl/list/ListRequest.java b/accord-core/src/test/java/accord/impl/list/ListRequest.java index 8afc122f2a..11ac116217 100644 --- a/accord-core/src/test/java/accord/impl/list/ListRequest.java +++ b/accord-core/src/test/java/accord/impl/list/ListRequest.java @@ -92,7 +92,7 @@ static class CheckOnResult extends CheckShards> int count = 0; protected CheckOnResult(Node node, TxnId txnId, RoutingKey homeKey, BiConsumer callback) throws TopologyException { - super(node, node.someSequentialExecutor(), txnId, txnId.is(Key) ? RoutingKeys.of(homeKey) : Ranges.of(homeKey.asRange()), IncludeInfo.All, null, NotKnownToBeInvalid, callback); + super(node, node.someExclusiveExecutor(), txnId, txnId.is(Key) ? RoutingKeys.of(homeKey) : Ranges.of(homeKey.asRange()), IncludeInfo.All, null, NotKnownToBeInvalid, callback); } static void checkOnResult(Node node, TxnId txnId, RoutingKey homeKey, BiConsumer callback) diff --git a/accord-core/src/test/java/accord/impl/list/ListStore.java b/accord-core/src/test/java/accord/impl/list/ListStore.java index 36c1cdd97d..e737ee53a4 100644 --- a/accord-core/src/test/java/accord/impl/list/ListStore.java +++ b/accord-core/src/test/java/accord/impl/list/ListStore.java @@ -36,7 +36,7 @@ import accord.local.CommandStore; import accord.local.CommandStores; import accord.local.Node; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.RedundantBefore; import accord.local.SafeCommandStore; import accord.primitives.Range; @@ -169,7 +169,7 @@ public void ensureDurable(CommandStore commandStore, RedundantBefore onSuccess, if (commandStore.node().isReplaying()) return; snapshot(false).invoke((success, fail) -> { - if (fail == null) commandStore.execute((PreLoadContext.Empty)()->"Report DataStore Durable", safeStore -> safeStore.reportDurable(onSuccess, flags)); + if (fail == null) commandStore.execute((ExecutionContext.Empty)()->"Report DataStore Durable", safeStore -> safeStore.reportDurable(onSuccess, flags)); }); } diff --git a/accord-core/src/test/java/accord/impl/mock/MockStore.java b/accord-core/src/test/java/accord/impl/mock/MockStore.java index 2546e232dc..019f15a228 100644 --- a/accord-core/src/test/java/accord/impl/mock/MockStore.java +++ b/accord-core/src/test/java/accord/impl/mock/MockStore.java @@ -27,7 +27,7 @@ import accord.api.Write; import accord.local.CommandStore; import accord.local.Node; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.RedundantBefore; import accord.local.SafeCommandStore; import accord.primitives.Participants; @@ -167,6 +167,6 @@ public FetchResult sync(Node node, SafeCommandStore safeStore, Ranges ranges, Sy @Override public void ensureDurable(CommandStore commandStore, RedundantBefore reportOnSuccess, int flags) { - commandStore.execute((PreLoadContext.Empty)() -> "Report CommandStore Durable", safeStore -> safeStore.reportDurable(reportOnSuccess, flags)); + commandStore.execute((ExecutionContext.Empty)() -> "Report CommandStore Durable", safeStore -> safeStore.reportDurable(reportOnSuccess, flags)); } } diff --git a/accord-core/src/test/java/accord/local/ImmutableCommandTest.java b/accord-core/src/test/java/accord/local/ImmutableCommandTest.java index 18ed273809..59db835beb 100644 --- a/accord-core/src/test/java/accord/local/ImmutableCommandTest.java +++ b/accord-core/src/test/java/accord/local/ImmutableCommandTest.java @@ -134,7 +134,7 @@ void noConflictWitnessTest() } commands.execute(() -> { - SafeCommandStore safeStore = commands.beginOperation(PreLoadContext.contextFor(txnId, "Test"), null); + SafeCommandStore safeStore = commands.beginOperation(ExecutionContext.unsequenced(txnId, "Test"), null); try { StoreParticipants participants = StoreParticipants.update(safeStore, ROUTE, txnId.epoch(), txnId, txnId.epoch()); @@ -168,7 +168,7 @@ void supersedingEpochWitnessTest() throws ExecutionException Assertions.assertEquals(Status.NotDefined, command.status()); Assertions.assertNull(command.executeAt()); } - PreLoadContext context = PreLoadContext.contextFor(txnId, "Test"); + ExecutionContext context = ExecutionContext.unsequenced(txnId, "Test"); setTopologyEpoch(support.local, 2); node.topology().reportTopology(TopologyUtils.withEpoch(support.local.get(), 2)); @@ -177,7 +177,7 @@ void supersedingEpochWitnessTest() throws ExecutionException StoreParticipants participants = StoreParticipants.update(safeStore, ROUTE, txnId.epoch(), txnId, 2); Commands.preaccept(safeStore, safeStore.get(txnId, participants), participants, txnId, txn.slice(FULL_RANGES, true), null, false); })); - commands.chain(PreLoadContext.contextFor(txnId, "Test"), safeStore -> { + commands.chain(ExecutionContext.unsequenced(txnId, "Test"), safeStore -> { Command command = safeStore.get(txnId).current(); Assertions.assertEquals(Status.PreAccepted, command.status()); Assertions.assertEquals(expectedTimestamp, command.executeAt()); diff --git a/accord-core/src/test/java/accord/local/MaybeExecuteAdapterTest.java b/accord-core/src/test/java/accord/local/MaybeExecuteAdapterTest.java index b0ea9bd370..d6b712534c 100644 --- a/accord-core/src/test/java/accord/local/MaybeExecuteAdapterTest.java +++ b/accord-core/src/test/java/accord/local/MaybeExecuteAdapterTest.java @@ -255,7 +255,7 @@ private static void runMaybeExecute(InMemoryCommandStore.Synchronized commands, Recorder rec) { commands.execute(() -> { - SafeCommandStore safeStore = commands.beginOperation(PreLoadContext.contextFor(txnId, "Test"), null); + SafeCommandStore safeStore = commands.beginOperation(ExecutionContext.unsequenced(txnId, "Test"), null); try { SafeCommand safeCommand = safeStore.unsafeGet(txnId); diff --git a/accord-core/src/test/java/accord/local/cfk/CommandsForKeyTest.java b/accord-core/src/test/java/accord/local/cfk/CommandsForKeyTest.java index 5836a44b29..8676569aca 100644 --- a/accord-core/src/test/java/accord/local/cfk/CommandsForKeyTest.java +++ b/accord-core/src/test/java/accord/local/cfk/CommandsForKeyTest.java @@ -58,11 +58,10 @@ import accord.local.CommandBuilder; import accord.local.Command; import accord.local.CommandStore; -import accord.local.CommandStores; import accord.local.CommandStores.RangesForEpoch; import accord.local.Node; import accord.local.NodeCommandStoreService; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.RedundantBefore; import accord.local.SafeCommand; import accord.local.SafeCommandStore; @@ -91,6 +90,7 @@ import accord.utils.DefaultRandom; import accord.utils.Invariants; import accord.utils.RandomSource; +import accord.utils.async.AsyncCallbacks; import accord.utils.async.AsyncChain; import accord.utils.async.AsyncChains; import accord.utils.async.AsyncResults; @@ -679,7 +679,7 @@ private static void test(long seed, int minCount) result = prev.update(safeStore, update.next); safeCfk.set(result.cfk()); if (rnd.decide(pruneChance)) - safeCfk.set(safeCfk.current.maybePrune(pruneInterval, pruneHlcDelta)); + safeCfk.set(safeCfk.current().maybePrune(pruneInterval, pruneHlcDelta)); result.postProcess(safeStore, prev, update.next, canon, false); } @@ -698,53 +698,26 @@ private static void test(long seed, int minCount) } } - static class TestSafeCommand extends SafeCommand { final Canon canon; - Command current; + Command prev; public TestSafeCommand(TxnId txnId, Canon canon, Command command) { super(txnId); this.canon = canon; - current = command; - } - - @Override - public Command current() { return current; } - - @Override - public void markUnsafe() {} - - @Override - public boolean isUnsafe() { return false; } - - @Override - protected void set(Command command) - { - canon.set(current, command); - current = command; + current = prev = command; } } static class TestSafeCommandsForKey extends SafeCommandsForKey { - CommandsForKey current; public TestSafeCommandsForKey(CommandsForKey cfk) { super(cfk.key()); current = cfk; } - @Override - public CommandsForKey current() { return current; } - - @Override - protected void set(CommandsForKey command) - { - current = command; - } - @Override public void overrideSink(NotifySink overrideSink) { @@ -819,7 +792,7 @@ public SafeCommand ifInitialised(TxnId txnId) @Override public SafeCommand ifLoadedAndInitialised(TxnId txnId) { - if (txnId.compareTo(cfk.current.prunedBefore()) < 0) + if (txnId.compareTo(cfk.current().prunedBefore()) < 0) return null; return getInternal(txnId); @@ -846,13 +819,13 @@ protected SafeCommandsForKey ifLoadedInternal(RoutingKey key) } @Override - public PreLoadContext canExecute(PreLoadContext context) + public ExecutionContext canExecute(ExecutionContext context) { return context; } @Override - public PreLoadContext context() + public ExecutionContext context() { return null; } @@ -1011,7 +984,7 @@ public Journal.Replayer replayer(AbstractReplayer.Mode mode) @Override protected void ensureDurable(Ranges ranges, RedundantBefore onDataStoreDurable) {} @Override - public AsyncChain chain(PreLoadContext context, Consumer consumer) + public AsyncChain chain(ExecutionContext context, Consumer consumer) { return new AsyncChains.Head<>() { @@ -1032,7 +1005,7 @@ public void execute(Runnable run) } @Override - public AsyncChain chain(PreLoadContext context, Function apply) + public AsyncChain chain(ExecutionContext context, Function apply) { throw new UnsupportedOperationException(); } diff --git a/accord-core/src/test/java/accord/messages/PreAcceptTest.java b/accord-core/src/test/java/accord/messages/PreAcceptTest.java index f590fa71b5..2875a27201 100644 --- a/accord-core/src/test/java/accord/messages/PreAcceptTest.java +++ b/accord-core/src/test/java/accord/messages/PreAcceptTest.java @@ -40,7 +40,7 @@ import accord.local.cfk.CommandsForKey; import accord.local.Node; import accord.local.Node.Id; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.primitives.Status; import accord.primitives.Ballot; import accord.primitives.FullRoute; @@ -106,7 +106,7 @@ void initialCommandTest() throws ExecutionException clock.increment(10); preAccept.process(node, ID2, REPLY_CONTEXT); - commandStore.chain(PreLoadContext.contextFor(txnId, txn.keys().toParticipants(), SYNC, WRITE, "Test"), safeStore -> { + commandStore.chain(ExecutionContext.unsequencedWrite(txnId, txn.keys().toParticipants(), "Test"), safeStore -> { CommandsForKey cfk = safeStore.get(key.toUnseekable()).current(); TxnId commandId = cfk.get(0).plainTxnId(); Command command = safeStore.ifInitialised(commandId).current(); @@ -275,7 +275,7 @@ void supersedingEpochPrecludesFastPath() throws ExecutionException clock.increment(10); preAccept.process(node, ID2, REPLY_CONTEXT); - commandStore.chain(PreLoadContext.contextFor(txnId, txn.keys().toParticipants(), SYNC, WRITE, "Test"), safeStore -> { + commandStore.chain(ExecutionContext.unsequencedWrite(txnId, txn.keys().toParticipants(), "Test"), safeStore -> { CommandsForKey cfk = safeStore.get(key.toUnseekable()).current(); TxnId commandId = cfk.get(0).plainTxnId(); Command command = safeStore.ifInitialised(commandId).current(); diff --git a/accord-core/src/test/java/accord/messages/ReadDataTest.java b/accord-core/src/test/java/accord/messages/ReadDataTest.java index a508c0e4c7..daadaa65c8 100644 --- a/accord-core/src/test/java/accord/messages/ReadDataTest.java +++ b/accord-core/src/test/java/accord/messages/ReadDataTest.java @@ -46,7 +46,7 @@ import accord.local.Command; import accord.local.CommandStore; import accord.local.Node; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.SafeCommand; import accord.primitives.SaveStatus; import accord.local.StoreParticipants; @@ -153,7 +153,7 @@ public void commitObsoleteFromTracker() { // status=Commit, will listen waiting for ReadyToExecute; obsolete marked by status listener test(state -> { - state.forEach(store -> check(store.chain(PreLoadContext.contextFor(state.txnId, "Test"), safe -> { + state.forEach(store -> check(store.chain(ExecutionContext.unsequenced(state.txnId, "Test"), safe -> { CheckedCommands.preaccept(safe, state.txnId, state.partialTxn, state.route); CheckedCommands.accept(safe, state.txnId, Ballot.ZERO, state.partialRoute, state.executeAt, state.deps); @@ -190,7 +190,7 @@ public void mapReduceMarksObsolete() state.readyToExecute(store); store = stores.get(1); - check(store.chain(PreLoadContext.contextFor(state.txnId, "Test"), safeStore -> { + check(store.chain(ExecutionContext.unsequenced(state.txnId, "Test"), safeStore -> { StoreParticipants participants = StoreParticipants.notAccept(safeStore, state.route, state.txnId); SafeCommand safeCommand = safeStore.get(state.txnId, participants); Command prev = safeCommand.current(); @@ -208,7 +208,7 @@ public void mapReduceAllStageMarksObsolete() { test(state -> { List stores = stores(state); - stores.forEach(store -> check(store.chain(PreLoadContext.contextFor(state.txnId, "Test"), safeStore -> { + stores.forEach(store -> check(store.chain(ExecutionContext.unsequenced(state.txnId, "Test"), safeStore -> { StoreParticipants participants = StoreParticipants.notAccept(safeStore, state.route, state.txnId); SafeCommand command = safeStore.get(state.txnId, participants); command.commitInvalidated(safeStore); @@ -277,7 +277,7 @@ private static class State void readyToExecute(CommandStore store) { - check(store.chain(PreLoadContext.contextFor(txnId, "Test"), safe -> { + check(store.chain(ExecutionContext.unsequenced(txnId, "Test"), safe -> { CheckedCommands.preaccept(safe, txnId, partialTxn, route); CheckedCommands.accept(safe, txnId, Ballot.ZERO, partialRoute, executeAt, deps); CheckedCommands.commit(safe, SaveStatus.Stable, Ballot.ZERO, txnId, route, partialTxn, executeAt, deps); @@ -301,7 +301,7 @@ AsyncResults.SettableResult apply() Mockito.when(write.apply(any(), any(), any(), any(), any())).thenAnswer(mock -> writeResult.chain()); Writes writes = new Writes(txnId, executeAt, keys, write); - forEach(store -> check(store.chain(PreLoadContext.contextFor(txnId, "Test"), safe -> { + forEach(store -> check(store.chain(ExecutionContext.unsequenced(txnId, "Test"), safe -> { CheckedCommands.apply(safe, txnId, route, executeAt, deps, partialTxn, writes, Mockito.mock(PersistableResult.class)); }))); return writeResult; diff --git a/accord-core/src/test/java/accord/utils/SortedArraysTest.java b/accord-core/src/test/java/accord/utils/SortedArraysTest.java index 782e99a9fa..b9f6d15c9a 100644 --- a/accord-core/src/test/java/accord/utils/SortedArraysTest.java +++ b/accord-core/src/test/java/accord/utils/SortedArraysTest.java @@ -205,6 +205,13 @@ public void testLinearIntersection() }); } + @Test + public void testLinearIntersectionAdhoc() + { + int[] intersection = SortedArrays.linearIntersection(new int[] {0, 2, 4, 6}, 0, 4, new int[] {6, 7}, 0, 2, new ArrayBuffers.IntBufferCache(4, 1 << 14)); + Assertions.assertArrayEquals(new int[] {6}, intersection); + } + @Test public void testLinearIntersectionWithSubset() { diff --git a/accord-maelstrom/src/main/java/accord/maelstrom/MaelstromStore.java b/accord-maelstrom/src/main/java/accord/maelstrom/MaelstromStore.java index 0ed63b3b67..2a8fce61f2 100644 --- a/accord-maelstrom/src/main/java/accord/maelstrom/MaelstromStore.java +++ b/accord-maelstrom/src/main/java/accord/maelstrom/MaelstromStore.java @@ -25,7 +25,7 @@ import accord.api.DataStore; import accord.local.CommandStore; import accord.local.Node; -import accord.local.PreLoadContext; +import accord.local.ExecutionContext; import accord.local.RedundantBefore; import accord.local.SafeCommandStore; import accord.primitives.Ranges; @@ -64,5 +64,5 @@ public FetchResult image(Node node, SafeCommandStore safeStore, Ranges ranges, S @Override public void ensureDurable(CommandStore commandStore, RedundantBefore reportOnSuccess, int flags) { - commandStore.execute((PreLoadContext.Empty)() -> "Report CommandStore Durable", safeStore -> safeStore.reportDurable(reportOnSuccess, flags)); + commandStore.execute((ExecutionContext.Empty)() -> "Report CommandStore Durable", safeStore -> safeStore.reportDurable(reportOnSuccess, flags)); }}