private void invalidate()

in accord-core/src/main/java/accord/coordinate/Invalidate.java [145:261]


    private void invalidate()
    {
        Invariants.checkState(!isPrepareDone);
        isPrepareDone = true;

        // first look to see if it has already been
        {
            FullRoute<?> route = InvalidateReply.findRoute(replies);
            RoutingKey homeKey = route != null ? route.homeKey() : InvalidateReply.findHomeKey(replies);
            InvalidateReply maxReply = InvalidateReply.max(replies);

            switch (maxReply.status)
            {
                default: throw new IllegalStateException();
                case AcceptedInvalidate:
                    // latest accept also invalidating, so we're on the same page and should finish our invalidation
                case NotWitnessed:
                    break;

                case PreAccepted:
                    if (tracker.isSafeToInvalidate() || transitivelyInvokedByPriorInvalidation)
                        break;

                case Accepted:
                case PreCommitted:
                case Committed:
                case ReadyToExecute:
                case PreApplied:
                case Applied:
                    // TODO (desired, efficiency): if we see Committed or above, go straight to Execute if we have assembled enough information
                    if (route != null)
                    {
                        // The data we see might have made it only to a minority in the event of PreAccept ONLY.
                        // We want to protect against infinite loops, so we inform the recovery of the state we have
                        // witnessed during our initial invalidation.

                        // However, if the state is not guaranteed to be recoverable (i.e. PreAccept/NotWitnessed),
                        // we do not relay this information unless we can guarantee that any shard recovery may contact
                        // has been prevented from reaching a _later_ fast-path decision by our promises.
                        // Which means checking we contacted every shard, since we only reach this point if we have promises
                        // from every shard we contacted.

                        // Note that there's lots of scope for variations in behaviour here, but lots of care is needed.

                        Status witnessedByInvalidation = maxReply.status;
                        if (!witnessedByInvalidation.hasBeen(Accepted))
                        {
                            Invariants.checkState(tracker.all(InvalidationShardTracker::isPromised));
                            if (!invalidateWith.containsAll(route))
                                witnessedByInvalidation = null;
                        }
                        RecoverWithRoute.recover(node, ballot, txnId, route, witnessedByInvalidation, callback);
                    }
                    else if (homeKey != null)
                    {
                        Invariants.checkState(maxReply.status.hasBeen(Accepted) || tracker.all(InvalidationShardTracker::isPromised));
                        // if we included the home shard, and we have either a recoverable status OR have not rejected the fast path,
                        // we must have at least one response that should contain the Route
                        if (invalidateWith.contains(homeKey) && tracker.isPromisedForKey(homeKey, txnId.epoch()))
                            throw new IllegalStateException("Received replies from a node that must have known the route, but that did not include it");

                        // if < Accepted, we should have short-circuited to invalidation above. This guarantees no Invaldate/Recover loop, as any later status will forbid invoking Invalidate
                        Invariants.checkState(!(transitivelyInvokedByPriorInvalidation && !maxReply.status.hasBeen(Accepted)));

                        Status witnessedByInvalidation = maxReply.status;
                        if (!witnessedByInvalidation.hasBeen(Accepted))
                        {
                            Invariants.checkState(tracker.all(InvalidationShardTracker::isPromised));
                            if (!invalidateWith.contains(homeKey))
                                witnessedByInvalidation = null;
                        }

                        RecoverWithHomeKey.recover(node, txnId, homeKey, witnessedByInvalidation, callback);
                    }
                    else
                    {
                        throw new IllegalStateException("Received a reply from a node that must have known the homeKey, but that did not include it");
                    }
                    return;

                case Invalidated:
                    // TODO (desired, API consistency): standardise semantics of whether local application of state prior is async or sync to callback
                    isDone = true;
                    commitInvalidate();
                    return;
            }
        }

        // if we have witnessed the transaction, but are able to invalidate, do we want to proceed?
        // Probably simplest to do so, but perhaps better for user if we don't.
        Ranges ranges = Ranges.of(tracker.promisedShard().range);
        // we look up by TxnId at the target node, so it's fine to pick a RoutingKey even if it's a range transaction
        RoutingKey invalidateWithKey = invalidateWith.slice(ranges).get(0).someIntersectingRoutingKey(ranges);
        proposeInvalidate(node, ballot, txnId, invalidateWithKey, (success, fail) -> {
            /*
              We're now inside our *exactly once* callback we registered with proposeInvalidate, and we need to
              make sure we honour our own exactly once semantics with {@code callback}.
              So we are responsible for all exception handling.
             */
            isDone = true;
            if (fail != null)
            {
                callback.accept(null, fail);
            }
            else
            {
                try
                {
                    commitInvalidate();
                }
                catch (Throwable t)
                {
                    callback.accept(null, t);
                }
            }
        });
    }