blob: 7e83068bad52b91d68a1a30a699bba9c9fe31cbd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package accord.local;
import accord.local.Status.*;
import accord.primitives.Ballot;
import static accord.local.Status.Definition.DefinitionKnown;
import static accord.local.Status.Definition.DefinitionUnknown;
import static accord.local.Status.KnownDeps.*;
import static accord.local.Status.KnownExecuteAt.*;
import static accord.local.Status.Outcome.OutcomeUnknown;
/**
* Identical to Status but preserves whether we have previously been PreAccepted and therefore know the definition
* of the transaction. This would potentially complicate users of Status, and the distributed state machine is complicated
* enough. But it helps to formalise the relationships here as an auxiliary enum.
* Intended to be used internally by Command implementations.
*/
public enum SaveStatus
{
NotWitnessed (Status.NotWitnessed),
PreAccepted (Status.PreAccepted),
AcceptedInvalidate (Status.AcceptedInvalidate),
AcceptedInvalidateWithDefinition(Status.AcceptedInvalidate, DefinitionKnown, ExecuteAtUnknown, DepsUnknown, OutcomeUnknown),
Accepted (Status.Accepted),
AcceptedWithDefinition (Status.Accepted, DefinitionKnown, ExecuteAtProposed, DepsProposed, OutcomeUnknown),
PreCommitted (Status.PreCommitted),
PreCommittedWithAcceptedDeps (Status.PreCommitted, DefinitionUnknown, ExecuteAtKnown, DepsProposed, OutcomeUnknown),
PreCommittedWithDefinition (Status.PreCommitted, DefinitionKnown, ExecuteAtKnown, DepsUnknown, OutcomeUnknown),
PreCommittedWithDefinitionAndAcceptedDeps(Status.PreCommitted, DefinitionKnown, ExecuteAtKnown, DepsProposed, OutcomeUnknown),
Committed (Status.Committed),
ReadyToExecute (Status.ReadyToExecute),
PreApplied (Status.PreApplied),
Applied (Status.Applied),
Invalidated (Status.Invalidated);
public final Status status;
public final Phase phase;
public final Known known; // TODO (easy, API/efficiency): duplicate contents here to reduce indirection for majority of cases
SaveStatus(Status status)
{
this.status = status;
this.phase = status.phase;
this.known = status.minKnown;
}
SaveStatus(Status status, Definition definition, KnownExecuteAt executeAt, KnownDeps deps, Outcome outcome)
{
this.status = status;
this.phase = status.phase;
this.known = new Known(definition, executeAt, deps, outcome);
}
public boolean hasBeen(Status status)
{
return this.status.compareTo(status) >= 0;
}
// TODO (expected, testing): exhaustive testing, particularly around PreCommitted
public static SaveStatus get(Status status, Known known)
{
switch (status)
{
default: throw new AssertionError();
case NotWitnessed: return NotWitnessed;
case PreAccepted: return PreAccepted;
case AcceptedInvalidate:
// AcceptedInvalidate logically clears any proposed deps and executeAt
if (!known.executeAt.hasDecidedExecuteAt())
return known.isDefinitionKnown() ? AcceptedInvalidateWithDefinition : AcceptedInvalidate;
// If we know the executeAt decision then we do not clear it, and fall-through to PreCommitted
// however, we still clear the deps, as any deps we might have previously seen proposed are now expired
// TODO (expected, consider): consider clearing Command.partialDeps in this case also
known = known.with(DepsUnknown);
case Accepted:
if (!known.executeAt.hasDecidedExecuteAt())
return known.isDefinitionKnown() ? AcceptedWithDefinition : Accepted;
// if the decision is known, we're really PreCommitted
case PreCommitted:
if (known.isDefinitionKnown())
return known.deps.hasProposedOrDecidedDeps() ? PreCommittedWithDefinitionAndAcceptedDeps : PreCommittedWithDefinition;
return known.deps.hasProposedOrDecidedDeps() ? PreCommittedWithAcceptedDeps : PreCommitted;
case Committed: return Committed;
case ReadyToExecute: return ReadyToExecute;
case PreApplied: return PreApplied;
case Applied: return Applied;
case Invalidated: return Invalidated;
}
}
public static SaveStatus enrich(SaveStatus status, Known known)
{
if (known.isSatisfiedBy(status.known))
return status;
return get(status.status, status.known.merge(known));
}
public static SaveStatus merge(SaveStatus a, Ballot acceptedA, SaveStatus b, Ballot acceptedB)
{
SaveStatus prefer;
if (a.phase != b.phase) prefer = a.phase.compareTo(b.phase) >= 0 ? a : b;
else if (a.phase == Phase.Accept) prefer = acceptedA.compareTo(acceptedB) >= 0 ? a : b;
else prefer = a.compareTo(b) >= 0 ? a : b;
return SaveStatus.enrich(prefer, (prefer == a ? b : a).known);
}
}