| /* |
| * 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 java.util.Objects; |
| |
| import com.google.common.collect.ImmutableSortedSet; |
| |
| import accord.api.Result; |
| import accord.api.RoutingKey; |
| import accord.api.VisibleForImplementation; |
| import accord.local.Status.Durability; |
| import accord.local.Status.Known; |
| import accord.primitives.Ballot; |
| import accord.primitives.Deps; |
| import accord.primitives.FullRoute; |
| import accord.primitives.Keys; |
| import accord.primitives.PartialDeps; |
| import accord.primitives.PartialTxn; |
| import accord.primitives.Route; |
| import accord.primitives.Timestamp; |
| import accord.primitives.Txn; |
| import accord.primitives.TxnId; |
| import accord.primitives.Writes; |
| import accord.utils.ImmutableBitSet; |
| import accord.utils.IndexedQuadConsumer; |
| import accord.utils.Invariants; |
| import accord.utils.SimpleBitSet; |
| import javax.annotation.Nullable; |
| |
| import static accord.local.Command.AbstractCommand.validate; |
| import static accord.local.Listeners.Immutable.EMPTY; |
| import static accord.local.SaveStatus.Uninitialised; |
| import static accord.local.Status.Durability.Local; |
| import static accord.local.Status.Durability.NotDurable; |
| import static accord.local.Status.Durability.ShardUniversal; |
| import static accord.local.Status.Durability.UniversalOrInvalidated; |
| import static accord.local.Status.Invalidated; |
| import static accord.local.Status.KnownExecuteAt.ExecuteAtKnown; |
| import static accord.local.Status.Stable; |
| import static accord.utils.Invariants.illegalState; |
| import static accord.utils.SortedArrays.forEachIntersection; |
| import static accord.utils.Utils.ensureImmutable; |
| import static accord.utils.Utils.ensureMutable; |
| import static java.lang.String.format; |
| |
| public abstract class Command implements CommonAttributes |
| { |
| interface Listener |
| { |
| void onChange(SafeCommandStore safeStore, SafeCommand safeCommand); |
| |
| /** |
| * Scope needed to run onChange |
| */ |
| PreLoadContext listenerPreLoadContext(TxnId caller); |
| } |
| |
| public interface DurableAndIdempotentListener extends Listener |
| { |
| } |
| |
| public interface TransientListener extends Listener |
| { |
| } |
| |
| public static class ProxyListener implements DurableAndIdempotentListener |
| { |
| protected final TxnId listenerId; |
| |
| public ProxyListener(TxnId listenerId) |
| { |
| this.listenerId = listenerId; |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| ProxyListener that = (ProxyListener) o; |
| return listenerId.equals(that.listenerId); |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return Objects.hash(listenerId); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "ListenerProxy{" + listenerId + '}'; |
| } |
| |
| public TxnId txnId() |
| { |
| return listenerId; |
| } |
| |
| @Override |
| public void onChange(SafeCommandStore safeStore, SafeCommand safeCommand) |
| { |
| Commands.listenerUpdate(safeStore, safeStore.get(listenerId), safeCommand); |
| } |
| |
| @Override |
| public PreLoadContext listenerPreLoadContext(TxnId caller) |
| { |
| return PreLoadContext.contextFor(listenerId, caller, Keys.EMPTY); |
| } |
| } |
| |
| private static Durability durability(Durability durability, SaveStatus status) |
| { |
| if (status.compareTo(SaveStatus.PreApplied) >= 0 && !status.hasBeen(Invalidated) && durability == NotDurable) |
| return Local; // not necessary anywhere, but helps for logical consistency |
| return durability; |
| } |
| |
| @VisibleForImplementation |
| public static class SerializerSupport |
| { |
| public static NotDefined notDefined(CommonAttributes attributes, Ballot promised) |
| { |
| return NotDefined.notDefined(attributes, promised); |
| } |
| |
| public static PreAccepted preaccepted(CommonAttributes common, Timestamp executeAt, Ballot promised) |
| { |
| return PreAccepted.preAccepted(common, executeAt, promised); |
| } |
| |
| public static Accepted accepted(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted) |
| { |
| return Accepted.accepted(common, status, executeAt, promised, accepted); |
| } |
| |
| public static Committed committed(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted, WaitingOn waitingOn) |
| { |
| return Committed.committed(common, status, executeAt, promised, accepted, waitingOn); |
| } |
| |
| public static Executed executed(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted, WaitingOn waitingOn, Writes writes, Result result) |
| { |
| return Executed.executed(common, status, executeAt, promised, accepted, waitingOn, writes, result); |
| } |
| |
| public static Truncated invalidated(TxnId txnId, Listeners.Immutable durableListeners) |
| { |
| return Truncated.invalidated(txnId, durableListeners); |
| } |
| |
| public static Truncated truncatedApply(CommonAttributes common, SaveStatus saveStatus, Timestamp executeAt, Writes writes, Result result) |
| { |
| return Truncated.truncatedApply(common, saveStatus, executeAt, writes, result); |
| } |
| |
| public static Truncated truncatedApply(CommonAttributes common, SaveStatus saveStatus, Timestamp executeAt, Writes writes, Result result, Timestamp executesAtLeast) |
| { |
| return Truncated.truncatedApply(common, saveStatus, executeAt, writes, result, executesAtLeast); |
| } |
| } |
| |
| private static SaveStatus validateCommandClass(SaveStatus status, Class<?> expected, Class<?> actual) |
| { |
| if (actual != expected) |
| { |
| throw illegalState(format("Cannot instantiate %s for status %s. %s expected", |
| actual.getSimpleName(), status, expected.getSimpleName())); |
| } |
| return status; |
| } |
| |
| private static SaveStatus validateCommandClass(TxnId txnId, SaveStatus status, Class<?> klass) |
| { |
| switch (status) |
| { |
| case Uninitialised: |
| case NotDefined: |
| return validateCommandClass(status, NotDefined.class, klass); |
| case PreAccepted: |
| return validateCommandClass(status, PreAccepted.class, klass); |
| case Accepted: |
| case AcceptedWithDefinition: |
| case AcceptedInvalidate: |
| case AcceptedInvalidateWithDefinition: |
| case PreCommitted: |
| case PreCommittedWithAcceptedDeps: |
| case PreCommittedWithDefinition: |
| case PreCommittedWithDefinitionAndAcceptedDeps: |
| return validateCommandClass(status, Accepted.class, klass); |
| case Committed: |
| case ReadyToExecute: |
| case Stable: |
| return validateCommandClass(status, Committed.class, klass); |
| case PreApplied: |
| case Applying: |
| case Applied: |
| return validateCommandClass(status, Executed.class, klass); |
| case TruncatedApply: |
| case TruncatedApplyWithDeps: |
| case TruncatedApplyWithOutcome: |
| if (txnId.kind().awaitsOnlyDeps()) |
| return validateCommandClass(status, TruncatedAwaitsOnlyDeps.class, klass); |
| case Erased: |
| case ErasedOrInvalidated: |
| case Invalidated: |
| return validateCommandClass(status, Truncated.class, klass); |
| default: |
| throw illegalState("Unhandled status " + status); |
| } |
| } |
| |
| /** |
| * @return true if this command is equivalent to {@code other}; it could also be fuller - have some of its |
| * registers be not sliced, whereas {@code other} may have them sliced |
| */ |
| public abstract boolean isEqualOrFuller(Command other); |
| |
| abstract static class AbstractCommand extends Command |
| { |
| private final TxnId txnId; |
| private final SaveStatus status; |
| private final Durability durability; |
| private final @Nullable Route<?> route; |
| private final Ballot promised; |
| private final Listeners.Immutable listeners; |
| |
| private AbstractCommand(TxnId txnId, SaveStatus status, Durability durability, @Nullable Route<?> route, Ballot promised, Listeners.Immutable listeners) |
| { |
| this.txnId = txnId; |
| this.status = validateCommandClass(txnId, status, getClass()); |
| this.durability = durability; |
| this.route = route; |
| this.promised = promised; |
| this.listeners = listeners; |
| } |
| |
| private AbstractCommand(CommonAttributes common, SaveStatus status, Ballot promised) |
| { |
| this.txnId = common.txnId(); |
| this.status = validateCommandClass(txnId, status, getClass()); |
| this.durability = common.durability(); |
| this.route = common.route(); |
| this.promised = promised; |
| this.listeners = common.durableListeners(); |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| Command command = (Command) o; |
| return txnId().equals(command.txnId()) |
| && saveStatus() == command.saveStatus() |
| && durability() == command.durability() |
| && Objects.equals(route(), command.route()) |
| && Objects.equals(promised(), command.promised()) |
| && Objects.equals(durableListeners(), command.durableListeners()); |
| } |
| |
| @Override |
| public boolean isEqualOrFuller(Command command) |
| { |
| if (this == command) return true; |
| if (command == null || getClass() != command.getClass()) return false; |
| return txnId().equals(command.txnId()) |
| && saveStatus() == command.saveStatus() |
| && durability() == command.durability() |
| && Objects.equals(route(), command.route()) |
| && Objects.equals(promised(), command.promised()) |
| && durableListeners().containsAll(command.durableListeners()); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "Command@" + System.identityHashCode(this) + '{' + txnId + ':' + status + '}'; |
| } |
| |
| @Override |
| public TxnId txnId() |
| { |
| return txnId; |
| } |
| |
| @Override |
| public final Route<?> route() |
| { |
| return route; |
| } |
| |
| @Override |
| public Ballot promised() |
| { |
| return promised; |
| } |
| |
| @Override |
| public Durability durability() |
| { |
| return Command.durability(durability, saveStatus()); |
| } |
| |
| @Override |
| public Listeners.Immutable durableListeners() |
| { |
| return listeners == null ? EMPTY : listeners; |
| } |
| |
| @Override |
| public final SaveStatus saveStatus() |
| { |
| return status; |
| } |
| |
| public static <T extends AbstractCommand> T validate(T validate) |
| { |
| Known known = validate.known(); |
| switch (known.route) |
| { |
| default: throw new AssertionError("Unhandled KnownRoute: " + known.route); |
| case Maybe: break; |
| case Full: Invariants.checkState(Route.isFullRoute(validate.route())); break; |
| case Covering: Invariants.checkState(Route.isRoute(validate.route())); break; |
| } |
| { |
| PartialTxn partialTxn = validate.partialTxn(); |
| switch (known.definition) |
| { |
| default: throw new AssertionError("Unhandled Definition: " + known.definition); |
| case DefinitionErased: |
| case DefinitionUnknown: |
| case NoOp: |
| Invariants.checkState(partialTxn == null); |
| break; |
| case DefinitionKnown: |
| Invariants.checkState(partialTxn != null); |
| break; |
| } |
| } |
| { |
| Timestamp executeAt = validate.executeAt(); |
| switch (known.executeAt) |
| { |
| default: throw new AssertionError("Unhandled KnownExecuteAt: " + known.executeAt); |
| case ExecuteAtErased: |
| case ExecuteAtUnknown: |
| break; |
| case ExecuteAtProposed: |
| case ExecuteAtKnown: |
| Invariants.checkState(executeAt != null && !executeAt.equals(Timestamp.NONE)); |
| break; |
| case NoExecuteAt: |
| Invariants.checkState(executeAt.equals(Timestamp.NONE)); |
| break; |
| } |
| } |
| { |
| PartialDeps deps = validate.partialDeps(); |
| switch (known.deps) |
| { |
| default: throw new AssertionError("Unhandled KnownDeps: " + known.deps); |
| case DepsUnknown: |
| case DepsErased: |
| case NoDeps: |
| Invariants.checkState(deps == null); |
| break; |
| case DepsProposed: |
| case DepsCommitted: |
| case DepsKnown: |
| Invariants.checkState(deps != null); |
| break; |
| } |
| } |
| { |
| Writes writes = validate.writes(); |
| Result result = validate.result(); |
| switch (known.outcome) |
| { |
| default: throw new AssertionError("Unhandled Outcome: " + known.outcome); |
| case Apply: |
| Invariants.checkState(writes != null); |
| Invariants.checkState(result != null); |
| break; |
| case Invalidated: |
| Invariants.checkState(validate.durability().isMaybeInvalidated()); |
| case Unknown: |
| Invariants.checkState(validate.durability() != Local); |
| case Erased: |
| case WasApply: |
| Invariants.checkState(writes == null); |
| Invariants.checkState(result == null); |
| break; |
| } |
| } |
| switch (validate.saveStatus().execution) |
| { |
| case NotReady: |
| case CleaningUp: |
| break; |
| case ReadyToExclude: |
| Invariants.checkState(validate.saveStatus() != SaveStatus.Committed || validate.asCommitted().waitingOn == null); |
| break; |
| case WaitingToExecute: |
| case ReadyToExecute: |
| case Applied: |
| case Applying: |
| case WaitingToApply: |
| Invariants.checkState(validate.asCommitted().waitingOn != null); |
| break; |
| } |
| return validate; |
| } |
| } |
| |
| @Override |
| public final int hashCode() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * We require that this is a FullRoute for all states where isDefinitionKnown(). |
| * In some cases, the home shard will contain an arbitrary slice of the Route where !isDefinitionKnown(), |
| * i.e. when a non-home shard informs the home shards of a transaction to ensure forward progress. |
| * |
| * If hasBeen(Committed) this must contain the keys for both txnId.epoch and executeAt.epoch |
| * |
| * TODO (required): audit uses; do not assume non-null means it is a complete route for the shard; |
| * preferably introduce two variations so callers can declare whether they need the full shard's route |
| * or any route will do |
| */ |
| @Override |
| @Nullable |
| public abstract Route<?> route(); |
| |
| /** |
| * The command may have an incomplete route when this is false |
| */ |
| public boolean hasFullRoute() |
| { |
| return route() != null && route().kind().isFullRoute(); |
| } |
| |
| /** |
| * homeKey is a global value that defines the home shard - the one tasked with ensuring the transaction is finished. |
| * progressKey is a local value that defines the local shard responsible for ensuring progress on the transaction. |
| * This will be homeKey if it is owned by the node, and some other key otherwise. If not the home shard, the progress |
| * shard has much weaker responsibilities, only ensuring that the home shard has durably witnessed the txnId. |
| */ |
| @Nullable |
| public RoutingKey homeKey() |
| { |
| Route<?> route = route(); |
| return route == null ? null : route.homeKey(); |
| } |
| |
| @Override |
| public abstract TxnId txnId(); |
| public abstract Ballot promised(); |
| @Override |
| public abstract Durability durability(); |
| @Override |
| public abstract Listeners.Immutable<DurableAndIdempotentListener> durableListeners(); |
| public abstract SaveStatus saveStatus(); |
| |
| /** |
| * Only meaningful when txnId.kind().awaitsOnlyDeps() |
| */ |
| public Timestamp executesAtLeast() { return executeAt(); } |
| |
| static boolean isSameClass(Command command, Class<? extends Command> klass) |
| { |
| return command.getClass() == klass; |
| } |
| |
| private static void checkNewBallot(Ballot current, Ballot next, String name) |
| { |
| if (next.compareTo(current) < 0) |
| throw new IllegalArgumentException(format("Cannot update %s ballot from %s to %s. New ballot is less than current", name, current, next)); |
| } |
| |
| private static void checkPromised(Command command, Ballot ballot) |
| { |
| checkNewBallot(command.promised(), ballot, "promised"); |
| } |
| |
| private static void checkAccepted(Command command, Ballot ballot) |
| { |
| checkNewBallot(command.acceptedOrCommitted(), ballot, "accepted"); |
| } |
| |
| private static void checkSameClass(Command command, Class<? extends Command> klass, String errorMsg) |
| { |
| if (!isSameClass(command, klass)) |
| throw new IllegalArgumentException(errorMsg + format(" expected %s got %s", klass.getSimpleName(), command.getClass().getSimpleName())); |
| } |
| |
| public abstract Timestamp executeAt(); |
| public abstract Ballot acceptedOrCommitted(); |
| |
| @Override |
| public abstract PartialTxn partialTxn(); |
| |
| @Override |
| public abstract @Nullable PartialDeps partialDeps(); |
| |
| public @Nullable Writes writes() { return null; } |
| public @Nullable Result result() { return null; } |
| |
| public final Timestamp executeAtIfKnownElseTxnId() |
| { |
| if (known().executeAt == ExecuteAtKnown) |
| return executeAt(); |
| return txnId(); |
| } |
| |
| public final Timestamp executeAtIfKnown() |
| { |
| return executeAtIfKnown(null); |
| } |
| |
| public final Timestamp executeAtIfKnown(Timestamp orElse) |
| { |
| if (known().executeAt == ExecuteAtKnown) |
| return executeAt(); |
| return orElse; |
| } |
| |
| public final boolean executesInFutureEpoch() |
| { |
| return known().executeAt == ExecuteAtKnown && executeAt().epoch() > txnId().epoch(); |
| } |
| |
| public final Timestamp executeAtOrTxnId() |
| { |
| Timestamp executeAt = executeAt(); |
| return executeAt == null || executeAt.equals(Timestamp.NONE) ? txnId() : executeAt; |
| } |
| |
| public final Timestamp executeAtIfKnownOrTxnId() |
| { |
| Timestamp executeAt = executeAtIfKnown(); |
| return executeAt == null || executeAt.equals(Timestamp.NONE) ? txnId() : executeAt; |
| } |
| |
| public final Status status() |
| { |
| return saveStatus().status; |
| } |
| |
| public final Known known() |
| { |
| return saveStatus().known; |
| } |
| |
| public final boolean hasBeen(Status status) |
| { |
| return status().compareTo(status) >= 0; |
| } |
| |
| public boolean has(Known known) |
| { |
| return known.isSatisfiedBy(saveStatus().known); |
| } |
| |
| public boolean isAtLeast(SaveStatus.LocalExecution execution) |
| { |
| return saveStatus().execution.compareTo(execution) >= 0; |
| } |
| |
| public boolean is(Status status) |
| { |
| return status() == status; |
| } |
| |
| public final ProxyListener asListener() |
| { |
| return new ProxyListener(txnId()); |
| } |
| |
| public final boolean isDefined() |
| { |
| if (status().hasBeen(Status.Truncated)) |
| return false; |
| return status().hasBeen(Status.PreAccepted); |
| } |
| |
| public final PreAccepted asWitnessed() |
| { |
| return Invariants.cast(this, PreAccepted.class); |
| } |
| |
| public final boolean isAccepted() |
| { |
| return status().hasBeen(Status.AcceptedInvalidate); |
| } |
| |
| public final Accepted asAccepted() |
| { |
| return Invariants.cast(this, Accepted.class); |
| } |
| |
| public final boolean isCommitted() |
| { |
| SaveStatus saveStatus = saveStatus(); |
| return saveStatus.hasBeen(Status.Committed) && !saveStatus.hasBeen(Invalidated); |
| } |
| public final boolean isStable() |
| { |
| SaveStatus saveStatus = saveStatus(); |
| return saveStatus.hasBeen(Status.Stable) && !saveStatus.hasBeen(Invalidated); |
| } |
| |
| public final Committed asCommitted() |
| { |
| return Invariants.cast(this, Committed.class); |
| } |
| |
| public final Executed asExecuted() |
| { |
| return Invariants.cast(this, Executed.class); |
| } |
| |
| public final boolean isTruncated() |
| { |
| return status().hasBeen(Status.Truncated); |
| } |
| |
| public abstract Command updateAttributes(CommonAttributes attrs, Ballot promised); |
| |
| public final Command updateAttributes(CommonAttributes attrs) |
| { |
| return updateAttributes(attrs, promised()); |
| } |
| |
| public final Command updatePromised(Ballot promised) |
| { |
| return updateAttributes(this, promised); |
| } |
| |
| public static final class NotDefined extends AbstractCommand |
| { |
| NotDefined(TxnId txnId, SaveStatus status, Durability durability, @Nullable Route<?> route, Ballot promised, Listeners.Immutable listeners) |
| { |
| super(txnId, status, durability, route, promised, listeners); |
| } |
| |
| NotDefined(CommonAttributes common, SaveStatus status, Ballot promised) |
| { |
| super(common, status, promised); |
| } |
| |
| @Override |
| public Command updateAttributes(CommonAttributes attrs, Ballot promised) |
| { |
| return validate(new NotDefined(attrs, initialise(saveStatus()), promised)); |
| } |
| |
| public static NotDefined notDefined(CommonAttributes common, Ballot promised) |
| { |
| return validate(new NotDefined(common, SaveStatus.NotDefined, promised)); |
| } |
| |
| public static NotDefined uninitialised(TxnId txnId) |
| { |
| return validate(new NotDefined(txnId, Uninitialised, NotDurable, null, Ballot.ZERO, null)); |
| } |
| |
| @Override |
| public Timestamp executeAt() |
| { |
| return null; |
| } |
| |
| @Override |
| public Ballot promised() |
| { |
| // we can be preacceptedInvalidated, so can be promised even if not witnessed |
| return super.promised; |
| } |
| |
| @Override |
| public Ballot acceptedOrCommitted() |
| { |
| return Ballot.ZERO; |
| } |
| |
| @Override |
| public PartialTxn partialTxn() |
| { |
| return null; |
| } |
| |
| @Override |
| public @Nullable PartialDeps partialDeps() |
| { |
| return null; |
| } |
| |
| private static SaveStatus initialise(SaveStatus saveStatus) |
| { |
| return saveStatus == Uninitialised ? SaveStatus.NotDefined : saveStatus; |
| } |
| } |
| |
| public static class Truncated extends AbstractCommand |
| { |
| @Nullable final Timestamp executeAt; |
| @Nullable final Writes writes; |
| @Nullable final Result result; |
| |
| public Truncated(CommonAttributes commonAttributes, SaveStatus saveStatus, @Nullable Timestamp executeAt, @Nullable Writes writes, @Nullable Result result) |
| { |
| super(commonAttributes, saveStatus, Ballot.MAX); |
| this.executeAt = executeAt; |
| this.writes = writes; |
| this.result = result; |
| } |
| |
| public Truncated(TxnId txnId, SaveStatus saveStatus, Durability durability, @Nullable Route<?> route, @Nullable Timestamp executeAt, Listeners.Immutable listeners, @Nullable Writes writes, @Nullable Result result) |
| { |
| super(txnId, saveStatus, durability, route, Ballot.MAX, listeners); |
| this.executeAt = executeAt; |
| this.writes = writes; |
| this.result = result; |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| if (!super.equals(o)) return false; |
| Truncated that = (Truncated) o; |
| return Objects.equals(executeAt, that.executeAt) |
| && Objects.equals(writes, that.writes) |
| && Objects.equals(result, that.result); |
| } |
| |
| @Override |
| public boolean isEqualOrFuller(Command c) |
| { |
| if (this == c) return true; |
| if (c == null || getClass() != c.getClass()) return false; |
| if (!super.isEqualOrFuller(c)) return false; |
| Truncated that = (Truncated) c; |
| return Objects.equals(executeAt(), that.executeAt()) |
| && Objects.equals(writes(), that.writes()) |
| && Objects.equals(result(), that.result()); |
| } |
| |
| public static Truncated erased(Command command) |
| { |
| Durability durability = Durability.mergeAtLeast(command.durability(), UniversalOrInvalidated); |
| return erased(command.txnId(), durability, command.route()); |
| } |
| |
| public static Truncated erasedOrInvalidated(TxnId txnId, Status.Durability durability, Route<?> route) |
| { |
| return validate(new Truncated(txnId, SaveStatus.ErasedOrInvalidated, durability, route, null, EMPTY, null, null)); |
| } |
| |
| public static Truncated erased(TxnId txnId, Status.Durability durability, Route<?> route) |
| { |
| return validate(new Truncated(txnId, SaveStatus.Erased, durability, route, null, EMPTY, null, null)); |
| } |
| |
| public static Truncated truncatedApply(Command command) |
| { |
| return truncatedApply(command, null); |
| } |
| |
| public static Truncated truncatedApply(Command command, @Nullable FullRoute<?> route) |
| { |
| Invariants.checkArgument(command.known().executeAt.isDecidedAndKnownToExecute()); |
| if (route == null) route = Route.castToNonNullFullRoute(command.route()); |
| Durability durability = Durability.mergeAtLeast(command.durability(), ShardUniversal); |
| if (command.txnId().kind().awaitsOnlyDeps()) |
| { |
| Timestamp executesAtLeast = command.hasBeen(Stable) ? command.executesAtLeast() : null; |
| return validate(new TruncatedAwaitsOnlyDeps(command.txnId(), SaveStatus.TruncatedApply, durability, route, command.executeAt(), EMPTY, null, null, executesAtLeast)); |
| } |
| return validate(new Truncated(command.txnId(), SaveStatus.TruncatedApply, durability, route, command.executeAt(), EMPTY, null, null)); |
| } |
| |
| public static Truncated truncatedApplyWithOutcome(Executed command) |
| { |
| Durability durability = Durability.mergeAtLeast(command.durability(), ShardUniversal); |
| if (command.txnId().kind().awaitsOnlyDeps()) |
| return validate(new TruncatedAwaitsOnlyDeps(command.txnId(), SaveStatus.TruncatedApplyWithOutcome, durability, command.route(), command.executeAt(), EMPTY, command.writes, command.result, command.executesAtLeast())); |
| return validate(new Truncated(command.txnId(), SaveStatus.TruncatedApplyWithOutcome, durability, command.route(), command.executeAt(), EMPTY, command.writes, command.result)); |
| } |
| |
| public static Truncated truncatedApply(CommonAttributes common, SaveStatus saveStatus, Timestamp executeAt, Writes writes, Result result) |
| { |
| Invariants.checkArgument(!common.txnId().kind().awaitsOnlyDeps()); |
| Durability durability = checkTruncatedApplyInvariants(common, saveStatus, executeAt); |
| return validate(new Truncated(common.txnId(), saveStatus, durability, common.route(), executeAt, EMPTY, writes, result)); |
| } |
| |
| public static Truncated truncatedApply(CommonAttributes common, SaveStatus saveStatus, Timestamp executeAt, Writes writes, Result result, Timestamp dependencyExecutesAt) |
| { |
| Invariants.checkArgument(common.txnId().kind().awaitsOnlyDeps()); |
| Durability durability = checkTruncatedApplyInvariants(common, saveStatus, executeAt); |
| return validate(new TruncatedAwaitsOnlyDeps(common.txnId(), saveStatus, durability, common.route(), executeAt, EMPTY, writes, result, dependencyExecutesAt)); |
| } |
| |
| private static Durability checkTruncatedApplyInvariants(CommonAttributes common, SaveStatus saveStatus, Timestamp executeAt) |
| { |
| Invariants.checkArgument(executeAt != null); |
| Invariants.checkArgument(saveStatus == SaveStatus.TruncatedApply || saveStatus == SaveStatus.TruncatedApplyWithDeps || saveStatus == SaveStatus.TruncatedApplyWithOutcome); |
| return Durability.mergeAtLeast(common.durability(), ShardUniversal); |
| } |
| |
| public static Truncated invalidated(Command command) |
| { |
| Invariants.checkState(!command.hasBeen(Status.PreCommitted)); |
| // TODO (now): we shouldn't need to propagate these |
| return invalidated(command.txnId(), command.durableListeners()); |
| } |
| |
| public static Truncated invalidated(TxnId txnId, Listeners.Immutable durableListeners) |
| { |
| // TODO (expected): migrate to using null for executeAt when invalidated |
| // TODO (expected): is UniversalOrInvalidated correct here? Should we have a lower implication pure Invalidated? |
| return validate(new Truncated(txnId, SaveStatus.Invalidated, UniversalOrInvalidated, null, Timestamp.NONE, durableListeners, null, null)); |
| } |
| |
| @Override |
| public Timestamp executeAt() |
| { |
| return executeAt; |
| } |
| |
| @Override |
| public @Nullable Writes writes() |
| { |
| return writes; |
| } |
| |
| @Override |
| public @Nullable Result result() |
| { |
| return result; |
| } |
| |
| @Override |
| public Ballot acceptedOrCommitted() |
| { |
| return Ballot.MAX; |
| } |
| |
| @Override |
| public PartialTxn partialTxn() |
| { |
| return null; |
| } |
| |
| @Override |
| public @Nullable PartialDeps partialDeps() |
| { |
| return null; |
| } |
| |
| @Override |
| public Command updateAttributes(CommonAttributes attrs, Ballot promised) |
| { |
| // TODO (now): invoke listeners precisely once when we adopt this state, then we can simply return `this` |
| return validate(new Truncated(txnId(), saveStatus(), attrs.durability(), attrs.route(), executeAt, attrs.durableListeners(), writes, result)); |
| } |
| } |
| |
| public static class TruncatedAwaitsOnlyDeps extends Truncated |
| { |
| @Nullable final Timestamp executesAtLeast; |
| |
| public TruncatedAwaitsOnlyDeps(CommonAttributes commonAttributes, SaveStatus saveStatus, @Nullable Timestamp executeAt, @Nullable Writes writes, @Nullable Result result, @Nullable Timestamp executesAtLeast) |
| { |
| super(commonAttributes, saveStatus, executeAt, writes, result); |
| this.executesAtLeast = executesAtLeast; |
| } |
| |
| public TruncatedAwaitsOnlyDeps(TxnId txnId, SaveStatus saveStatus, Durability durability, @Nullable Route<?> route, @Nullable Timestamp executeAt, Listeners.Immutable listeners, @Nullable Writes writes, @Nullable Result result, @Nullable Timestamp executesAtLeast) |
| { |
| super(txnId, saveStatus, durability, route, executeAt, listeners, writes, result); |
| this.executesAtLeast = executesAtLeast; |
| } |
| |
| public Timestamp executesAtLeast() |
| { |
| return executesAtLeast; |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (!super.equals(o)) return false; |
| return Objects.equals(executesAtLeast, ((TruncatedAwaitsOnlyDeps)o).executesAtLeast); |
| } |
| |
| @Override |
| public boolean isEqualOrFuller(Command command) |
| { |
| if (!super.isEqualOrFuller(command)) return false; |
| return Objects.equals(executesAtLeast, ((TruncatedAwaitsOnlyDeps)command).executesAtLeast); |
| } |
| |
| } |
| |
| public static class PreAccepted extends AbstractCommand |
| { |
| private final Timestamp executeAt; |
| private final PartialTxn partialTxn; |
| private final @Nullable PartialDeps partialDeps; |
| |
| private PreAccepted(CommonAttributes common, SaveStatus status, Ballot promised, Timestamp executeAt) |
| { |
| this(common, status, promised, executeAt, common.partialTxn(), common.partialDeps()); |
| } |
| |
| private PreAccepted(CommonAttributes common, SaveStatus status, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps) |
| { |
| super(common, status, promised); |
| this.executeAt = executeAt; |
| this.partialTxn = partialTxn; |
| this.partialDeps = partialDeps; |
| } |
| |
| @Override |
| public Command updateAttributes(CommonAttributes attrs, Ballot promised) |
| { |
| return new PreAccepted(attrs, saveStatus(), promised, executeAt()); |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| if (!super.equals(o)) return false; |
| PreAccepted that = (PreAccepted) o; |
| return executeAt.equals(that.executeAt) |
| && Objects.equals(partialTxn, that.partialTxn) |
| && Objects.equals(partialDeps, that.partialDeps); |
| } |
| |
| @Override |
| public boolean isEqualOrFuller(Command c) |
| { |
| if (this == c) return true; |
| if (c == null || getClass() != c.getClass()) return false; |
| if (!super.isEqualOrFuller(c)) return false; |
| PreAccepted that = (PreAccepted) c; |
| if (!executeAt().equals(that.executeAt()) || !partialTxn().isEqualOrFuller(that.partialTxn())) |
| return false; |
| return (partialDeps() == null && that.partialDeps() == null) |
| || (partialDeps() != null && that.partialDeps() != null && partialDeps().isEqualOrFuller(that.partialDeps())); |
| } |
| |
| public static PreAccepted preAccepted(CommonAttributes common, Timestamp executeAt, Ballot promised) |
| { |
| return validate(new PreAccepted(common, SaveStatus.PreAccepted, promised, executeAt)); |
| } |
| |
| public static PreAccepted preAccepted(PreAccepted command, CommonAttributes common, Ballot promised) |
| { |
| checkPromised(command, promised); |
| checkSameClass(command, PreAccepted.class, "Cannot update"); |
| Invariants.checkArgument(command.getClass() == PreAccepted.class); |
| return preAccepted(common, command.executeAt(), promised); |
| } |
| |
| @Override |
| public Timestamp executeAt() |
| { |
| return executeAt; |
| } |
| |
| @Override |
| public Ballot acceptedOrCommitted() |
| { |
| return Ballot.ZERO; |
| } |
| |
| @Override |
| public PartialTxn partialTxn() |
| { |
| return partialTxn; |
| } |
| |
| @Override |
| public @Nullable PartialDeps partialDeps() |
| { |
| return partialDeps; |
| } |
| } |
| |
| public static class Accepted extends PreAccepted |
| { |
| private final Ballot acceptedOrCommitted; |
| |
| Accepted(CommonAttributes common, SaveStatus status, Ballot promised, Timestamp executeAt, Ballot acceptedOrCommitted) |
| { |
| super(common, status, promised, executeAt); |
| this.acceptedOrCommitted = acceptedOrCommitted; |
| } |
| |
| Accepted(CommonAttributes common, SaveStatus status, Ballot promised, Timestamp executeAt, PartialTxn partialTxn, PartialDeps partialDeps, Ballot acceptedOrCommitted) |
| { |
| super(common, status, promised, executeAt, partialTxn, partialDeps); |
| this.acceptedOrCommitted = acceptedOrCommitted; |
| } |
| |
| @Override |
| public Command updateAttributes(CommonAttributes attrs, Ballot promised) |
| { |
| return validate(new Accepted(attrs, saveStatus(), promised, executeAt(), acceptedOrCommitted())); |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| if (!super.equals(o)) return false; |
| Accepted that = (Accepted) o; |
| return Objects.equals(acceptedOrCommitted, that.acceptedOrCommitted); |
| } |
| |
| @Override |
| public boolean isEqualOrFuller(Command c) |
| { |
| if (this == c) return true; |
| if (c == null || getClass() != c.getClass()) return false; |
| if (!super.isEqualOrFuller(c)) return false; |
| Accepted that = (Accepted) c; |
| return Objects.equals(acceptedOrCommitted(), that.acceptedOrCommitted()); |
| } |
| |
| static Accepted accepted(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted) |
| { |
| return validate(new Accepted(common, status, promised, executeAt, accepted)); |
| } |
| |
| static Accepted accepted(Accepted command, CommonAttributes common, SaveStatus status, Ballot promised) |
| { |
| checkPromised(command, promised); |
| checkSameClass(command, Accepted.class, "Cannot update"); |
| return validate(new Accepted(common, status, promised, command.executeAt(), command.acceptedOrCommitted())); |
| } |
| |
| static Accepted accepted(Accepted command, CommonAttributes common, Ballot promised) |
| { |
| return accepted(command, common, command.saveStatus(), promised); |
| } |
| |
| @Override |
| public Ballot acceptedOrCommitted() |
| { |
| return acceptedOrCommitted; |
| } |
| } |
| |
| public static class Committed extends Accepted |
| { |
| public final WaitingOn waitingOn; |
| |
| private Committed(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted, WaitingOn waitingOn) |
| { |
| super(common, status, promised, executeAt, accepted); |
| this.waitingOn = waitingOn; |
| Invariants.checkState(common.route().kind().isFullRoute(), "Expected a full route but given %s", common.route().kind()); |
| if (status.hasBeen(Stable)) Invariants.checkState(waitingOn == WaitingOn.EMPTY || waitingOn.deps.equals(common.partialDeps()), "Deps do not match; expected %s == %s", waitingOn.deps, common.partialDeps()); |
| } |
| |
| @Override |
| public Timestamp executesAtLeast() |
| { |
| if (!txnId().kind().awaitsOnlyDeps()) return executeAt(); |
| if (status().hasBeen(Stable)) return waitingOn.executeAtLeast(executeAt()); |
| return null; |
| } |
| |
| @Override |
| public Command updateAttributes(CommonAttributes attrs, Ballot promised) |
| { |
| return validate(new Committed(attrs, saveStatus(), executeAt(), promised, acceptedOrCommitted(), waitingOn())); |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| if (!super.equals(o)) return false; |
| Committed committed = (Committed) o; |
| return Objects.equals(waitingOn, committed.waitingOn); |
| } |
| |
| @Override |
| public boolean isEqualOrFuller(Command c) |
| { |
| if (this == c) return true; |
| if (c == null || getClass() != c.getClass()) return false; |
| if (!super.isEqualOrFuller(c)) return false; |
| Committed committed = (Committed) c; |
| return (waitingOn() == null && committed.waitingOn() == null) |
| || (waitingOn() != null && committed.waitingOn() != null && waitingOn().isEqualOrFuller(committed.waitingOn())); |
| } |
| |
| private static Committed committed(Committed command, CommonAttributes common, Ballot promised, SaveStatus status, WaitingOn waitingOn) |
| { |
| checkPromised(command, promised); |
| checkSameClass(command, Committed.class, "Cannot update"); |
| return validate(new Committed(common, status, command.executeAt(), promised, command.acceptedOrCommitted(), waitingOn)); |
| } |
| |
| static Committed committed(Committed command, CommonAttributes common, Ballot promised) |
| { |
| return committed(command, common, promised, command.saveStatus(), command.waitingOn()); |
| } |
| |
| static Committed committed(Committed command, CommonAttributes common, SaveStatus status) |
| { |
| return committed(command, common, command.promised(), status, command.waitingOn()); |
| } |
| |
| static Committed committed(Committed command, CommonAttributes common, WaitingOn waitingOn) |
| { |
| return committed(command, common, command.promised(), command.saveStatus(), waitingOn); |
| } |
| |
| static Committed committed(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted, WaitingOn waitingOn) |
| { |
| return validate(new Committed(common, status, executeAt, promised, accepted, waitingOn)); |
| } |
| |
| public WaitingOn waitingOn() |
| { |
| return waitingOn; |
| } |
| |
| public boolean isWaitingOnCommit() |
| { |
| return waitingOn.isWaitingOnCommit(); |
| } |
| |
| public boolean isWaitingOnApply() |
| { |
| return waitingOn.isWaitingOnApply(); |
| } |
| |
| public boolean isWaitingOnDependency() |
| { |
| return isWaitingOnCommit() || isWaitingOnApply(); |
| } |
| } |
| |
| public static class Executed extends Committed |
| { |
| private final Writes writes; |
| private final Result result; |
| |
| public Executed(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted, WaitingOn waitingOn, Writes writes, Result result) |
| { |
| super(common, status, executeAt, promised, accepted, waitingOn); |
| Invariants.checkState(txnId().kind() != Txn.Kind.Write || writes != null); |
| this.writes = writes; |
| this.result = result; |
| } |
| |
| @Override |
| public Command updateAttributes(CommonAttributes attrs, Ballot promised) |
| { |
| return validate(new Executed(attrs, saveStatus(), executeAt(), promised, acceptedOrCommitted(), waitingOn(), writes, result)); |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| if (!super.equals(o)) return false; |
| Executed executed = (Executed) o; |
| return Objects.equals(writes, executed.writes) |
| && Objects.equals(result, executed.result); |
| } |
| |
| @Override |
| public boolean isEqualOrFuller(Command c) |
| { |
| if (this == c) return true; |
| if (c == null || getClass() != c.getClass()) return false; |
| if (!super.isEqualOrFuller(c)) return false; |
| Executed executed = (Executed) c; |
| return Objects.equals(writes(), executed.writes()) |
| && Objects.equals(result(), executed.result()); |
| } |
| |
| public static Executed executed(Executed command, CommonAttributes common, SaveStatus status, Ballot promised, WaitingOn waitingOn) |
| { |
| checkSameClass(command, Executed.class, "Cannot update"); |
| return validate(new Executed(common, status, command.executeAt(), promised, command.acceptedOrCommitted(), waitingOn, command.writes(), command.result())); |
| } |
| |
| public static Executed executed(Executed command, CommonAttributes common, SaveStatus status) |
| { |
| return executed(command, common, status, command.promised(), command.waitingOn()); |
| } |
| |
| public static Executed executed(Executed command, CommonAttributes common, WaitingOn waitingOn) |
| { |
| return executed(command, common, command.saveStatus(), command.promised(), waitingOn); |
| } |
| |
| public static Executed executed(Executed command, CommonAttributes common, Ballot promised) |
| { |
| return executed(command, common, command.saveStatus(), promised, command.waitingOn()); |
| } |
| |
| public static Executed executed(CommonAttributes common, SaveStatus status, Timestamp executeAt, Ballot promised, Ballot accepted, WaitingOn waitingOn, Writes writes, Result result) |
| { |
| return validate(new Executed(common, status, executeAt, promised, accepted, waitingOn, writes, result)); |
| } |
| |
| public Writes writes() |
| { |
| return writes; |
| } |
| |
| public Result result() |
| { |
| return result; |
| } |
| } |
| |
| public static class WaitingOn |
| { |
| public static final WaitingOn EMPTY = new WaitingOn(Deps.NONE, ImmutableBitSet.EMPTY, ImmutableBitSet.EMPTY, ImmutableBitSet.EMPTY); |
| |
| public final Deps deps; |
| // note that transactions default to waitingOnCommit, so presence in the set does not mean the transaction is uncommitted |
| public final ImmutableBitSet waitingOnCommit, waitingOnApply, appliedOrInvalidated; |
| |
| public WaitingOn(WaitingOn copy) |
| { |
| this(copy.deps, copy.waitingOnCommit, copy.waitingOnApply, copy.appliedOrInvalidated); |
| } |
| |
| public WaitingOn(Deps deps, ImmutableBitSet waitingOnCommit, ImmutableBitSet waitingOnApply, ImmutableBitSet appliedOrInvalidated) |
| { |
| this.deps = deps; |
| this.waitingOnCommit = waitingOnCommit; |
| this.waitingOnApply = waitingOnApply; |
| this.appliedOrInvalidated = appliedOrInvalidated; |
| } |
| |
| public Timestamp executeAtLeast() |
| { |
| return null; |
| } |
| |
| public Timestamp executeAtLeast(Timestamp ifNull) |
| { |
| return ifNull; |
| } |
| |
| public static WaitingOn none(Deps deps) |
| { |
| ImmutableBitSet empty = new ImmutableBitSet(deps.txnIdCount()); |
| return new WaitingOn(deps, empty, empty, empty); |
| } |
| |
| public boolean isWaitingOnCommit() |
| { |
| return !waitingOnCommit.isEmpty(); |
| } |
| |
| public boolean isWaitingOnApply() |
| { |
| return !waitingOnApply.isEmpty(); |
| } |
| |
| public boolean isWaitingOn(TxnId txnId) |
| { |
| int index = deps.indexOf(txnId); |
| return index >= 0 && (waitingOnCommit.get(index) || waitingOnApply.get(index)); |
| } |
| |
| public TxnId nextWaitingOnCommit() |
| { |
| int i = waitingOnCommit.lastSetBit(); |
| return i < 0 ? null : deps.txnId(i); |
| } |
| |
| public TxnId nextWaitingOnApply() |
| { |
| int i = waitingOnApply.lastSetBit(); |
| return i < 0 ? null : deps.txnId(i); |
| } |
| |
| public TxnId nextWaitingOn() |
| { |
| TxnId next = nextWaitingOnApply(); |
| return next != null ? next : nextWaitingOnCommit(); |
| } |
| |
| public boolean isAppliedOrInvalidatedRangeIdx(int i) |
| { |
| return appliedOrInvalidated.get(i + deps.keyDeps.txnIdCount()); |
| } |
| |
| public TxnId minWaitingOnTxnId() |
| { |
| return minWaitingOnTxnId(deps, waitingOnCommit, waitingOnApply); |
| } |
| |
| static TxnId minWaitingOnTxnId(Deps deps, SimpleBitSet waitingOnCommit, SimpleBitSet waitingOnApply) |
| { |
| int keyDepsCount = deps.keyDeps.txnIdCount(); |
| int minWaitingOnKeys = Math.min(waitingOnCommit.firstSetBitBefore(keyDepsCount, Integer.MAX_VALUE), waitingOnApply.nextSetBitBefore(0, keyDepsCount, Integer.MAX_VALUE)); |
| int minWaitingOnRanges = Math.min(waitingOnCommit.nextSetBit(keyDepsCount, Integer.MAX_VALUE), waitingOnApply.nextSetBit(keyDepsCount, Integer.MAX_VALUE)); |
| return TxnId.nonNullOrMin(minWaitingOnKeys == Integer.MAX_VALUE ? null : deps.txnId(minWaitingOnKeys), |
| minWaitingOnRanges == Integer.MAX_VALUE ? null : deps.txnId(minWaitingOnRanges)); |
| } |
| |
| static TxnId minWaitingOn(Deps deps, SimpleBitSet waitingOn) |
| { |
| int keyDepsCount = deps.keyDeps.txnIdCount(); |
| int minWaitingOnKeys = waitingOn.firstSetBitBefore(keyDepsCount, -1); |
| int minWaitingOnRanges = waitingOn.nextSetBit(keyDepsCount, -1); |
| return TxnId.nonNullOrMin(minWaitingOnKeys < 0 ? null : deps.keyDeps.txnId(minWaitingOnKeys), |
| minWaitingOnRanges < 0 ? null : deps.rangeDeps.txnId(minWaitingOnRanges - keyDepsCount)); |
| } |
| |
| static TxnId maxWaitingOn(Deps deps, SimpleBitSet waitingOn) |
| { |
| int keyDepsCount = deps.keyDeps.txnIdCount(); |
| int maxWaitingOnRanges = waitingOn.lastSetBitNotBefore(keyDepsCount); |
| int maxWaitingOnKeys = waitingOn.prevSetBit(keyDepsCount); |
| return TxnId.nonNullOrMax(maxWaitingOnKeys < 0 ? null : deps.keyDeps.txnId(maxWaitingOnKeys), |
| maxWaitingOnRanges < 0 ? null : deps.rangeDeps.txnId(maxWaitingOnRanges - keyDepsCount)); |
| } |
| |
| public ImmutableSortedSet<TxnId> computeWaitingOnCommit() |
| { |
| return computeWaitingOnCommit(deps, waitingOnCommit); |
| } |
| |
| public ImmutableSortedSet<TxnId> computeWaitingOnApply() |
| { |
| return computeWaitingOnApply(deps, waitingOnCommit, waitingOnApply); |
| } |
| |
| private static ImmutableSortedSet<TxnId> computeWaitingOnCommit(Deps deps, SimpleBitSet waitingOnCommit) |
| { |
| ImmutableSortedSet.Builder<TxnId> builder = new ImmutableSortedSet.Builder<>(TxnId::compareTo); |
| waitingOnCommit.forEach(builder, deps, (b, d, i) -> b.add(d.txnId(i))); |
| return builder.build(); |
| } |
| |
| private static ImmutableSortedSet<TxnId> computeWaitingOnApply(Deps deps, SimpleBitSet waitingOnCommit, SimpleBitSet waitingOnApply) |
| { |
| ImmutableSortedSet.Builder<TxnId> builder = new ImmutableSortedSet.Builder<>(TxnId::compareTo); |
| waitingOnApply.forEach(builder, deps, waitingOnCommit, (b, d, s, i) -> { |
| if (!s.get(i)) |
| b.add(d.txnId(i)); |
| }); |
| return builder.build(); |
| } |
| |
| private static String toString(Deps deps, SimpleBitSet waitingOnCommit, SimpleBitSet waitingOnApply) |
| { |
| return "onApply=" + computeWaitingOnApply(deps, waitingOnCommit, waitingOnApply).descendingSet() + ", onCommit=" + computeWaitingOnCommit(deps, waitingOnCommit).descendingSet(); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return toString(deps, waitingOnCommit, waitingOnApply); |
| } |
| |
| @Override |
| public boolean equals(Object other) |
| { |
| return other.getClass() == WaitingOn.class && this.equals((WaitingOn) other); |
| } |
| |
| boolean equals(WaitingOn other) |
| { |
| return this.deps.equals(other.deps) |
| && this.waitingOnCommit.equals(other.waitingOnCommit) |
| && this.waitingOnApply.equals(other.waitingOnApply) |
| && this.appliedOrInvalidated.equals(other.appliedOrInvalidated); |
| } |
| |
| public boolean isEqualOrFuller(WaitingOn other) |
| { |
| return computeWaitingOnCommit().containsAll(other.computeWaitingOnCommit()) |
| && computeWaitingOnApply().containsAll(other.computeWaitingOnApply()); |
| } |
| |
| public static class Update |
| { |
| final Deps deps; |
| private SimpleBitSet waitingOnCommit, waitingOnApply, appliedOrInvalidated; |
| private Timestamp executeAtLeast; |
| |
| public Update(WaitingOn waitingOn) |
| { |
| this.deps = waitingOn.deps; |
| this.waitingOnCommit = waitingOn.waitingOnCommit; |
| this.waitingOnApply = waitingOn.waitingOnApply; |
| this.appliedOrInvalidated = waitingOn.appliedOrInvalidated; |
| if (waitingOn.getClass() == WaitingOnWithExecuteAt.class) |
| executeAtLeast = ((WaitingOnWithExecuteAt) waitingOn).executeAtLeast; |
| } |
| |
| public Update(Committed committed) |
| { |
| this(committed.waitingOn); |
| } |
| |
| public Update(Deps deps) |
| { |
| this.deps = deps; |
| this.waitingOnCommit = new SimpleBitSet(deps.txnIdCount(), false); |
| this.waitingOnApply = new SimpleBitSet(deps.txnIdCount(), false); |
| this.appliedOrInvalidated = new SimpleBitSet(deps.txnIdCount(), false); |
| } |
| |
| public boolean hasChanges() |
| { |
| return !(waitingOnCommit instanceof ImmutableBitSet) |
| || !(waitingOnApply instanceof ImmutableBitSet) |
| || !(appliedOrInvalidated instanceof ImmutableBitSet); |
| } |
| |
| public boolean removeWaitingOnCommit(TxnId txnId) |
| { |
| int index = deps.indexOf(txnId); |
| if (!waitingOnCommit.get(index)) |
| return false; |
| |
| waitingOnCommit = ensureMutable(waitingOnCommit); |
| waitingOnCommit.unset(index); |
| return true; |
| } |
| |
| public boolean isWaitingOnApply(TxnId txnId) |
| { |
| int index = deps.indexOf(txnId); |
| return waitingOnApply.get(index); |
| } |
| |
| public boolean isWaitingOn(TxnId txnId) |
| { |
| int index = deps.indexOf(txnId); |
| return waitingOnApply.get(index) || waitingOnCommit.get(index); |
| } |
| |
| public boolean addWaitingOnApply(TxnId txnId) |
| { |
| int index = deps.indexOf(txnId); |
| if (waitingOnApply.get(index)) |
| return false; |
| |
| waitingOnApply = ensureMutable(waitingOnApply); |
| waitingOnApply.set(index); |
| return true; |
| } |
| |
| void initialiseWaitingOnCommit(int index) |
| { |
| waitingOnCommit.set(index); |
| } |
| |
| public boolean isEmpty() |
| { |
| return waitingOnApply.isEmpty() && waitingOnCommit.isEmpty(); |
| } |
| |
| public TxnId minWaitingOnTxnId() |
| { |
| return WaitingOn.minWaitingOnTxnId(deps, waitingOnCommit, waitingOnApply); |
| } |
| |
| public boolean isWaitingOn(int txnIdx) |
| { |
| return waitingOnApply.get(txnIdx) || waitingOnCommit.get(txnIdx); |
| } |
| |
| public boolean isWaitingOnRangeIdx(int txnIdx) |
| { |
| txnIdx += deps.keyDeps.txnIdCount(); |
| return waitingOnApply.get(txnIdx) || waitingOnCommit.get(txnIdx); |
| } |
| |
| boolean removeWaitingOn(int i, boolean isAppliedOrInvalidated) |
| { |
| if (!removeWaitingOn(i)) |
| return false; |
| |
| if (isAppliedOrInvalidated) |
| { |
| appliedOrInvalidated = ensureMutable(appliedOrInvalidated); |
| appliedOrInvalidated.set(i); |
| } |
| return true; |
| } |
| |
| public void updateExecuteAtLeast(Timestamp executeAtLeast) |
| { |
| this.executeAtLeast = Timestamp.nonNullOrMax(executeAtLeast, this.executeAtLeast); |
| } |
| |
| public boolean removeWaitingOn(TxnId txnId) |
| { |
| int index = this.deps.indexOf(txnId); |
| return removeWaitingOn(index); |
| } |
| |
| boolean removeWaitingOnRangeIdx(int i) |
| { |
| return removeWaitingOn(i + deps.keyDeps.txnIdCount()); |
| } |
| |
| boolean removeWaitingOn(int i) |
| { |
| if (waitingOnCommit.get(i)) |
| { |
| waitingOnCommit = ensureMutable(waitingOnCommit); |
| waitingOnCommit.unset(i); |
| return true; |
| } |
| else if (waitingOnApply.get(i)) |
| { |
| waitingOnApply = ensureMutable(waitingOnApply); |
| waitingOnApply.unset(i); |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| /** |
| * Warning: DO NOT invoke this when you really mean removeWaitingOn. |
| * This propagates the applied/invalidated status to dependent transactions, which may |
| * adopt a different set of dependency relations on this transaction. If the transaction |
| * is e.g. partially stale, pre-bootstrap etc this could lead to an erroneous propagation |
| * unless the transaction is truly (and fully) applied or invalidated locally. |
| */ |
| public boolean setAppliedOrInvalidated(TxnId txnId) |
| { |
| int index = this.deps.indexOf(txnId); |
| return setAppliedOrInvalidated(index); |
| } |
| |
| boolean setAppliedOrInvalidatedRangeIdx(int i) |
| { |
| return setAppliedOrInvalidated(i + deps.keyDeps.txnIdCount()); |
| } |
| |
| public boolean setAppliedOrInvalidated(int i) |
| { |
| if (appliedOrInvalidated.get(i)) |
| return false; |
| |
| if (!removeWaitingOn(i)) |
| return false; |
| |
| appliedOrInvalidated = ensureMutable(appliedOrInvalidated); |
| appliedOrInvalidated.set(i); |
| return true; |
| } |
| |
| public boolean setAppliedAndPropagate(TxnId txnId, WaitingOn propagate) |
| { |
| int index = this.deps.indexOf(txnId); |
| if (!setAppliedOrInvalidated(index)) |
| return false; |
| |
| if (!propagate.appliedOrInvalidated.isEmpty()) |
| { |
| forEachIntersection(propagate.deps.keyDeps.txnIds(), deps.keyDeps.txnIds(), |
| (from, to, ignore, i1, i2) -> { |
| if (from.get(i1)) |
| to.setAppliedOrInvalidated(i2); |
| }, propagate.appliedOrInvalidated, this, null); |
| |
| forEachIntersection(propagate.deps.rangeDeps.txnIds(), deps.rangeDeps.txnIds(), |
| (from, to, ignore, i1, i2) -> { |
| if (from.isAppliedOrInvalidatedRangeIdx(i1)) |
| to.setAppliedOrInvalidatedRangeIdx(i2); |
| }, propagate, this, null); |
| } |
| |
| return true; |
| } |
| |
| public <P1, P2, P3, P4> void forEachWaitingOnCommit(P1 p1, P2 p2, P3 p3, P4 p4, IndexedQuadConsumer<P1, P2, P3, P4> forEach) |
| { |
| waitingOnCommit.reverseForEach(p1, p2, p3, p4, forEach); |
| } |
| |
| public <P1, P2, P3, P4> void forEachWaitingOnApply(P1 p1, P2 p2, P3 p3, P4 p4, IndexedQuadConsumer<P1, P2, P3, P4> forEach) |
| { |
| waitingOnApply.reverseForEach(p1, p2, p3, p4, forEach); |
| } |
| |
| public WaitingOn build() |
| { |
| WaitingOn result = new WaitingOn(deps, ensureImmutable(waitingOnCommit), ensureImmutable(waitingOnApply), ensureImmutable(appliedOrInvalidated)); |
| if (executeAtLeast == null) |
| return result; |
| return new WaitingOnWithExecuteAt(result, executeAtLeast); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return WaitingOn.toString(deps, waitingOnCommit, waitingOnApply); |
| } |
| } |
| } |
| |
| public static final class WaitingOnWithExecuteAt extends WaitingOn |
| { |
| public final Timestamp executeAtLeast; |
| public WaitingOnWithExecuteAt(WaitingOn waitingOn, Timestamp executeAtLeast) |
| { |
| super(waitingOn); |
| this.executeAtLeast = executeAtLeast; |
| } |
| |
| @Override |
| public Timestamp executeAtLeast() |
| { |
| return executeAtLeast; |
| } |
| |
| @Override |
| public Timestamp executeAtLeast(Timestamp ifNull) |
| { |
| return executeAtLeast != null ? executeAtLeast : ifNull; |
| } |
| |
| @Override |
| public boolean equals(Object other) |
| { |
| return other.getClass() == WaitingOnWithExecuteAt.class && this.equals((WaitingOnWithExecuteAt) other); |
| } |
| |
| boolean equals(WaitingOnWithExecuteAt other) |
| { |
| return super.equals((WaitingOn) other) && executeAtLeast == other.executeAtLeast; |
| } |
| } |
| |
| static Command addListener(Command command, DurableAndIdempotentListener listener) |
| { |
| CommonAttributes attrs = command.mutable().addListener(listener); |
| return command.updateAttributes(attrs); |
| } |
| |
| static Command removeListener(Command command, Listener listener) |
| { |
| CommonAttributes attrs = command.mutable().removeListener(listener); |
| return command.updateAttributes(attrs); |
| } |
| |
| static Command.Committed updateWaitingOn(Committed command, WaitingOn.Update waitingOn) |
| { |
| if (!waitingOn.hasChanges()) |
| return command; |
| |
| return command instanceof Command.Executed ? |
| Command.Executed.executed(command.asExecuted(), command, waitingOn.build()) : |
| Command.Committed.committed(command, command, waitingOn.build()); |
| } |
| |
| static Command.PreAccepted preaccept(Command command, CommonAttributes attrs, Timestamp executeAt, Ballot ballot) |
| { |
| if (command.status() == Status.NotDefined) |
| { |
| return Command.PreAccepted.preAccepted(attrs, executeAt, ballot); |
| } |
| else if (command.status() == Status.AcceptedInvalidate && command.executeAt() == null) |
| { |
| // TODO (now): reconsider this special-casing |
| Command.Accepted accepted = command.asAccepted(); |
| return Command.Accepted.accepted(attrs, SaveStatus.enrich(accepted.saveStatus(), SaveStatus.PreAccepted.known), executeAt, ballot, accepted.acceptedOrCommitted()); |
| } |
| else |
| { |
| Invariants.checkState(command.status() == Status.Accepted); |
| return (Command.PreAccepted) command.updateAttributes(attrs, ballot); |
| } |
| } |
| |
| static Command.Accepted markDefined(Command command, CommonAttributes attributes, Ballot promised) |
| { |
| if (Command.isSameClass(command, Command.Accepted.class)) |
| return Command.Accepted.accepted(command.asAccepted(), attributes, SaveStatus.enrich(command.saveStatus(), Known.DefinitionOnly), promised); |
| return (Command.Accepted) command.updateAttributes(attributes, promised); |
| } |
| |
| static Command.Accepted accept(Command command, CommonAttributes attrs, Timestamp executeAt, Ballot ballot) |
| { |
| return validate(new Command.Accepted(attrs, SaveStatus.get(Status.Accepted, command.known()), ballot, executeAt, ballot)); |
| } |
| |
| static Command.Accepted acceptInvalidated(Command command, Ballot ballot) |
| { |
| return validate(new Command.Accepted(command, SaveStatus.get(Status.AcceptedInvalidate, command.known()), ballot, command.executeAt(), command.partialTxn(), null, ballot)); |
| } |
| |
| static Command.Committed commit(Command command, CommonAttributes attrs, Ballot ballot, Timestamp executeAt) |
| { |
| return validate(Command.Committed.committed(attrs, SaveStatus.get(Status.Committed, command.known()), executeAt, Ballot.max(command.promised(), ballot), ballot, null)); |
| } |
| |
| static Command.Committed stable(Command command, CommonAttributes attrs, Ballot ballot, Timestamp executeAt, Command.WaitingOn waitingOn) |
| { |
| return validate(Command.Committed.committed(attrs, SaveStatus.get(Status.Stable, command.known()), executeAt, Ballot.max(command.promised(), ballot), ballot, waitingOn)); |
| } |
| |
| static Command precommit(CommonAttributes attrs, Command command, Timestamp executeAt) |
| { |
| return validate(new Command.Accepted(attrs, SaveStatus.get(Status.PreCommitted, command.known()), command.promised(), executeAt, command.acceptedOrCommitted())); |
| } |
| |
| static Command.Committed readyToExecute(Command.Committed command) |
| { |
| return Command.Committed.committed(command, command, SaveStatus.ReadyToExecute); |
| } |
| |
| static Command.Executed preapplied(Command command, CommonAttributes attrs, Timestamp executeAt, Command.WaitingOn waitingOn, Writes writes, Result result) |
| { |
| return Command.Executed.executed(attrs, SaveStatus.get(Status.PreApplied, command.known()), executeAt, command.promised(), command.acceptedOrCommitted(), waitingOn, writes, result); |
| } |
| |
| static Command.Executed applying(Command.Executed command) |
| { |
| return Command.Executed.executed(command, command, SaveStatus.Applying); |
| } |
| |
| static Command.Executed applied(Command.Executed command) |
| { |
| return Command.Executed.executed(command, command, SaveStatus.Applied); |
| } |
| } |