Skip to content

Commit bf85660

Browse files
committed
Fixes
- Don't assume stillExecutes applies to remote request - must mark unavailable any keys we don't have the txn definition for - Don't exit notifyManagedPreBootstrap notify loop early, as could have later transactions with lower txnId - CoordinateEphemeralRead must retry if insufficient responses from replicas that still own the range - Burn test terminates while non-recurring tasks pending on command store queues - Infinite recovery due to not sending InformDurable when partially truncated - Incorrect participants when invoking removeRedundantDependencies - Infinite bootstrap loop on retired topology Improve - Do not perform linear filters of CommandStores or Topologies - Some default implementations of forEach/iterator - TopologyRetiredException messages patch by Benedict; reviewed by Alex Petrov for CASSANDRA-20707
1 parent b22b935 commit bf85660

44 files changed

Lines changed: 530 additions & 238 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

accord-core/src/main/java/accord/coordinate/AbstractCoordinatePreAccept.java

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import accord.topology.Topologies;
3131
import accord.utils.Invariants;
3232
import accord.utils.WrappableException;
33-
import accord.utils.async.AsyncResults.SettableResult;
3433

3534
import static accord.api.ProtocolModifiers.QuorumEpochIntersections;
3635
import static accord.topology.Topologies.SelectNodeOwnership.SHARE;
@@ -39,26 +38,28 @@
3938
* Abstract parent class for implementing preaccept-like operations where we may need to fetch additional replies
4039
* from future epochs.
4140
*/
42-
abstract class AbstractCoordinatePreAccept<T, R> extends SettableResult<T> implements Callback<R>, BiConsumer<T, Throwable>
41+
abstract class AbstractCoordinatePreAccept<T, R> implements Callback<R>
4342
{
4443
final Node node;
4544
final TxnId txnId;
4645
final FullRoute<?> route;
4746

4847
final Topologies topologies;
48+
final BiConsumer<T, Throwable> callback;
4949
private boolean isDone;
5050

51-
AbstractCoordinatePreAccept(Node node, FullRoute<?> route, @Nonnull TxnId txnId)
51+
AbstractCoordinatePreAccept(Node node, FullRoute<?> route, @Nonnull TxnId txnId, BiConsumer<T, Throwable> callback)
5252
{
53-
this(node, route, txnId, node.topology().select(route, txnId, txnId, SHARE, QuorumEpochIntersections.preaccept.include));
53+
this(node, route, txnId, node.topology().select(route, txnId, txnId, SHARE, QuorumEpochIntersections.preaccept.include), callback);
5454
}
5555

56-
AbstractCoordinatePreAccept(Node node, FullRoute<?> route, @Nonnull TxnId txnId, Topologies topologies)
56+
AbstractCoordinatePreAccept(Node node, FullRoute<?> route, @Nonnull TxnId txnId, Topologies topologies, BiConsumer<T, Throwable> callback)
5757
{
5858
this.node = node;
5959
this.txnId = txnId;
6060
this.route = route;
6161
this.topologies = topologies;
62+
this.callback = callback;
6263
}
6364

6465
void start()
@@ -86,7 +87,8 @@ public final boolean onCallbackFailure(Id from, Throwable failure)
8687
{
8788
if (isDone) return false;
8889
isDone = true;
89-
return tryFailure(failure);
90+
callback.accept(null, failure);
91+
return true;
9092
}
9193

9294
@Override
@@ -103,19 +105,12 @@ public final void onSlowResponse(Id from)
103105
onSlowResponseInternal(from);
104106
}
105107

106-
@Override
107-
public final boolean tryFailure(Throwable failure)
108-
{
109-
if (!super.tryFailure(failure))
110-
return false;
111-
onFailure(failure);
112-
return true;
113-
}
114-
115-
private void onFailure(Throwable failure)
108+
void setFailure(Throwable failure)
116109
{
110+
Invariants.require(!isDone);
117111
// we may already be complete, as we may receive a failure from a later phase; but it's fine to redundantly mark done
118112
isDone = true;
113+
callback.accept(null, failure);
119114
if (failure instanceof CoordinationFailed)
120115
{
121116
((CoordinationFailed) failure).set(txnId, route.homeKey());
@@ -133,7 +128,7 @@ final void onPreAcceptedOrNewEpoch()
133128
Invariants.require(!isDone);
134129
isDone = true;
135130
long latestEpoch = executeAtEpoch();
136-
if (latestEpoch > topologies.currentEpoch()) node.withEpoch(latestEpoch, this, () -> onPreAcceptedInNewEpoch(topologies, latestEpoch));
131+
if (latestEpoch > topologies.currentEpoch()) node.withEpochExact(latestEpoch, callback, t -> WrappableException.wrap(t), () -> onPreAcceptedInNewEpoch(topologies, latestEpoch));
137132
else onPreAccepted(topologies);
138133
}
139134

@@ -143,11 +138,4 @@ final void onPreAcceptedInNewEpoch(Topologies topologies, long latestEpoch)
143138
if (mismatch == null) onPreAccepted(topologies);
144139
else onNewEpochTopologyMismatch(mismatch);
145140
}
146-
147-
@Override
148-
public final void accept(T success, Throwable failure)
149-
{
150-
if (success != null) trySuccess(success);
151-
else tryFailure(WrappableException.wrap(failure));
152-
}
153141
}

accord-core/src/main/java/accord/coordinate/CoordinateEphemeralRead.java

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package accord.coordinate;
2020

2121
import java.util.Collection;
22+
import java.util.function.BiConsumer;
2223

2324
import accord.api.Result;
2425
import accord.coordinate.tracking.QuorumTracker;
@@ -35,7 +36,7 @@
3536
import accord.utils.Invariants;
3637
import accord.utils.SortedListMap;
3738
import accord.utils.async.AsyncResult;
38-
import accord.utils.async.AsyncResults;
39+
import accord.utils.async.AsyncResults.SettableByCallback;
3940

4041
import static accord.api.ProtocolModifiers.QuorumEpochIntersections;
4142
import static accord.api.ProtocolModifiers.QuorumEpochIntersections.Include.Owned;
@@ -59,26 +60,36 @@
5960
public class CoordinateEphemeralRead extends AbstractCoordinatePreAccept<Result, GetEphemeralReadDepsOk>
6061
{
6162
public static AsyncResult<Result> coordinate(Node node, FullRoute<?> route, TxnId txnId, Txn txn)
63+
{
64+
SettableByCallback<Result> result = new SettableByCallback<>();
65+
coordinate(node, route, txnId, txn, result);
66+
return result;
67+
}
68+
69+
public static void coordinate(Node node, FullRoute<?> route, TxnId txnId, Txn txn, BiConsumer<Result, Throwable> callback)
6270
{
6371
TopologyMismatch mismatch = TopologyMismatch.checkForMismatchOrPendingRemoval(node.topology().globalForEpoch(txnId.epoch()), txnId, route.homeKey(), route);
6472
if (mismatch != null)
65-
return AsyncResults.failure(mismatch);
73+
{
74+
callback.accept(null, mismatch);
75+
return;
76+
}
6677

6778
Topologies topologies = node.topology().withUnsyncedEpochs(route, txnId, txnId);
68-
CoordinateEphemeralRead coordinate = new CoordinateEphemeralRead(node, topologies, route, txnId, txn);
79+
CoordinateEphemeralRead coordinate = new CoordinateEphemeralRead(node, topologies, route, txnId, txn, callback);
6980
coordinate.start();
70-
return coordinate;
7181
}
7282

7383
private final Txn txn;
7484

7585
private final QuorumTracker tracker;
7686
private final SortedListMap<Node.Id, GetEphemeralReadDepsOk> oks;
7787
private long executeAtEpoch;
88+
private long retryInEpoch;
7889

79-
CoordinateEphemeralRead(Node node, Topologies topologies, FullRoute<?> route, TxnId txnId, Txn txn)
90+
CoordinateEphemeralRead(Node node, Topologies topologies, FullRoute<?> route, TxnId txnId, Txn txn, BiConsumer<Result, Throwable> callback)
8091
{
81-
super(node, route, txnId);
92+
super(node, route, txnId, callback);
8293
this.txn = txn;
8394
this.tracker = new QuorumTracker(topologies);
8495
this.executeAtEpoch = txnId.epoch();
@@ -101,33 +112,50 @@ long executeAtEpoch()
101112
@Override
102113
public void onSuccessInternal(Node.Id from, GetEphemeralReadDepsOk ok)
103114
{
104-
oks.put(from, ok);
105-
if (ok.latestEpoch > executeAtEpoch)
106-
executeAtEpoch = ok.latestEpoch;
107-
108-
if (tracker.recordSuccess(from) == Success)
109-
onPreAcceptedOrNewEpoch();
115+
if (ok.deps != null)
116+
{
117+
oks.put(from, ok);
118+
if (ok.latestEpoch > executeAtEpoch)
119+
executeAtEpoch = ok.latestEpoch;
120+
121+
if (tracker.recordSuccess(from) == Success)
122+
onPreAcceptedOrNewEpoch();
123+
}
124+
else
125+
{
126+
retryInEpoch = ok.latestEpoch;
127+
if (tracker.recordFailure(from) == Failed)
128+
retry();
129+
}
110130
}
111131

112132
@Override
113133
public void onFailureInternal(Node.Id from, Throwable failure)
114134
{
115135
if (tracker.recordFailure(from) == Failed)
116-
setFailure(new Timeout(txnId, route.homeKey()));
136+
{
137+
if (retryInEpoch > 0) retry();
138+
else setFailure(new Timeout(txnId, route.homeKey()));
139+
}
140+
}
141+
142+
private void retry()
143+
{
144+
coordinate(node, route, txnId.withEpoch(retryInEpoch), txn, callback);
117145
}
118146

119147
@Override
120148
void onNewEpochTopologyMismatch(TopologyMismatch mismatch)
121149
{
122-
accept(null, mismatch);
150+
setFailure(mismatch);
123151
}
124152

125153
@Override
126154
void onPreAccepted(Topologies topologies)
127155
{
128156
Deps deps = Deps.merge(oks, oks.domainSize(), SortedListMap::getValue, ok -> ok.deps);
129157
topologies = node.topology().reselect(topologies, QuorumEpochIntersections.preaccept.include, route, executeAtEpoch, executeAtEpoch, SHARE, Owned);
130-
new ExecuteEphemeralRead(node, topologies, route, txnId.withEpoch(executeAtEpoch), txn, deps, this).start();
158+
new ExecuteEphemeralRead(node, topologies, route, txnId.withEpoch(executeAtEpoch), txn, deps, callback).start();
131159
if (!Invariants.debug()) oks.clear();
132160
}
133161
}

accord-core/src/main/java/accord/coordinate/CoordinateMaxConflict.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package accord.coordinate;
2020

2121
import java.util.Collection;
22+
import java.util.function.BiConsumer;
2223

2324
import accord.api.VisibleForImplementation;
2425
import accord.coordinate.tracking.QuorumTracker;
@@ -32,6 +33,7 @@
3233
import accord.topology.Topologies;
3334
import accord.utils.async.AsyncResult;
3435
import accord.utils.async.AsyncResults;
36+
import accord.utils.async.AsyncResults.SettableByCallback;
3537

3638
import static accord.coordinate.tracking.RequestStatus.Failed;
3739
import static accord.coordinate.tracking.RequestStatus.Success;
@@ -46,14 +48,14 @@ public class CoordinateMaxConflict extends AbstractCoordinatePreAccept<Timestamp
4648
Timestamp maxConflict;
4749
long executionEpoch;
4850

49-
private CoordinateMaxConflict(Node node, FullRoute<?> route, long executionEpoch)
51+
private CoordinateMaxConflict(Node node, FullRoute<?> route, long executionEpoch, BiConsumer<Timestamp, Throwable> callback)
5052
{
51-
this(node, route, executionEpoch, node.topology().withUnsyncedEpochs(route, executionEpoch, executionEpoch));
53+
this(node, route, executionEpoch, node.topology().withUnsyncedEpochs(route, executionEpoch, executionEpoch), callback);
5254
}
5355

54-
private CoordinateMaxConflict(Node node, FullRoute<?> route, long executionEpoch, Topologies topologies)
56+
private CoordinateMaxConflict(Node node, FullRoute<?> route, long executionEpoch, Topologies topologies, BiConsumer<Timestamp, Throwable> callback)
5557
{
56-
super(node, route, null, topologies);
58+
super(node, route, null, topologies, callback);
5759
this.maxConflict = Timestamp.NONE;
5860
this.executionEpoch = executionEpoch;
5961
this.tracker = new QuorumTracker(topologies);
@@ -66,9 +68,11 @@ public static AsyncResult<Timestamp> maxConflict(Node node, Routables<?> keysOrR
6668
TopologyMismatch mismatch = TopologyMismatch.checkForMismatchOrPendingRemoval(node.topology().globalForEpoch(epoch), null, route.homeKey(), keysOrRanges);
6769
if (mismatch != null)
6870
return AsyncResults.failure(mismatch);
69-
CoordinateMaxConflict coordinate = new CoordinateMaxConflict(node, route, epoch);
71+
72+
SettableByCallback<Timestamp> result = new SettableByCallback<>();
73+
CoordinateMaxConflict coordinate = new CoordinateMaxConflict(node, route, epoch, result);
7074
coordinate.start();
71-
return coordinate;
75+
return result;
7276
}
7377

7478
@Override
@@ -91,13 +95,13 @@ void onSuccessInternal(Node.Id from, GetMaxConflictOk reply)
9195
void onFailureInternal(Node.Id from, Throwable failure)
9296
{
9397
if (tracker.recordFailure(from) == Failed)
94-
tryFailure(failure);
98+
setFailure(failure);
9599
}
96100

97101
@Override
98102
void onNewEpochTopologyMismatch(TopologyMismatch mismatch)
99103
{
100-
tryFailure(mismatch);
104+
setFailure(mismatch);
101105
}
102106

103107
@Override
@@ -109,6 +113,6 @@ long executeAtEpoch()
109113
@Override
110114
void onPreAccepted(Topologies topologies)
111115
{
112-
setSuccess(maxConflict);
116+
callback.accept(maxConflict, null);
113117
}
114118
}

accord-core/src/main/java/accord/coordinate/CoordinatePreAccept.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package accord.coordinate;
2020

2121
import java.util.Collection;
22+
import java.util.function.BiConsumer;
2223
import java.util.function.BiFunction;
2324

2425
import accord.coordinate.tracking.FastPathTracker;
@@ -59,19 +60,19 @@ abstract class CoordinatePreAccept<T> extends AbstractCoordinatePreAccept<T, Pre
5960
final Txn txn;
6061
boolean fastPathEnabled = true;
6162

62-
CoordinatePreAccept(Node node, TxnId txnId, Txn txn, FullRoute<?> route)
63+
CoordinatePreAccept(Node node, TxnId txnId, Txn txn, FullRoute<?> route, BiConsumer<T, Throwable> callback)
6364
{
64-
this(node, txnId, txn, route, node.topology().select(route, txnId, txnId, SHARE, QuorumEpochIntersections.preaccept.include));
65+
this(node, txnId, txn, route, node.topology().select(route, txnId, txnId, SHARE, QuorumEpochIntersections.preaccept.include), callback);
6566
}
6667

67-
CoordinatePreAccept(Node node, TxnId txnId, Txn txn, FullRoute<?> route, Topologies topologies)
68+
CoordinatePreAccept(Node node, TxnId txnId, Txn txn, FullRoute<?> route, Topologies topologies, BiConsumer<T, Throwable> callback)
6869
{
69-
this(node, txnId, txn, route, topologies, FastPathTracker::new);
70+
this(node, txnId, txn, route, topologies, FastPathTracker::new, callback);
7071
}
7172

72-
CoordinatePreAccept(Node node, TxnId txnId, Txn txn, FullRoute<?> route, Topologies topologies, BiFunction<Topologies, TxnId, PreAcceptTracker<?>> trackerFactory)
73+
CoordinatePreAccept(Node node, TxnId txnId, Txn txn, FullRoute<?> route, Topologies topologies, BiFunction<Topologies, TxnId, PreAcceptTracker<?>> trackerFactory, BiConsumer<T, Throwable> callback)
7374
{
74-
super(node, route, txnId, topologies);
75+
super(node, route, txnId, topologies, callback);
7576
this.tracker = trackerFactory.apply(topologies, txnId);
7677
this.oks = new SortedListMap<>(topologies.nodes(), PreAcceptOk[]::new);
7778
this.txn = txn;
@@ -141,15 +142,15 @@ void onNewEpochTopologyMismatch(TopologyMismatch mismatch)
141142
proposeInvalidate(node, node.uniqueTimestamp(Ballot::fromValues), txnId, route.homeKey(), (outcome, failure) -> {
142143
if (failure != null)
143144
mismatch.addSuppressed(failure);
144-
accept(null, mismatch);
145+
setFailure(mismatch);
145146
});
146147
}
147148

148149
@Override
149150
void onPreAccepted(Topologies topologies)
150151
{
151152
Timestamp executeAt = oks.foldlNonNullValues((ok, prev) -> mergeMaxAndFlags(ok.witnessedAt, prev), Timestamp.NONE);
152-
node.withEpoch(executeAt.epoch(), this, t -> WrappableException.wrap(t), () -> {
153+
node.withEpochExact(executeAt.epoch(), callback, t -> WrappableException.wrap(t), () -> {
153154
onPreAccepted(topologies, executeAt, oks);
154155
if (!Invariants.debug()) oks.clear();
155156
});

0 commit comments

Comments
 (0)