From 6d303e6843169a92e8ef02ab440e645d50ccba83 Mon Sep 17 00:00:00 2001 From: Benedict Elliott Smith Date: Sun, 13 Apr 2025 14:41:34 +0100 Subject: [PATCH] Improve: - Introduce SequentialAgentExecutor - ExecuteEphemeralRead.LocalExecute (matching ExecuteTxn.LocalExecute) - remove AgentExecutor (agent() only used as implementation detail) - Skip CommandStore/CommandsForKey on read or apply, when safe to do so - Implement maybeExecuteImmediately - Faster maxTimestamp Fix: - Marking vestigial without knowing all covering keys - Incorrect size in WaitingOn.none - Cleanup RX that are not known but are no longer needed, due to being defunct - Home should not abort because of local truncation; may still be incomplete on another shard - Invalidate infinite loop due to interpreting Vestigial as a possible decision (as though it were another Truncated state) - Known.isTruncated reporting incorrect answer in some cases, leading to infinite RecoverWithRoute loops as not slicing to correct subset and some shards are truncated and reject the deps collection - BurnTest slowCoordinatorDelay should grow with retryCount - NotAccept should check both ballot and saveStatus; PreCommitted or later can be propagated by Apply that has an earlier ballot - Handle awaiting locally retired epoch - Fix propagate low epoch of sync points - Fix GetEphemeralReadDeps.reduce NPE - Don't use tombstones to ERASE journal entries, as want to produce an Erased SaveStatus until EXPUNGE patch by Benedict; reviewed by Alex Petrov for CASSANDRA-20726 --- modules/accord | 2 +- .../cassandra/concurrent/DebuggableTask.java | 7 +- .../cassandra/concurrent/SEPWorker.java | 17 +- .../concurrent/SharedExecutorPool.java | 11 +- .../db/compaction/CompactionIterator.java | 18 +- .../db/partitions/AbstractBTreePartition.java | 29 + .../db/partitions/PartitionUpdate.java | 53 +- .../apache/cassandra/db/rows/BTreeRow.java | 1 - .../cassandra/db/rows/ComplexColumnData.java | 5 +- .../cassandra/db/virtual/QueriesTable.java | 27 +- .../cassandra/metrics/AccordMetrics.java | 36 +- src/java/org/apache/cassandra/net/Verb.java | 12 +- .../apache/cassandra/schema/TableParams.java | 2 +- .../service/accord/AccordCacheEntry.java | 55 +- .../service/accord/AccordCommandStore.java | 80 +-- .../service/accord/AccordCommandStores.java | 10 + .../service/accord/AccordDataStore.java | 7 +- .../service/accord/AccordExecutor.java | 671 +++++++++++++----- .../AccordExecutorAbstractLockLoop.java | 284 ++++---- .../accord/AccordExecutorAsyncSubmit.java | 6 + .../service/accord/AccordExecutorLoops.java | 26 +- .../accord/AccordExecutorSemiSyncSubmit.java | 6 + .../service/accord/AccordExecutorSimple.java | 4 +- .../accord/AccordExecutorSyncSubmit.java | 6 + .../accord/AccordFetchCoordinator.java | 24 +- .../service/accord/AccordMessageSink.java | 4 +- .../service/accord/AccordObjectSizes.java | 4 +- .../service/accord/AccordService.java | 31 +- .../cassandra/service/accord/AccordTask.java | 110 ++- .../service/accord/AccordVerbHandler.java | 2 +- .../service/accord/IAccordService.java | 14 + .../accord/ImmediateAsyncExecutor.java | 68 ++ .../service/accord/api/AccordAgent.java | 17 +- .../accord/api/AccordViolationHandler.java | 45 ++ .../accord/interop/AccordInteropAdapter.java | 35 +- .../accord/interop/AccordInteropApply.java | 19 +- .../interop/AccordInteropExecution.java | 45 +- .../accord/interop/AccordInteropPersist.java | 6 +- .../accord/interop/AccordInteropRead.java | 50 +- .../interop/AccordInteropReadRepair.java | 15 +- .../interop/AccordInteropStableThenRead.java | 3 +- .../accord/serializers/ApplySerializers.java | 18 +- .../serializers/ReadDataSerializer.java | 360 ++++++++++ .../serializers/ReadDataSerializers.java | 369 ---------- .../cassandra/service/accord/txn/TxnData.java | 87 ++- .../service/accord/txn/TxnDataKeyValue.java | 8 + .../service/accord/txn/TxnDataRangeValue.java | 45 +- .../service/accord/txn/TxnDataValue.java | 5 + .../service/accord/txn/TxnNamedRead.java | 169 ++--- .../cassandra/service/accord/txn/TxnRead.java | 47 +- .../service/accord/txn/TxnWrite.java | 23 +- .../distributed/test/QueriesTableTest.java | 9 +- .../test/accord/AccordLoadTest.java | 14 +- .../test/accord/AccordMetricsTest.java | 16 +- .../AccordReadInteroperabilityTest.java | 3 +- .../CompactionAccordIteratorsTest.java | 25 +- .../db/virtual/AccordDebugKeyspaceTest.java | 1 + .../index/accord/RouteIndexTest.java | 2 +- .../accord/AccordCommandStoreTest.java | 3 +- .../service/accord/AccordMessageSinkTest.java | 2 +- .../service/accord/AccordTaskTest.java | 33 +- .../service/accord/AccordTestUtils.java | 49 +- .../accord/SimulatedAccordCommandStore.java | 27 + .../CommandsForKeySerializerTest.java | 11 +- 64 files changed, 1938 insertions(+), 1255 deletions(-) create mode 100644 src/java/org/apache/cassandra/service/accord/ImmediateAsyncExecutor.java create mode 100644 src/java/org/apache/cassandra/service/accord/api/AccordViolationHandler.java create mode 100644 src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializer.java delete mode 100644 src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializers.java diff --git a/modules/accord b/modules/accord index 07ba9c85d0..154c05f50d 160000 --- a/modules/accord +++ b/modules/accord @@ -1 +1 @@ -Subproject commit 07ba9c85d083b586ff5c2382ef747b0fe71a9c47 +Subproject commit 154c05f50dcfe9cb69d14e4a67a137ecd0600e88 diff --git a/src/java/org/apache/cassandra/concurrent/DebuggableTask.java b/src/java/org/apache/cassandra/concurrent/DebuggableTask.java index 4e0d6d407c..3bc2d468fd 100644 --- a/src/java/org/apache/cassandra/concurrent/DebuggableTask.java +++ b/src/java/org/apache/cassandra/concurrent/DebuggableTask.java @@ -42,9 +42,14 @@ public interface DebuggableTask public long startTimeNanos(); public String description(); - + interface RunnableDebuggableTask extends Runnable, DebuggableTask {} interface CallableDebuggableTask extends Callable, DebuggableTask {} + interface DebuggableTaskRunner + { + DebuggableTask running(); + String id(); + } /** * Wraps a {@link DebuggableTask} to include the name of the thread running it. diff --git a/src/java/org/apache/cassandra/concurrent/SEPWorker.java b/src/java/org/apache/cassandra/concurrent/SEPWorker.java index add2ef8b06..01a8db45ee 100644 --- a/src/java/org/apache/cassandra/concurrent/SEPWorker.java +++ b/src/java/org/apache/cassandra/concurrent/SEPWorker.java @@ -33,7 +33,7 @@ import static org.apache.cassandra.concurrent.SEPExecutor.TakeTaskPermitResult.T import static org.apache.cassandra.config.CassandraRelevantProperties.SET_SEP_THREAD_NAME; import static org.apache.cassandra.utils.Clock.Global.nanoTime; -final class SEPWorker extends AtomicReference implements Runnable +final class SEPWorker extends AtomicReference implements Runnable, DebuggableTask.DebuggableTaskRunner { private static final Logger logger = LoggerFactory.getLogger(SEPWorker.class); private static final boolean SET_THREAD_NAME = SET_SEP_THREAD_NAME.getBoolean(); @@ -63,10 +63,8 @@ final class SEPWorker extends AtomicReference implements Runnabl thread.start(); } - /** - * @return the current {@link DebuggableTask}, if one exists - */ - public DebuggableTask currentDebuggableTask() + @Override + public DebuggableTask running() { // can change after null check so go off local reference Runnable task = currentTask.get(); @@ -77,10 +75,17 @@ final class SEPWorker extends AtomicReference implements Runnabl if (task instanceof FutureTask) return ((FutureTask) task).debuggableTask(); - + return null; } + // note this is not guaranteed to be in sync with running() + @Override + public String id() + { + return thread.getName(); + } + public void run() { /* diff --git a/src/java/org/apache/cassandra/concurrent/SharedExecutorPool.java b/src/java/org/apache/cassandra/concurrent/SharedExecutorPool.java index 0631ec61da..3ad848d325 100644 --- a/src/java/org/apache/cassandra/concurrent/SharedExecutorPool.java +++ b/src/java/org/apache/cassandra/concurrent/SharedExecutorPool.java @@ -29,9 +29,9 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; -import java.util.stream.Collectors; +import java.util.stream.Stream; -import org.apache.cassandra.concurrent.DebuggableTask.RunningDebuggableTask; +import org.apache.cassandra.concurrent.DebuggableTask.DebuggableTaskRunner; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.concurrent.SEPWorker.Work; @@ -121,12 +121,9 @@ public class SharedExecutorPool allWorkers.remove(worker); } - public List runningTasks() + public Stream workers() { - return allWorkers.stream() - .map(worker -> new RunningDebuggableTask(worker.toString(), worker.currentDebuggableTask())) - .filter(RunningDebuggableTask::hasTask) - .collect(Collectors.toList()); + return allWorkers.stream(); } void maybeStartSpinningWorker() diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionIterator.java b/src/java/org/apache/cassandra/db/compaction/CompactionIterator.java index 9619e379e2..2bba287663 100644 --- a/src/java/org/apache/cassandra/db/compaction/CompactionIterator.java +++ b/src/java/org/apache/cassandra/db/compaction/CompactionIterator.java @@ -119,6 +119,7 @@ import static org.apache.cassandra.config.Config.PaxosStatePurging.legacy; import static org.apache.cassandra.config.DatabaseDescriptor.paxosStatePurging; import static org.apache.cassandra.service.accord.AccordKeyspace.CFKAccessor; import static org.apache.cassandra.service.accord.AccordKeyspace.JournalColumns.getJournalKey; +import static org.apache.cassandra.service.accord.AccordKeyspace.JournalColumns.key; /** * Merge multiple iterators over the content of sstable into a "compacted" iterator. @@ -1066,8 +1067,6 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte for (int i = 0; i < entries.size() ; ++i) entries.get(i).clear(); entries.clear(); - if (!partition.partitionLevelDeletion().isLive()) - mainBuilder.addCleanup(false, ERASE); } @Override @@ -1138,13 +1137,22 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte return newVersion.build().unfilteredIterator(); } - private UnfilteredRowIterator erase(DecoratedKey partitionKey) + private UnfilteredRowIterator erase(DecoratedKey partitionKey) throws IOException { - return PartitionUpdate.fullPartitionDelete(AccordKeyspace.Journal, partitionKey, Long.MAX_VALUE, nowInSec).unfilteredIterator(); + AccordCommandRowEntry entry = entries.get(entries.size() - 1); + entry.builder.addCleanup(false, ERASE); + return PartitionUpdate.singleRowUpdate(AccordKeyspace.Journal, partitionKey, toRow(entry)).unfilteredIterator(); + } + + private BTreeRow toRow(AccordCommandRowEntry entry) throws IOException + { + Object[] newRow = rowTemplate.clone(); + newRow[0] = BufferCell.live(AccordKeyspace.JournalColumns.record, timestamp, entry.builder.asByteBuffer(userVersion)); + newRow[1] = userVersionCell; + return BTreeRow.create(entry.row.clustering(), entry.row.primaryKeyLivenessInfo(), entry.row.deletion(), newRow); } } - private static class AbortableUnfilteredPartitionTransformation extends Transformation { private final AbortableUnfilteredRowTransformation abortableIter; diff --git a/src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java b/src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java index 923ef54ab6..e4a653bec9 100644 --- a/src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java +++ b/src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java @@ -51,6 +51,7 @@ import org.apache.cassandra.utils.SearchIterator; import org.apache.cassandra.utils.btree.BTree; import org.apache.cassandra.utils.btree.BTree.Dir; +import static org.apache.cassandra.db.rows.Rows.EMPTY_STATIC_ROW; import static org.apache.cassandra.utils.btree.BTree.Dir.desc; public abstract class AbstractBTreePartition implements Partition, Iterable @@ -442,4 +443,32 @@ public abstract class AbstractBTreePartition implements Partition, Iterable return BTree.findByIndex(tree, BTree.size(tree) - 1); } + + /** + * The maximum timestamp used in this partition. + * + * @return the maximum timestamp used in this update. + */ + public long maxTimestamp() + { + return maxTimestamp(0); + } + + protected long maxTimestamp(long atLeast) + { + BTreePartitionData holder = holder(); + long maxTimestamp = BTree.accumulate(holder.tree, (row, maxRowTs) -> { + return row.accumulate((cd, maxCdTs) -> { + return Math.max(cd.maxTimestamp(), maxCdTs); + }, Math.max(row.primaryKeyLivenessInfo().timestamp(), maxRowTs)); + }, Math.max(atLeast, holder.deletionInfo.maxTimestamp())); + + Row staticRow = holder.staticRow; + if (staticRow != EMPTY_STATIC_ROW) + { + maxTimestamp = Math.max(maxTimestamp, staticRow.primaryKeyLivenessInfo().timestamp()); + maxTimestamp = staticRow.accumulate((cd, maxCdTs) -> Math.max(cd.maxTimestamp(), maxCdTs), maxTimestamp); + } + return maxTimestamp; + } } diff --git a/src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java b/src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java index 2b776e31c0..3da60b3eb5 100644 --- a/src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java +++ b/src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java @@ -52,7 +52,6 @@ import org.apache.cassandra.db.rows.BTreeRow; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.db.rows.CellPath; import org.apache.cassandra.db.rows.ColumnData; -import org.apache.cassandra.db.rows.ComplexColumnData; import org.apache.cassandra.db.rows.DeserializationHelper; import org.apache.cassandra.db.rows.EncodingStats; import org.apache.cassandra.db.rows.RangeTombstoneMarker; @@ -87,6 +86,7 @@ import org.apache.cassandra.utils.btree.UpdateFunction; import org.apache.cassandra.utils.vint.VIntCoding; import static org.apache.cassandra.db.SerializationHeader.StableHeaderSerializer.STABLE; +import static org.apache.cassandra.db.rows.Rows.EMPTY_STATIC_ROW; import static org.apache.cassandra.db.rows.UnfilteredRowIteratorSerializer.IS_EMPTY; /** @@ -142,7 +142,7 @@ public class PartitionUpdate extends AbstractBTreePartition public static PartitionUpdate emptyUpdate(TableMetadata metadata, DecoratedKey key) { MutableDeletionInfo deletionInfo = MutableDeletionInfo.live(); - BTreePartitionData holder = new BTreePartitionData(RegularAndStaticColumns.NONE, BTree.empty(), deletionInfo, Rows.EMPTY_STATIC_ROW, EncodingStats.NO_STATS); + BTreePartitionData holder = new BTreePartitionData(RegularAndStaticColumns.NONE, BTree.empty(), deletionInfo, EMPTY_STATIC_ROW, EncodingStats.NO_STATS); return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, false); } @@ -159,7 +159,7 @@ public class PartitionUpdate extends AbstractBTreePartition public static PartitionUpdate fullPartitionDelete(TableMetadata metadata, DecoratedKey key, long timestamp, long nowInSec) { MutableDeletionInfo deletionInfo = new MutableDeletionInfo(timestamp, nowInSec); - BTreePartitionData holder = new BTreePartitionData(RegularAndStaticColumns.NONE, BTree.empty(), deletionInfo, Rows.EMPTY_STATIC_ROW, EncodingStats.NO_STATS); + BTreePartitionData holder = new BTreePartitionData(RegularAndStaticColumns.NONE, BTree.empty(), deletionInfo, EMPTY_STATIC_ROW, EncodingStats.NO_STATS); return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, false); } @@ -183,7 +183,7 @@ public class PartitionUpdate extends AbstractBTreePartition ), row == null ? BTree.empty() : BTree.singleton(row), deletionInfo, - staticRow == null ? Rows.EMPTY_STATIC_ROW : staticRow, + staticRow == null ? EMPTY_STATIC_ROW : staticRow, EncodingStats.NO_STATS ); return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, false); @@ -222,7 +222,7 @@ public class PartitionUpdate extends AbstractBTreePartition columns = columns.mergeTo(Columns.from(row)); BTreePartitionData holder = new BTreePartitionData(new RegularAndStaticColumns(Columns.NONE, columns), - BTree.build(rows), deletionInfo, Rows.EMPTY_STATIC_ROW, + BTree.build(rows), deletionInfo, EMPTY_STATIC_ROW, EncodingStats.NO_STATS); return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, false); } @@ -484,44 +484,7 @@ public class PartitionUpdate extends AbstractBTreePartition */ public long maxTimestamp() { - long maxTimestamp = deletionInfo.maxTimestamp(); - for (Row row : this) - { - maxTimestamp = Math.max(maxTimestamp, row.primaryKeyLivenessInfo().timestamp()); - for (ColumnData cd : row) - { - if (cd.column().isSimple()) - { - maxTimestamp = Math.max(maxTimestamp, ((Cell)cd).timestamp()); - } - else - { - ComplexColumnData complexData = (ComplexColumnData)cd; - maxTimestamp = Math.max(maxTimestamp, complexData.complexDeletion().markedForDeleteAt()); - for (Cell cell : complexData) - maxTimestamp = Math.max(maxTimestamp, cell.timestamp()); - } - } - } - - if (this.holder.staticRow != null) - { - for (ColumnData cd : this.holder.staticRow.columnData()) - { - if (cd.column().isSimple()) - { - maxTimestamp = Math.max(maxTimestamp, ((Cell) cd).timestamp()); - } - else - { - ComplexColumnData complexData = (ComplexColumnData) cd; - maxTimestamp = Math.max(maxTimestamp, complexData.complexDeletion().markedForDeleteAt()); - for (Cell cell : complexData) - maxTimestamp = Math.max(maxTimestamp, cell.timestamp()); - } - } - } - return maxTimestamp; + return maxTimestamp(deletionInfo.maxTimestamp()); } /** @@ -1003,7 +966,7 @@ public class PartitionUpdate extends AbstractBTreePartition private BTree.Builder rowBuilder; private final int initialRowCapacity; - private Row staticRow = Rows.EMPTY_STATIC_ROW; + private Row staticRow = EMPTY_STATIC_ROW; private final RegularAndStaticColumns columns; private boolean isBuilt = false; @@ -1013,7 +976,7 @@ public class PartitionUpdate extends AbstractBTreePartition int initialRowCapacity, boolean canHaveShadowedData) { - this(metadata, key, columns, initialRowCapacity, canHaveShadowedData, Rows.EMPTY_STATIC_ROW, MutableDeletionInfo.live(), BTree.empty()); + this(metadata, key, columns, initialRowCapacity, canHaveShadowedData, EMPTY_STATIC_ROW, MutableDeletionInfo.live(), BTree.empty()); } public Builder(TableMetadata metadata, diff --git a/src/java/org/apache/cassandra/db/rows/BTreeRow.java b/src/java/org/apache/cassandra/db/rows/BTreeRow.java index 84ebdc00be..bf6b5b5061 100644 --- a/src/java/org/apache/cassandra/db/rows/BTreeRow.java +++ b/src/java/org/apache/cassandra/db/rows/BTreeRow.java @@ -330,7 +330,6 @@ public class BTreeRow extends AbstractRow if (!mayFilterColumns && !mayHaveShadowed && droppedColumns.isEmpty()) return this; - LivenessInfo newInfo = primaryKeyLivenessInfo; Deletion newDeletion = deletion; if (mayHaveShadowed) diff --git a/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java b/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java index 129ad69a4f..84dbc0a8f7 100644 --- a/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java +++ b/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java @@ -286,10 +286,7 @@ public class ComplexColumnData extends ColumnData implements Iterable> public long maxTimestamp() { - long timestamp = complexDeletion.markedForDeleteAt(); - for (Cell cell : this) - timestamp = Math.max(timestamp, cell.timestamp()); - return timestamp; + return BTree.accumulate(cells, (cell, ts) -> Math.max(ts, cell.timestamp()), complexDeletion.markedForDeleteAt()); } // This is the partner in crime of ArrayBackedRow.setValue. The exact warning apply. The short diff --git a/src/java/org/apache/cassandra/db/virtual/QueriesTable.java b/src/java/org/apache/cassandra/db/virtual/QueriesTable.java index b4b03dedfe..47b5a0c9f2 100644 --- a/src/java/org/apache/cassandra/db/virtual/QueriesTable.java +++ b/src/java/org/apache/cassandra/db/virtual/QueriesTable.java @@ -17,12 +17,16 @@ */ package org.apache.cassandra.db.virtual; +import java.util.stream.Stream; + import org.apache.cassandra.concurrent.DebuggableTask; import org.apache.cassandra.concurrent.SharedExecutorPool; import org.apache.cassandra.db.marshal.LongType; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.dht.LocalPartitioner; import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.service.accord.AccordExecutor; +import org.apache.cassandra.service.accord.AccordService; import static java.lang.Long.max; import static java.util.concurrent.TimeUnit.NANOSECONDS; @@ -72,24 +76,29 @@ final class QueriesTable extends AbstractVirtualTable public DataSet data() { SimpleDataSet result = new SimpleDataSet(metadata()); - - for (DebuggableTask.RunningDebuggableTask task : SharedExecutorPool.SHARED.runningTasks()) - { - if (!task.hasTask()) continue; - + + Stream streams = Stream.concat( + SharedExecutorPool.SHARED.workers(), + AccordService.started() ? AccordService.instance().executors().stream().flatMap(AccordExecutor::active) : Stream.of() + ); + streams.forEach(runner -> { + DebuggableTask task = runner.running(); + if (task == null) return; + + String id = runner.id(); long creationTimeNanos = task.creationTimeNanos(); long startTimeNanos = task.startTimeNanos(); long now = nanoTime(); long queuedMicros = NANOSECONDS.toMicros(max((startTimeNanos > 0 ? startTimeNanos : now) - creationTimeNanos, 0)); long runningMicros = startTimeNanos > 0 ? NANOSECONDS.toMicros(now - startTimeNanos) : 0; - - result.row(task.threadId()) + + result.row(id) .column(QUEUED, queuedMicros) .column(RUNNING, runningMicros) .column(DESC, task.description()); - } - + }); + return result; } } diff --git a/src/java/org/apache/cassandra/metrics/AccordMetrics.java b/src/java/org/apache/cassandra/metrics/AccordMetrics.java index 6367fe7873..f489ec899e 100644 --- a/src/java/org/apache/cassandra/metrics/AccordMetrics.java +++ b/src/java/org/apache/cassandra/metrics/AccordMetrics.java @@ -42,7 +42,7 @@ public class AccordMetrics public final static AccordMetrics writeMetrics = new AccordMetrics("rw"); public static final String STABLE_LATENCY = "StableLatency"; - public static final String EXECUTE_LATENCY = "ExecuteLatency"; + public static final String PREAPPLY_LATENCY = "PreApplyLatency"; public static final String APPLY_LATENCY = "ApplyLatency"; public static final String APPLY_DURATION = "ApplyDuration"; public static final String PARTIAL_DEPENDENCIES = "PartialDependencies"; @@ -67,9 +67,9 @@ public class AccordMetrics public final Timer stableLatency; /** - * The time between start on the coordinator and execution on this replica. + * The time between start on the coordinator and arrival of the result on this replica. */ - public final Timer executeLatency; + public final Timer preapplyLatency; /** * The time between start on the coordinator and application on this replica. @@ -77,21 +77,26 @@ public class AccordMetrics public final Timer applyLatency; /** + * TODO (expected): probably more interesting is latency from preapplied to apply; + * we already track local write latencies, whch this effectively duplicates (but including queueing latencies) * Duration of applying changes. */ public final Timer applyDuration; /** - * A histogram of the number of dependencies per partial transaction at this replica. + * A histogram of the number of dependencies per transaction at this replica. */ - public final Histogram partialDependencies; + public final Histogram localDependencies; + /** + * TODO (expected): this should be a Gauge + */ public final Meter progressLogSize; /** * A histogram of the number of dependencies per transaction at this coordinator. */ - public final Histogram dependencies; + public final Histogram coordinatorDependencies; /** * The number of fast path transactions executed on this coordinator. @@ -137,14 +142,14 @@ public class AccordMetrics { DefaultNameFactory replica = new DefaultNameFactory(ACCORD_REPLICA, scope); stableLatency = Metrics.timer(replica.createMetricName(STABLE_LATENCY)); - executeLatency = Metrics.timer(replica.createMetricName(EXECUTE_LATENCY)); + preapplyLatency = Metrics.timer(replica.createMetricName(PREAPPLY_LATENCY)); applyLatency = Metrics.timer(replica.createMetricName(APPLY_LATENCY)); applyDuration = Metrics.timer(replica.createMetricName(APPLY_DURATION)); - partialDependencies = Metrics.histogram(replica.createMetricName(PARTIAL_DEPENDENCIES), true); + localDependencies = Metrics.histogram(replica.createMetricName(PARTIAL_DEPENDENCIES), true); progressLogSize = Metrics.meter(replica.createMetricName(PROGRESS_LOG_SIZE)); DefaultNameFactory coordinator = new DefaultNameFactory(ACCORD_COORDINATOR, scope); - dependencies = Metrics.histogram(coordinator.createMetricName(DEPENDENCIES), true); + coordinatorDependencies = Metrics.histogram(coordinator.createMetricName(DEPENDENCIES), true); fastPaths = Metrics.meter(coordinator.createMetricName(FAST_PATHS)); slowPaths = Metrics.meter(coordinator.createMetricName(SLOW_PATHS)); preempts = Metrics.meter(coordinator.createMetricName(PREEMPTS)); @@ -217,16 +222,16 @@ public class AccordMetrics } @Override - public void onExecuted(Command cmd) + public void onPreApplied(Command cmd) { long now = AccordTimeService.nowMicros(); AccordMetrics metrics = forTransaction(cmd.txnId()); if (metrics != null) { Timestamp trxTimestamp = cmd.txnId(); - metrics.executeLatency.update(now - trxTimestamp.hlc(), TimeUnit.MICROSECONDS); + metrics.preapplyLatency.update(now - trxTimestamp.hlc(), TimeUnit.MICROSECONDS); PartialDeps deps = cmd.partialDeps(); - metrics.partialDependencies.update(deps != null ? deps.txnIdCount() : 0); + metrics.localDependencies.update(deps != null ? deps.txnIdCount() : 0); } } @@ -239,7 +244,8 @@ public class AccordMetrics { Timestamp trxTimestamp = cmd.txnId(); metrics.applyLatency.update(now - trxTimestamp.hlc(), TimeUnit.MICROSECONDS); - metrics.applyDuration.update(now - applyStartTimestamp, TimeUnit.MICROSECONDS); + if (applyStartTimestamp > 0) + metrics.applyDuration.update(now - applyStartTimestamp, TimeUnit.MICROSECONDS); } } @@ -250,7 +256,7 @@ public class AccordMetrics if (metrics != null) { metrics.fastPaths.mark(); - metrics.dependencies.update(deps.txnIdCount()); + metrics.coordinatorDependencies.update(deps.txnIdCount()); } } @@ -261,7 +267,7 @@ public class AccordMetrics if (metrics != null) { metrics.slowPaths.mark(); - metrics.dependencies.update(deps.txnIdCount()); + metrics.coordinatorDependencies.update(deps.txnIdCount()); } } diff --git a/src/java/org/apache/cassandra/net/Verb.java b/src/java/org/apache/cassandra/net/Verb.java index 91d7b8ad58..3103b2058b 100644 --- a/src/java/org/apache/cassandra/net/Verb.java +++ b/src/java/org/apache/cassandra/net/Verb.java @@ -104,7 +104,7 @@ import org.apache.cassandra.service.accord.serializers.InformDurableSerializers; import org.apache.cassandra.service.accord.serializers.LatestDepsSerializers; import org.apache.cassandra.service.accord.serializers.PreacceptSerializers; import org.apache.cassandra.service.accord.serializers.GetDurableBeforeSerializers; -import org.apache.cassandra.service.accord.serializers.ReadDataSerializers; +import org.apache.cassandra.service.accord.serializers.ReadDataSerializer; import org.apache.cassandra.service.accord.serializers.RecoverySerializers; import org.apache.cassandra.service.accord.serializers.SetDurableSerializers; import org.apache.cassandra.service.accord.serializers.Version; @@ -318,14 +318,14 @@ public enum Verb ACCORD_ACCEPT_RSP (122, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(AcceptSerializers.reply), AccordService::responseHandlerOrNoop ), ACCORD_ACCEPT_REQ (123, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(AcceptSerializers.request), AccordService::requestHandlerOrNoop, ACCORD_ACCEPT_RSP ), ACCORD_NOT_ACCEPT_REQ (124, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(AcceptSerializers.notAccept), AccordService::requestHandlerOrNoop, ACCORD_ACCEPT_RSP ), - ACCORD_READ_RSP (125, P2, readTimeout, IMMEDIATE, () -> accordEmbedded(ReadDataSerializers.reply), AccordService::responseHandlerOrNoop ), - ACCORD_READ_REQ (126, P2, readTimeout, IMMEDIATE, () -> accordEmbedded(ReadDataSerializers.readData), AccordService::requestHandlerOrNoop, ACCORD_READ_RSP ), - ACCORD_STABLE_THEN_READ_REQ (127, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(ReadDataSerializers.stableThenRead), AccordService::requestHandlerOrNoop, ACCORD_READ_RSP ), + ACCORD_READ_RSP (125, P2, readTimeout, IMMEDIATE, () -> accordEmbedded(ReadDataSerializer.reply), AccordService::responseHandlerOrNoop ), + ACCORD_READ_REQ (126, P2, readTimeout, IMMEDIATE, () -> accordEmbedded(ReadDataSerializer.request), AccordService::requestHandlerOrNoop, ACCORD_READ_RSP ), + ACCORD_STABLE_THEN_READ_REQ (127, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(ReadDataSerializer.request), AccordService::requestHandlerOrNoop, ACCORD_READ_RSP ), ACCORD_COMMIT_REQ (128, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(CommitSerializers.request), AccordService::requestHandlerOrNoop, ACCORD_READ_RSP ), ACCORD_COMMIT_INVALIDATE_REQ (129, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(CommitSerializers.invalidate), AccordService::requestHandlerOrNoop ), ACCORD_APPLY_RSP (130, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(ApplySerializers.reply), AccordService::responseHandlerOrNoop ), ACCORD_APPLY_REQ (131, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(ApplySerializers.request), AccordService::requestHandlerOrNoop, ACCORD_APPLY_RSP ), - ACCORD_APPLY_AND_WAIT_REQ (132, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(ReadDataSerializers.readData), AccordService::requestHandlerOrNoop, ACCORD_READ_RSP), + ACCORD_APPLY_AND_WAIT_REQ (132, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(ReadDataSerializer.request), AccordService::requestHandlerOrNoop, ACCORD_READ_RSP), ACCORD_BEGIN_RECOVER_RSP (133, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(RecoverySerializers.reply), AccordService::responseHandlerOrNoop ), ACCORD_BEGIN_RECOVER_REQ (134, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(RecoverySerializers.request), AccordService::requestHandlerOrNoop, ACCORD_BEGIN_RECOVER_RSP ), ACCORD_BEGIN_INVALIDATE_RSP (135, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(BeginInvalidationSerializers.reply), AccordService::responseHandlerOrNoop ), @@ -333,7 +333,7 @@ public enum Verb ACCORD_AWAIT_RSP (137, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(AwaitSerializers.syncReply), AccordService::responseHandlerOrNoop ), ACCORD_AWAIT_REQ (138, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(AwaitSerializers.request), AccordService::requestHandlerOrNoop, ACCORD_AWAIT_RSP ), ACCORD_AWAIT_ASYNC_RSP_REQ (139, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(AwaitSerializers.asyncReply), AccordService::requestHandlerOrNoop ), - ACCORD_WAIT_UNTIL_APPLIED_REQ (140, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(ReadDataSerializers.waitUntilApplied), AccordService::requestHandlerOrNoop, ACCORD_READ_RSP ), + ACCORD_WAIT_UNTIL_APPLIED_REQ (140, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(ReadDataSerializer.request), AccordService::requestHandlerOrNoop, ACCORD_READ_RSP ), ACCORD_RECOVER_AWAIT_RSP (141, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(AwaitSerializers.recoverReply), AccordService::responseHandlerOrNoop ), ACCORD_RECOVER_AWAIT_REQ (142, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(AwaitSerializers.recoverRequest), AccordService::requestHandlerOrNoop, ACCORD_RECOVER_AWAIT_RSP), ACCORD_INFORM_DURABLE_REQ (143, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(InformDurableSerializers.request), AccordService::requestHandlerOrNoop, ACCORD_SIMPLE_RSP ), diff --git a/src/java/org/apache/cassandra/schema/TableParams.java b/src/java/org/apache/cassandra/schema/TableParams.java index e44af5ca56..fa8a2ac44c 100644 --- a/src/java/org/apache/cassandra/schema/TableParams.java +++ b/src/java/org/apache/cassandra/schema/TableParams.java @@ -293,7 +293,7 @@ public final class TableParams && extensions.equals(p.extensions) && cdc == p.cdc && readRepair == p.readRepair - && fastPath.equals(fastPath) + && fastPath.equals(p.fastPath) && transactionalMode == p.transactionalMode && transactionalMigrationFrom == p.transactionalMigrationFrom && pendingDrop == p.pendingDrop diff --git a/src/java/org/apache/cassandra/service/accord/AccordCacheEntry.java b/src/java/org/apache/cassandra/service/accord/AccordCacheEntry.java index f03d01c558..6ad5c58b20 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordCacheEntry.java +++ b/src/java/org/apache/cassandra/service/accord/AccordCacheEntry.java @@ -326,18 +326,32 @@ public class AccordCacheEntry extends IntrusiveLinkedListNode public

Loading load(BiFunction loadExecutor, P param, Adapter adapter, OnLoaded onLoaded) { Invariants.require(is(WAITING_TO_LOAD), "%s", this); - Loading loading = ((WaitingToLoad)state).load(loadExecutor.apply(param, () -> { - V result; - try + + WaitingToLoad cur = (WaitingToLoad)state; + Collection> waiters = cur.waiters; + Loading loading = cur.load(loadExecutor.apply(param, new Runnable() + { + @Override + public void run() { - result = adapter.load(owner.commandStore, key); + V result; + try + { + result = adapter.load(owner.commandStore, key); + } + catch (Throwable t) + { + onLoaded.onLoaded(AccordCacheEntry.this, null, t); + throw t; + } + onLoaded.onLoaded(AccordCacheEntry.this, result, null); } - catch (Throwable t) + + @Override + public String toString() { - onLoaded.onLoaded(this, null, t); - throw t; + return "Loading " + key + " on " + owner.commandStore + " for " + waiters; } - onLoaded.onLoaded(this, result, null); })); setStatus(LOADING); state = loading; @@ -459,17 +473,28 @@ public class AccordCacheEntry extends IntrusiveLinkedListNode { setStatus(SAVING); Object identity = new Object(); - Cancellable saving = saveExecutor.apply(() -> { - try + Cancellable saving = saveExecutor.apply(new Runnable() + { + @Override + public void run() { - save.run(); + try + { + save.run(); + } + catch (Throwable t) + { + onSaved.onSaved(AccordCacheEntry.this, identity, t); + throw t; + } + onSaved.onSaved(AccordCacheEntry.this, identity, null); } - catch (Throwable t) + + @Override + public String toString() { - onSaved.onSaved(this, identity, t); - throw t; + return "Saving " + key + " for Accord cache eviction"; } - onSaved.onSaved(this, identity, null); }); state = new Saving(saving, identity, state); } diff --git a/src/java/org/apache/cassandra/service/accord/AccordCommandStore.java b/src/java/org/apache/cassandra/service/accord/AccordCommandStore.java index ab74a128a7..9c07b7c5aa 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordCommandStore.java +++ b/src/java/org/apache/cassandra/service/accord/AccordCommandStore.java @@ -22,7 +22,6 @@ import java.util.List; import java.util.NavigableMap; import java.util.Objects; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; @@ -68,7 +67,6 @@ import org.apache.cassandra.service.accord.IAccordService.AccordCompactionInfo; import org.apache.cassandra.service.accord.api.TokenKey; import org.apache.cassandra.service.accord.txn.TxnRead; import org.apache.cassandra.utils.Clock; -import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import static accord.api.Journal.CommandUpdate; import static accord.api.Journal.FieldUpdates; @@ -145,8 +143,8 @@ public class AccordCommandStore extends CommandStore public final String loggingId; private final Journal journal; private final RangeSearcher rangeSearcher; - private final AccordExecutor executor; - private final Executor taskExecutor; + private final AccordExecutor sharedExecutor; + final AccordExecutor.SequentialExecutor exclusiveExecutor; private final ExclusiveCaches caches; private long lastSystemTimestampMicros = Long.MIN_VALUE; private final CommandsForRanges.Manager commandsForRanges; @@ -154,7 +152,6 @@ public class AccordCommandStore extends CommandStore volatile SafeRedundantBefore safeRedundantBefore; private AccordSafeCommandStore current; - private Thread currentThread; private final CommandStoreLoader loader; @@ -166,24 +163,24 @@ public class AccordCommandStore extends CommandStore LocalListeners.Factory listenerFactory, EpochUpdateHolder epochUpdateHolder, Journal journal, - AccordExecutor executor) + AccordExecutor sharedExecutor) { super(id, node, agent, dataStore, progressLogFactory, listenerFactory, epochUpdateHolder); this.loggingId = String.format("[%s]", id); this.journal = journal; this.rangeSearcher = RangeSearcher.extractRangeSearcher(journal); - this.executor = executor; + this.sharedExecutor = sharedExecutor; final AccordCache.Type.Instance commands; final AccordCache.Type.Instance commandsForKey; - try (AccordExecutor.ExclusiveGlobalCaches exclusive = executor.lockCaches()) + try (AccordExecutor.ExclusiveGlobalCaches exclusive = sharedExecutor.lockCaches()) { commands = exclusive.commands.newInstance(this); commandsForKey = exclusive.commandsForKey.newInstance(this); - this.caches = new ExclusiveCaches(executor.lock, exclusive.global, commands, commandsForKey); + this.caches = new ExclusiveCaches(sharedExecutor.lock, exclusive.global, commands, commandsForKey); } - this.taskExecutor = executor.executor(this); + this.exclusiveExecutor = sharedExecutor.executor(); this.commandsForRanges = new CommandsForRanges.Manager(this); this.loader = new CommandStoreLoader(this); @@ -212,7 +209,7 @@ public class AccordCommandStore extends CommandStore new AccordCommandStore(id, node, agent, dataStore, progressLogFactory, listenerFactory, rangesForEpoch, journal, executorFactory.apply(id)); } - public CommandsForRanges.Manager diskCommandsForRanges() + public CommandsForRanges.Manager commandsForRanges() { return commandsForRanges; } @@ -221,7 +218,7 @@ public class AccordCommandStore extends CommandStore public void markShardDurable(SafeCommandStore safeStore, TxnId globalSyncId, Ranges ranges, Status.Durability durability) { if (durability.compareTo(Status.Durability.UniversalOrInvalidated) >= 0) - store.snapshot(ranges, globalSyncId); + dataStore.snapshot(ranges, globalSyncId); super.markShardDurable(safeStore, globalSyncId, ranges, durability); if (durability.compareTo(Status.Durability.UniversalOrInvalidated) >= 0) commandsForRanges.gcBefore(globalSyncId, ranges); @@ -230,7 +227,7 @@ public class AccordCommandStore extends CommandStore @Override public boolean inStore() { - return currentThread == Thread.currentThread(); + return exclusiveExecutor.inExecutor(); } void tryPreSetup(AccordTask task) @@ -246,7 +243,7 @@ public class AccordCommandStore extends CommandStore public AccordExecutor executor() { - return executor; + return sharedExecutor; } // TODO (desired): we use this for executing callbacks with mutual exclusivity, @@ -254,7 +251,7 @@ public class AccordCommandStore extends CommandStore // inflate a separate queue dynamically in AccordExecutor public Executor taskExecutor() { - return taskExecutor; + return exclusiveExecutor; } public ExclusiveCaches lockCaches() @@ -273,7 +270,7 @@ public class AccordCommandStore extends CommandStore public Caches cachesExclusive() { - Invariants.require(executor.isOwningThread()); + Invariants.require(sharedExecutor.isOwningThread()); return caches; } @@ -346,43 +343,12 @@ public class AccordCommandStore extends CommandStore return AsyncChains.ofCallable(taskExecutor(), task); } - public DataStore dataStore() - { - return store; - } - - NodeCommandStoreService node() - { - return node; - } - - ProgressLog progressLog() - { - return progressLog; - } - @Override public AsyncChain build(PreLoadContext preLoadContext, Consumer consumer) { return AccordTask.create(this, preLoadContext, consumer).chain(); } - public void executeBlocking(Runnable runnable) - { - try - { - executor.submit(runnable).get(); - } - catch (InterruptedException e) - { - throw new UncheckedInterruptedException(e); - } - catch (ExecutionException e) - { - throw new RuntimeException(e); - } - } - public AccordSafeCommandStore begin(AccordTask operation, @Nullable CommandsForRanges commandsForRanges) { @@ -391,18 +357,21 @@ public class AccordCommandStore extends CommandStore return current; } - void setOwner(Thread thread, Thread self) - { - Invariants.require(thread == null ? currentThread == self : currentThread == null); - currentThread = thread; - if (thread != null) CommandStore.register(this); - } - public boolean hasSafeStore() { return current != null; } + DataStore dataStore() + { + return dataStore; + } + + ProgressLog progressLog() + { + return progressLog; + } + public void complete(AccordSafeCommandStore store) { require(current == store); @@ -412,7 +381,6 @@ public class AccordCommandStore extends CommandStore public void abort(AccordSafeCommandStore store) { - checkInStore(); Invariants.require(store == current); current = null; } @@ -454,7 +422,7 @@ public class AccordCommandStore extends CommandStore if (addRanges.intersects(coordinateRanges)) continue; addRanges = redundantBefore.removeGcBefore(txnId, txnId, addRanges); if (addRanges.isEmpty()) continue; - diskCommandsForRanges().mergeTransitive(txnId, addRanges, Ranges::with); + commandsForRanges().mergeTransitive(txnId, addRanges, Ranges::with); } } } diff --git a/src/java/org/apache/cassandra/service/accord/AccordCommandStores.java b/src/java/org/apache/cassandra/service/accord/AccordCommandStores.java index 6fa8fa6224..11276a7a6c 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordCommandStores.java +++ b/src/java/org/apache/cassandra/service/accord/AccordCommandStores.java @@ -32,6 +32,7 @@ import accord.api.ProgressLog; import accord.local.CommandStores; import accord.local.Node; import accord.local.NodeCommandStoreService; +import accord.local.SequentialAsyncExecutor; import accord.local.ShardDistributor; import accord.primitives.Range; import accord.topology.Topology; @@ -61,6 +62,7 @@ public class AccordCommandStores extends CommandStores implements CacheSize private final CacheSizeMetrics cacheSizeMetrics; private final AccordExecutor[] executors; + private final int mask; private long cacheSize, workingSetSize; private int maxQueuedLoads, maxQueuedRangeLoads; @@ -74,6 +76,7 @@ public class AccordCommandStores extends CommandStores implements CacheSize AccordCommandStore.factory(id -> executors[id % executors.length])); this.executors = executors; this.cacheSizeMetrics = new CacheSizeMetrics(ACCORD_STATE_CACHE, this); + this.mask = Integer.highestOneBit(executors.length) - 1; cacheSize = DatabaseDescriptor.getAccordCacheSizeInMiB() << 20; workingSetSize = DatabaseDescriptor.getAccordWorkingSetSizeInMiB() << 20; maxQueuedLoads = DatabaseDescriptor.getAccordMaxQueuedLoadCount(); @@ -134,6 +137,13 @@ public class AccordCommandStores extends CommandStores implements CacheSize return contains(previous, ((TokenKey) range.start()).table()); } + @Override + public SequentialAsyncExecutor someSequentialExecutor() + { + int idx = ((int) Thread.currentThread().getId()) & mask; + return executors[idx].newSequentialExecutor(); + } + private static boolean contains(Topology previous, TableId searchTable) { for (Range range : previous.ranges()) diff --git a/src/java/org/apache/cassandra/service/accord/AccordDataStore.java b/src/java/org/apache/cassandra/service/accord/AccordDataStore.java index 2ab5c98641..993e713115 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordDataStore.java +++ b/src/java/org/apache/cassandra/service/accord/AccordDataStore.java @@ -103,13 +103,14 @@ public class AccordDataStore implements DataStore List> futures = new ArrayList<>(); for (Map.Entry e : tables.entrySet()) { + // TODO (required): is it safe to ignore null tableMetadata (or ColumnFamilyStore below)? TableMetadata tableMetadata = metadata.schema.getTableMetadata(e.getKey()); - SnapshotBounds bounds = e.getValue(); - ColumnFamilyStore cfs = Keyspace.openAndGetStoreIfExists(tableMetadata); + if (tableMetadata == null) continue; - // TODO (required): is it safe to ignore null cfs? + ColumnFamilyStore cfs = Keyspace.openAndGetStoreIfExists(tableMetadata); if (cfs == null) continue; + SnapshotBounds bounds = e.getValue(); View view = cfs.getTracker().getView(); for (Memtable memtable : view.getAllMemtables()) { diff --git a/src/java/org/apache/cassandra/service/accord/AccordExecutor.java b/src/java/org/apache/cassandra/service/accord/AccordExecutor.java index 9d5c97b463..018fe7d2ef 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordExecutor.java +++ b/src/java/org/apache/cassandra/service/accord/AccordExecutor.java @@ -19,18 +19,26 @@ package org.apache.cassandra.service.accord; import java.util.concurrent.Callable; -import java.util.concurrent.Executor; +import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.LockSupport; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; +import java.util.stream.Stream; + +import javax.annotation.Nullable; + +import com.google.common.base.Functions; import accord.api.Agent; +import accord.api.AsyncExecutor; import accord.api.RoutingKey; -import accord.local.AgentExecutor; import accord.local.Command; +import accord.local.SequentialAsyncExecutor; import accord.local.cfk.CommandsForKey; import accord.primitives.TxnId; import accord.utils.ArrayBuffers.BufferList; @@ -45,12 +53,14 @@ import accord.utils.UnhandledEnum; import accord.utils.async.AsyncChain; import accord.utils.async.AsyncChains; import accord.utils.async.Cancellable; -import org.agrona.collections.Object2ObjectHashMap; import org.apache.cassandra.cache.CacheSize; +import org.apache.cassandra.concurrent.DebuggableTask; +import org.apache.cassandra.concurrent.DebuggableTask.DebuggableTaskRunner; import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.concurrent.ScheduledExecutors; import org.apache.cassandra.concurrent.Shutdownable; import org.apache.cassandra.metrics.AccordCacheMetrics; +import org.apache.cassandra.utils.MonotonicClock; import org.apache.cassandra.utils.concurrent.AsyncPromise; import org.apache.cassandra.utils.concurrent.Future; @@ -62,9 +72,11 @@ import static org.apache.cassandra.service.accord.AccordTask.State.LOADING; import static org.apache.cassandra.service.accord.AccordTask.State.SCANNING_RANGES; import static org.apache.cassandra.service.accord.AccordTask.State.WAITING_TO_LOAD; import static org.apache.cassandra.service.accord.AccordTask.State.WAITING_TO_RUN; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; -public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLoaded, AccordCacheEntry.OnSaved, Shutdownable, AgentExecutor +/** + * NOTE: We assume that NO BLOCKING TASKS are submitted to this executor AND WAITED ON by another task executing on this executor. + */ +public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLoaded, AccordCacheEntry.OnSaved, Shutdownable, AsyncExecutor { public interface AccordExecutorFactory { @@ -122,7 +134,6 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo private final TaskQueue> waitingToLoad = new TaskQueue<>(WAITING_TO_LOAD); private final TaskQueue waitingToRun = new TaskQueue<>(WAITING_TO_RUN); - private final Object2ObjectHashMap commandStoreQueues = new Object2ObjectHashMap<>(); private final AccordCacheEntry.OnLoaded onRangeLoaded = this::onRangeLoaded; private final ExclusiveGlobalCaches caches; @@ -208,6 +219,11 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo maybeUnpauseLoading(); } + public Stream active() + { + return Stream.of(); + } + void maybeUnpauseLoading() { if (!hasPausedLoading) @@ -318,16 +334,29 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo return true; } - @Override - public Agent agent() - { - return agent; - } - @Override public AsyncChain build(Callable task) { - return AsyncChains.ofCallable(this, task); + return new AsyncChains.Head<>() + { + @Override + protected Cancellable start(BiConsumer callback) + { + return submit(new PlainChain<>(task, callback, null)); + } + }; + } + + public AsyncChain buildDebuggable(Callable task, Object describe) + { + return new AsyncChains.Head<>() + { + @Override + protected Cancellable start(BiConsumer callback) + { + return submit(new DebuggableChain<>(task, callback, null, describe)); + } + }; } private void parkRangeLoad(AccordTask task) @@ -343,10 +372,8 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo { try { - if (object instanceof AccordTask) - loadExclusive((AccordTask) object); - else - ((SubmitAsync) object).acceptExclusive(this); + if (object instanceof SubmittableTask) ((SubmittableTask) object).submitExclusive(this); + else ((SubmitAsync) object).submitExclusive(this); } catch (Throwable t) { @@ -371,31 +398,26 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo task.addToQueue(loading); break; case WAITING_TO_RUN: - task.runQueuedAt = nanoTime(); - commandStoreQueues.computeIfAbsent(task.commandStore, CommandStoreQueue::new) - .appendOrSetNext(task); + waitingToRun(task); break; } } - - private void waitingToRun(Task task) + + private void waitingToRun(AccordTask task) { - if (task.commandStore == null) - { - waitingToRun.append(task); - } - else - { - commandStoreQueues.computeIfAbsent(task.commandStore, CommandStoreQueue::new) - .appendOrSetNext(task); - } + task.addToQueue(task.commandStore.exclusiveExecutor); + } + + private void waitingToRun(SubmittableTask task, @Nullable SequentialExecutor queue) + { + task.addToQueue(queue == null ? waitingToRun : queue); } private Cancellable submitIOExclusive(Task parent, Runnable run) { Invariants.require(isOwningThread()); ++tasks; - PlainRunnable task = new PlainRunnable(null, run, null); + IORunnable task = new IORunnable(run); // TODO (expected): adopt queue position of the submitting task if (parent == null) assignNewQueuePosition(task); else assignQueueSubPosition(parent, task); @@ -413,14 +435,19 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo task.queuePosition = parent.queuePosition | (++nextPosition & 0x7fffffff); } - public Executor executor(AccordCommandStore commandStore) + public SequentialExecutor executor() { - return task -> AccordExecutor.this.submit(task, commandStore); + return new SequentialExecutor(); + } + + public SequentialAsyncExecutor newSequentialExecutor() + { + return new SequentialExecutor(); } public void submit(AccordTask operation) { - submit(AccordExecutor::loadExclusive, Function.identity(), operation); + submit(AccordExecutor::submitExclusive, Function.identity(), operation); } public void cancel(AccordTask task) @@ -474,22 +501,14 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo abstract void submit(QuintConsumer sync, QuadFunction async, P1s p1s, P1a p1a, P2 p2, P3 p3, P4 p4); - private void submitExclusive(AsyncPromise result, Runnable run, AccordCommandStore commandStore) - { - ++tasks; - PlainRunnable task = new PlainRunnable(result, run, commandStore); - task.queuePosition = ++nextPosition; - waitingToRun(task); - } - - private void submitExclusive(AsyncPromise result, PlainRunnable task) + private void submitExclusive(Plain task) { ++tasks; task.queuePosition = ++nextPosition; - waitingToRun(task); + waitingToRun(task, task.executor()); } - private void loadExclusive(AccordTask task) + void submitExclusive(AccordTask task) { ++tasks; assignNewQueuePosition(task); @@ -559,7 +578,7 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo finally { task.unqueueIfQueued(); - task.cleanupExclusive(); + task.cleanupExclusive(null); } } @@ -609,17 +628,22 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo } // TODO (expected): offer queue jumping/priorities - public Future submit(Runnable run, AccordCommandStore commandStore) + private Future submit(Runnable run, @Nullable SequentialExecutor executor) { - PlainRunnable task = new PlainRunnable(new AsyncPromise<>(), run, commandStore); - AsyncPromise result = new AsyncPromise<>(); - submit(AccordExecutor::submitExclusive, SubmitPlainRunnable::new, result, run, commandStore); - return result; + PlainRunnable task = new PlainRunnable(new AsyncPromise<>(), run, executor); + submit(task); + return task.result; + } + + private Cancellable submit(Plain task) + { + submit(AccordExecutor::submitExclusive, Functions.identity(), task); + return task; } public void execute(Runnable command) { - submit(command); + submit(new PlainRunnable(null, command, null)); } public void executeDirectlyWithLock(Runnable command) @@ -637,7 +661,7 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo public void execute(Runnable command, AccordCommandStore commandStore) { - submit(command, commandStore); + submit(new PlainRunnable(null, command, commandStore.exclusiveExecutor)); } @Override @@ -682,20 +706,43 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo return cache.weightedSize(); } + protected static abstract class TaskRunner implements DebuggableTaskRunner + { + private volatile Task running; + private static final AtomicReferenceFieldUpdater runningUpdater = AtomicReferenceFieldUpdater.newUpdater(TaskRunner.class, Task.class, "running"); + + @Override + public DebuggableTask running() + { + Task running = this.running; + return running == null ? null : running.debuggable(); + } + + void setDebuggable(Task debuggable) + { + runningUpdater.lazySet(this, debuggable); + } + + void clearDebuggable() + { + runningUpdater.lazySet(this, null); + } + } + public static abstract class Task extends IntrusivePriorityHeap.Node { - final AccordCommandStore commandStore; long queuePosition; - protected Task(AccordCommandStore commandStore) + protected Task() { - this.commandStore = commandStore; } + public DebuggableTask debuggable() { return null; } + /** * Prepare to run while holding the state cache lock */ - abstract protected void preRunExclusive(); + abstract protected void preRunExclusive(@Nullable TaskRunner runner); /** * Run the command; the state cache lock may or may not be held depending on the executor implementation @@ -709,66 +756,48 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo /** * Cleanup the command while holding the state cache lock */ - abstract protected void cleanupExclusive(); + abstract protected void cleanupExclusive(@Nullable TaskRunner runner); abstract protected void addToQueue(TaskQueue queue); } - static class CommandStoreQueueTask extends Task + static abstract class SubmittableTask extends Task { - private final CommandStoreQueue queue; - private Task task; + abstract void submitExclusive(AccordExecutor owner); + } - CommandStoreQueueTask(CommandStoreQueue queue, AccordCommandStore commandStore) + static class SequentialQueueTask extends Task + { + private final SequentialExecutor queue; + + SequentialQueueTask(SequentialExecutor queue) { - super(commandStore); + super(); this.queue = queue; } - public boolean isSet() - { - return this.task != null; - } - - public void reset() - { - queuePosition = -1; - this.task = null; - } - - public void setNext(Task task) - { - queuePosition = task.queuePosition; - this.task = task; - } - @Override - protected void preRunExclusive() + protected void preRunExclusive(@Nullable TaskRunner runner) { - Invariants.require(task != null); - Thread self = Thread.currentThread(); - commandStore.setOwner(self, self); - task.preRunExclusive(); + queue.preRunTask(runner); } @Override protected void run() { - task.run(); + queue.runTask(); } @Override protected void fail(Throwable t) { - task.fail(t); + queue.failTask(t); } @Override - protected void cleanupExclusive() + protected void cleanupExclusive(@Nullable TaskRunner runner) { - task.cleanupExclusive(); - commandStore.setOwner(null, Thread.currentThread()); - queue.updateNext(); + queue.cleanupTask(runner); } @Override @@ -778,75 +807,157 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo } } - class CommandStoreQueue extends TaskQueue + private static final AtomicReferenceFieldUpdater ownerUpdater = AtomicReferenceFieldUpdater.newUpdater(SequentialExecutor.class, Thread.class, "owner"); + public class SequentialExecutor extends TaskQueue implements SequentialAsyncExecutor { - final CommandStoreQueueTask next; + final SequentialQueueTask selfTask; + private Task task; + private volatile Thread owner, waiting; - CommandStoreQueue(AccordCommandStore commandStore) + SequentialExecutor() { super(WAITING_TO_RUN); - this.next = new CommandStoreQueueTask(this, commandStore); + this.selfTask = new SequentialQueueTask(this); } - void updateNext() + void preRunTask(@Nullable TaskRunner runner) { - updateNext(super.poll()); + Invariants.require(task != null); + task.preRunExclusive(runner); } - void updateNext(Task task) + void runTask() { - next.reset(); - if (task != null) - task.addToQueue(this); - } - - public void appendOrSetNext(Task task) - { - if (!next.isSet()) - task.addToQueue(this); - else - super.append(task); - } - - @Override - public void append(Task task) - { - Invariants.require(!next.isSet()); - // TODO (expected): if the new task is higher priority, replace next - next.setNext(task); - waitingToRun.append(next); - } - - @Override - public void remove(Task remove) - { - if (next.isSet() && next.task == remove) + Thread self = Thread.currentThread(); + if (!ownerUpdater.compareAndSet(this, null, self)) { - next.reset(); - waitingToRun.remove(next); - return; + waiting = self; + while (owner != null) + LockSupport.park(); + waiting = null; } + task.run(); + } - super.remove(remove); + void failTask(Throwable t) + { + task.fail(t); + } + + void cleanupTask(@Nullable TaskRunner runner) + { + task.cleanupExclusive(runner); + owner = null; + task = super.poll(); + if (task != null) + { + selfTask.queuePosition = task.queuePosition; + waitingToRun.append(selfTask); + } + } + + // invoked by removeAndUpdateNext; expect to already be next + @Override + protected void append(Task newTask) + { + if (task != null) + { + super.append(newTask); + } + else + { + task = newTask; + selfTask.queuePosition = newTask.queuePosition; + waitingToRun.append(selfTask); + } } @Override - public Task poll() + protected void remove(Task remove) + { + if (remove != task) + { + super.remove(remove); + } + else + { + Invariants.require(waitingToRun.contains(selfTask)); + task = super.poll(); + if (task == null) waitingToRun.remove(selfTask); + else + { + selfTask.queuePosition = task.queuePosition; + waitingToRun.update(task); + } + } + } + + public boolean inExecutor() + { + return owner == Thread.currentThread(); + } + + @Override + protected Task poll() { throw new UnsupportedOperationException(); } @Override - public Task peek() + protected Task peek() { throw new UnsupportedOperationException(); } @Override - public boolean contains(Task contains) + protected boolean contains(Task contains) { throw new UnsupportedOperationException(); } + + @Override + public AsyncChain build(Callable task) + { + return new AsyncChains.Head<>() + { + @Override + protected Cancellable start(BiConsumer callback) + { + return submit(new PlainChain<>(task, callback, SequentialExecutor.this)); + } + }; + } + + @Override + public void execute(Runnable run) + { + submit(new PlainRunnable(null, run, this)); + } + + @Override + public void maybeExecuteImmediately(Runnable run) + { + Thread self = Thread.currentThread(); + Thread owner = this.owner; + if (owner == self || (owner == null && ownerUpdater.compareAndSet(this, null, self))) + { + try { run.run(); } + catch (Throwable t) { agent.onUncaughtException(t); } + finally + { + if (owner == null) + { + this.owner = null; + if (waiting != null) + LockSupport.unpark(waiting); + } + } + } + else + { + execute(run); + } + } } static class TaskQueue extends IntrusivePriorityHeap @@ -863,31 +974,35 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo { return Long.compare(o1.queuePosition, o2.queuePosition); } - public void append(T task) + + protected void append(T task) { super.append(task); } - public T poll() + protected void update(T task) + { + super.update(task); + } + + protected T poll() { ensureHeapified(); return pollNode(); } - public T peek() + protected T peek() { ensureHeapified(); return peekNode(); } - public void remove(T remove) + protected void remove(T remove) { - Invariants.require(super.contains(remove)); super.remove(remove); - Invariants.require(!super.contains(remove)); } - public boolean contains(T contains) + protected boolean contains(T contains) { return super.contains(contains); } @@ -895,27 +1010,7 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo private abstract static class SubmitAsync { - abstract void acceptExclusive(AccordExecutor executor); - } - - private static class SubmitPlainRunnable extends SubmitAsync - { - final AsyncPromise result; - final Runnable run; - final AccordCommandStore commandStore; - - private SubmitPlainRunnable(AsyncPromise result, Runnable run, AccordCommandStore commandStore) - { - this.result = result; - this.run = run; - this.commandStore = commandStore; - } - - @Override - void acceptExclusive(AccordExecutor executor) - { - executor.submitExclusive(result, run, commandStore); - } + abstract void submitExclusive(AccordExecutor executor); } private static class OnLoaded extends SubmitAsync @@ -958,7 +1053,7 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo } @Override - void acceptExclusive(AccordExecutor executor) + void submitExclusive(AccordExecutor executor) { executor.onLoadedExclusive(loaded, success(), fail(), isForRange()); } @@ -976,7 +1071,7 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo } @Override - void acceptExclusive(AccordExecutor executor) + void submitExclusive(AccordExecutor executor) { executor.onScannedRangesExclusive(scanned, fail); } @@ -996,7 +1091,7 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo } @Override - void acceptExclusive(AccordExecutor executor) + void submitExclusive(AccordExecutor executor) { executor.onSavedExclusive(state, identity, fail); } @@ -1012,7 +1107,7 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo } @Override - void acceptExclusive(AccordExecutor executor) + void submitExclusive(AccordExecutor executor) { executor.cancelExclusive(cancel); } @@ -1053,20 +1148,56 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo return r -> f.apply(null, r); } - class PlainRunnable extends Task implements Cancellable + abstract class Plain extends SubmittableTask implements Cancellable { - final AsyncPromise result; - final Runnable run; + abstract SequentialExecutor executor(); - PlainRunnable(AsyncPromise result, Runnable run, AccordCommandStore commandStore) + @Override + protected void preRunExclusive(@Nullable TaskRunner runner) {} + + @Override + protected void cleanupExclusive(@Nullable TaskRunner runner) {} + + @Override + protected final void addToQueue(TaskQueue queue) { - super(commandStore); - this.result = result; - this.run = run; + Invariants.require(queue.kind == WAITING_TO_RUN); + queue.append(this); } @Override - protected void preRunExclusive() {} + public final void cancel() + { + executeDirectlyWithLock(() -> { + SequentialExecutor executor = executor(); + TaskQueue queue = executor == null ? waitingToRun : executor; + if (queue.contains(this)) + { + queue.remove(this); + fail(new CancellationException()); + } + }); + } + + @Override + final void submitExclusive(AccordExecutor owner) + { + owner.submitExclusive(this); + } + } + + class PlainRunnable extends Plain implements Cancellable + { + final @Nullable AsyncPromise result; + final Runnable run; + final @Nullable SequentialExecutor executor; + + PlainRunnable(AsyncPromise result, Runnable run, @Nullable SequentialExecutor executor) + { + this.result = result; + this.run = run; + this.executor = executor; + } @Override protected void run() @@ -1085,27 +1216,185 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo } @Override - protected void cleanupExclusive() {} - - @Override - protected void addToQueue(TaskQueue queue) + SequentialExecutor executor() { - Invariants.require(queue.kind == WAITING_TO_RUN); - queue.append(this); - } - - @Override - public void cancel() - { - executeDirectlyWithLock(() -> { - if (isInHeap()) - { - waitingToRun.remove(this); - if (result != null) - result.cancel(false); - } - }); + return executor; } } + class IORunnable extends Plain implements Cancellable, DebuggableTask + { + // expected to implement toString + final Runnable run; + final long createdAtNanos = MonotonicClock.Global.approxTime.now(); + long startedAtNanos; + + IORunnable(Runnable run) + { + this.run = run; + } + + @Override + protected void run() + { + run.run(); + } + + @Override + protected void preRunExclusive(@Nullable TaskRunner runner) + { + if (runner != null) + { + startedAtNanos = MonotonicClock.Global.approxTime.now(); + runner.setDebuggable(this); + } + } + + @Override + protected void cleanupExclusive(@Nullable TaskRunner runner) + { + if (runner != null) + runner.clearDebuggable(); + } + + @Override + protected void fail(Throwable t) + { + agent.onUncaughtException(t); + } + + @Override + SequentialExecutor executor() + { + return null; + } + + @Override + public long creationTimeNanos() + { + return createdAtNanos; + } + + @Override + public long startTimeNanos() + { + return startedAtNanos; + } + + @Override + public String description() + { + return run.toString(); + } + } + + class PlainChain extends Plain + { + final Callable call; + final BiConsumer callback; + final @Nullable SequentialExecutor executor; + + PlainChain(Callable call, BiConsumer callback, @Nullable SequentialExecutor executor) + { + this.call = call; + this.callback = callback; + this.executor = executor; + } + + @Override + SequentialExecutor executor() + { + return executor; + } + + @Override + protected void run() + { + T success; + try + { + success = call.call(); + } + catch (Throwable t) + { + fail(t); + return; + } + try + { + callback.accept(success, null); + } + catch (Throwable t) + { + agent.onUncaughtException(t); + } + } + + @Override + protected void fail(Throwable fail) + { + try + { + callback.accept(null, fail); + } + catch (Throwable t) + { + fail.addSuppressed(t); + agent.onUncaughtException(fail); + } + } + } + + class DebuggableChain extends PlainChain implements DebuggableTask + { + final long createdAtNanos; + long startedAtNanos; + final Object describe; + + DebuggableChain(Callable call, BiConsumer callback, @Nullable SequentialExecutor executor, Object describe) + { + super(call, callback, executor); + this.createdAtNanos = MonotonicClock.Global.approxTime.now(); + this.describe = Invariants.nonNull(describe); + } + + @Override + public long creationTimeNanos() + { + return createdAtNanos; + } + + @Override + public long startTimeNanos() + { + return startedAtNanos; + } + + @Override + protected void preRunExclusive(@Nullable TaskRunner runner) + { + startedAtNanos = MonotonicClock.Global.approxTime.now(); + if (runner != null) + runner.setDebuggable(this); + } + + @Override + protected void cleanupExclusive(@Nullable TaskRunner runner) + { + if (runner != null) + runner.clearDebuggable(); + } + + @Override + public String description() + { + return describe.toString(); + } + + @Override + public DebuggableTask debuggable() + { + return this; + } + } } diff --git a/src/java/org/apache/cassandra/service/accord/AccordExecutorAbstractLockLoop.java b/src/java/org/apache/cassandra/service/accord/AccordExecutorAbstractLockLoop.java index 8ef72c95b0..f442473a46 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordExecutorAbstractLockLoop.java +++ b/src/java/org/apache/cassandra/service/accord/AccordExecutorAbstractLockLoop.java @@ -19,11 +19,14 @@ package org.apache.cassandra.service.accord; import java.util.concurrent.locks.Lock; +import java.util.stream.Stream; import accord.api.Agent; import accord.utils.QuadFunction; import accord.utils.QuintConsumer; +import org.apache.cassandra.concurrent.DebuggableTask.DebuggableTaskRunner; import org.apache.cassandra.metrics.AccordCacheMetrics; +import org.apache.cassandra.service.accord.AccordExecutorLoops.LoopTask; import org.apache.cassandra.utils.concurrent.ConcurrentLinkedStack; import static org.apache.cassandra.service.accord.AccordExecutor.Mode.RUN_WITH_LOCK; @@ -42,12 +45,14 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutor abstract void notifyWork(); abstract void notifyWorkExclusive(); abstract void awaitExclusive() throws InterruptedException; + abstract AccordExecutorLoops loops(); abstract boolean isInLoop(); abstract void submitExternal(QuintConsumer sync, QuadFunction async, P1s p1s, P1a p1a, P2 p2, P3 p3, P4 p4); void submit(QuintConsumer sync, QuadFunction async, P1s p1s, P1a p1a, P2 p2, P3 p3, P4 p4) { // if we're a loop thread, we will poll the waitingToRun queue when we come around + // NOTE: this assumes no synchronous blocking tasks are submitted to this executor if (isInLoop()) submitted.push(async.apply(p1a, p2, p3, p4)); else submitExternal(sync, async, p1s, p1a, p2, p3, p4); } @@ -129,150 +134,171 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutor ++running; } - Runnable task(Mode mode) + LoopTask task(String name, Mode mode) { - return mode == RUN_WITH_LOCK ? this::runWithLock : this::runWithoutLock; + return mode == RUN_WITH_LOCK ? runWithLock(name) : runWithoutLock(name); } - protected void runWithLock() + protected LoopTask runWithLock(String name) { - while (true) + return new LoopTask(name) { - lock.lock(); - try + @Override + public void run() { - resumeExclusive(); - enterLockExclusive(); + Task task; while (true) { - Task task = pollWaitingToRunExclusive(); - - if (task != null) - { - --tasks; - try - { - task.preRunExclusive(); - task.run(); - } - catch (Throwable t) - { - task.fail(t); - } - finally - { - task.cleanupExclusive(); - } - } - else - { - if (shutdown) - { - pauseExclusive(); - exitLockExclusive(); - notifyWorkExclusive(); // always notify on shutdown - return; - } - - pauseExclusive(); - awaitExclusive(); - resumeExclusive(); - } - } - } - catch (Throwable t) - { - pauseExclusive(); - exitLockExclusive(); - - try { agent.onUncaughtException(t); } - catch (Throwable t2) { } - } - finally - { - lock.unlock(); - } - } - } - - protected void runWithoutLock() - { - Task task = null; - while (true) - { - lock.lock(); - try - { - if (task != null) task.cleanupExclusive(); - else resumeExclusive(); - enterLockExclusive(); - - while (true) - { - task = pollWaitingToRunExclusive(); - if (task != null) - { - exitLockExclusive(); - break; - } - - if (shutdown) - { - exitLockExclusive(); - notifyWorkExclusive(); - return; - } - - pauseExclusive(); - awaitExclusive(); - resumeExclusive(); - } - --tasks; - task.preRunExclusive(); - } - catch (Throwable t) - { - if (task != null) - { - try { task.fail(t); } - catch (Throwable t2) { t.addSuppressed(t2); } - try { task.cleanupExclusive(); } - catch (Throwable t2) { t.addSuppressed(t2); } - try { agent.onUncaughtException(t); } - catch (Throwable t2) { /* nothing we can sensibly do after already reporting */ } - task = null; - } - if (isHeldByExecutor) - pauseExclusive(); - exitLockExclusive(); - continue; - } - finally - { - lock.unlock(); - } - - try - { - task.run(); - } - catch (Throwable t) - { - try { task.fail(t); } - catch (Throwable t2) - { + lock.lock(); try { - t2.addSuppressed(t); - agent.onUncaughtException(t2); + resumeExclusive(); + enterLockExclusive(); + while (true) + { + task = pollWaitingToRunExclusive(); + + if (task != null) + { + --tasks; + try + { + task.preRunExclusive(this); + task.run(); + } + catch (Throwable t) + { + task.fail(t); + } + finally + { + task.cleanupExclusive(this); + } + } + else + { + if (shutdown) + { + pauseExclusive(); + exitLockExclusive(); + notifyWorkExclusive(); // always notify on shutdown + return; + } + + pauseExclusive(); + awaitExclusive(); + resumeExclusive(); + } + } } - catch (Throwable t3) + catch (Throwable t) { - // empty to ensure we definitely loop so we cleanup the task + pauseExclusive(); + exitLockExclusive(); + + try { agent.onUncaughtException(t); } + catch (Throwable t2) { } + } + finally + { + lock.unlock(); } } } - } + }; + } + + protected LoopTask runWithoutLock(String name) + { + return new LoopTask(name) + { + @Override + public void run() + { + Task task = null; + while (true) + { + lock.lock(); + try + { + if (task != null) task.cleanupExclusive(this); + else resumeExclusive(); + enterLockExclusive(); + + while (true) + { + task = pollWaitingToRunExclusive(); + if (task != null) + { + exitLockExclusive(); + break; + } + + if (shutdown) + { + exitLockExclusive(); + notifyWorkExclusive(); + return; + } + + pauseExclusive(); + awaitExclusive(); + resumeExclusive(); + } + --tasks; + task.preRunExclusive(this); + } + catch (Throwable t) + { + if (task != null) + { + try { task.fail(t); } + catch (Throwable t2) { t.addSuppressed(t2); } + try { task.cleanupExclusive(this); } + catch (Throwable t2) { t.addSuppressed(t2); } + try { agent.onUncaughtException(t); } + catch (Throwable t2) { /* nothing we can sensibly do after already reporting */ } + task = null; + } + if (isHeldByExecutor) + pauseExclusive(); + exitLockExclusive(); + continue; + } + finally + { + lock.unlock(); + } + + try + { + task.run(); + } + catch (Throwable t) + { + try { task.fail(t); } + catch (Throwable t2) + { + try + { + t2.addSuppressed(t); + agent.onUncaughtException(t2); + } + catch (Throwable t3) + { + // empty to ensure we definitely loop so we cleanup the task + } + } + } + } + } + }; + } + + @Override + public Stream active() + { + return loops().active(); } @Override diff --git a/src/java/org/apache/cassandra/service/accord/AccordExecutorAsyncSubmit.java b/src/java/org/apache/cassandra/service/accord/AccordExecutorAsyncSubmit.java index 7510e6159a..99086259a9 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordExecutorAsyncSubmit.java +++ b/src/java/org/apache/cassandra/service/accord/AccordExecutorAsyncSubmit.java @@ -51,6 +51,12 @@ class AccordExecutorAsyncSubmit extends AccordExecutorAbstractSemiSyncSubmit lock.await(); } + @Override + AccordExecutorLoops loops() + { + return loops; + } + @Override boolean isInLoop() { diff --git a/src/java/org/apache/cassandra/service/accord/AccordExecutorLoops.java b/src/java/org/apache/cassandra/service/accord/AccordExecutorLoops.java index c5442c644d..803839de94 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordExecutorLoops.java +++ b/src/java/org/apache/cassandra/service/accord/AccordExecutorLoops.java @@ -20,12 +20,15 @@ package org.apache.cassandra.service.accord; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.function.IntFunction; +import java.util.stream.Stream; import accord.utils.Invariants; import org.agrona.collections.Long2ObjectHashMap; +import org.apache.cassandra.concurrent.DebuggableTask.DebuggableTaskRunner; import org.apache.cassandra.service.accord.AccordExecutor.Mode; +import org.apache.cassandra.service.accord.AccordExecutor.TaskRunner; import org.apache.cassandra.utils.concurrent.Condition; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; @@ -36,21 +39,33 @@ import static org.apache.cassandra.utils.Clock.Global.nanoTime; class AccordExecutorLoops { + static abstract class LoopTask extends TaskRunner implements Runnable + { + final String id; + LoopTask(String id) { this.id = id; } + @Override public String id() { return id; } + } + private final Long2ObjectHashMap loops; + private final Long2ObjectHashMap tasks; private final AtomicInteger running = new AtomicInteger(); private final Condition terminated = Condition.newOneTimeCondition(); - public AccordExecutorLoops(Mode mode, int threads, IntFunction name, Function loopFactory) + public AccordExecutorLoops(Mode mode, int threads, IntFunction loopName, BiFunction loopFactory) { Invariants.require(mode == RUN_WITH_LOCK ? threads == 1 : threads >= 1); running.addAndGet(threads); loops = new Long2ObjectHashMap<>(threads, 0.65f); + tasks = new Long2ObjectHashMap<>(threads, 0.65f); for (int i = 0; i < threads; ++i) { - Thread thread = executorFactory().startThread(name.apply(i), wrap(loopFactory.apply(mode)), NON_DAEMON, INFINITE_LOOP); + String name = loopName.apply(i); + LoopTask task = loopFactory.apply(name, mode); + Thread thread = executorFactory().startThread(name, wrap(task), NON_DAEMON, INFINITE_LOOP); Thread conflict = loops.putIfAbsent(thread.getId(), thread); Invariants.require(conflict == null || !conflict.isAlive(), "Allocated two threads with the same threadId!"); + tasks.put(thread.getId(), task); } } @@ -70,6 +85,11 @@ class AccordExecutorLoops }; } + public Stream active() + { + return tasks.values().stream(); + } + public boolean isInLoop() { Thread thread = Thread.currentThread(); diff --git a/src/java/org/apache/cassandra/service/accord/AccordExecutorSemiSyncSubmit.java b/src/java/org/apache/cassandra/service/accord/AccordExecutorSemiSyncSubmit.java index 0e26e38537..bd4b494646 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordExecutorSemiSyncSubmit.java +++ b/src/java/org/apache/cassandra/service/accord/AccordExecutorSemiSyncSubmit.java @@ -53,6 +53,12 @@ class AccordExecutorSemiSyncSubmit extends AccordExecutorAbstractSemiSyncSubmit hasWork.await(); } + @Override + AccordExecutorLoops loops() + { + return loops; + } + @Override boolean isInLoop() { diff --git a/src/java/org/apache/cassandra/service/accord/AccordExecutorSimple.java b/src/java/org/apache/cassandra/service/accord/AccordExecutorSimple.java index cd192e43c5..ac0069fdc9 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordExecutorSimple.java +++ b/src/java/org/apache/cassandra/service/accord/AccordExecutorSimple.java @@ -92,9 +92,9 @@ class AccordExecutorSimple extends AccordExecutor return; --tasks; - try { task.preRunExclusive(); task.run(); } + try { task.preRunExclusive(null); task.run(); } catch (Throwable t) { task.fail(t); } - finally { task.cleanupExclusive(); } + finally { task.cleanupExclusive(null); } } } catch (Throwable t) diff --git a/src/java/org/apache/cassandra/service/accord/AccordExecutorSyncSubmit.java b/src/java/org/apache/cassandra/service/accord/AccordExecutorSyncSubmit.java index 5fb5d7895b..3bb07b5f2e 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordExecutorSyncSubmit.java +++ b/src/java/org/apache/cassandra/service/accord/AccordExecutorSyncSubmit.java @@ -64,6 +64,12 @@ class AccordExecutorSyncSubmit extends AccordExecutorAbstractLockLoop hasWork.await(); } + @Override + AccordExecutorLoops loops() + { + return loops; + } + @Override boolean isInLoop() { diff --git a/src/java/org/apache/cassandra/service/accord/AccordFetchCoordinator.java b/src/java/org/apache/cassandra/service/accord/AccordFetchCoordinator.java index 1ceedc4612..0d26c56b56 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordFetchCoordinator.java +++ b/src/java/org/apache/cassandra/service/accord/AccordFetchCoordinator.java @@ -164,6 +164,26 @@ public class AccordFetchCoordinator extends AbstractFetchCoordinator implements builder.putAll(that.streams); return new StreamData(builder.build()); } + + @Override + public Data without(Ranges ranges) + { + ImmutableMap.Builder copy = ImmutableMap.builder(); + for (Map.Entry e : streams.entrySet()) + { + if (!ranges.intersects(e.getKey())) + { + copy.put(e.getKey(), e.getValue()); + } + else + { + Ranges remainder = Ranges.of(e.getKey()).without(ranges); + for (Range range : remainder) + copy.put((TokenRange) range, e.getValue()); + } + } + return new StreamData(copy.build()); + } } // needs to be externally synchronized @@ -265,7 +285,7 @@ public class AccordFetchCoordinator extends AbstractFetchCoordinator implements } @Override - public AsyncChain read(Seekable key, SafeCommandStore commandStore, Timestamp executeAt, DataStore store) + public AsyncChain read(SafeCommandStore commandStore, Seekable key, Timestamp executeAt) { try { @@ -359,7 +379,7 @@ public class AccordFetchCoordinator extends AbstractFetchCoordinator implements public AccordFetchCoordinator(Node node, Ranges ranges, SyncPoint syncPoint, DataStore.FetchRanges fetchRanges, CommandStore commandStore) { - super(node, ranges, syncPoint, fetchRanges, commandStore); + super(node, node.someSequentialExecutor(), ranges, syncPoint, fetchRanges, commandStore); } @Override diff --git a/src/java/org/apache/cassandra/service/accord/AccordMessageSink.java b/src/java/org/apache/cassandra/service/accord/AccordMessageSink.java index cfa9db8101..19e37f06c3 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordMessageSink.java +++ b/src/java/org/apache/cassandra/service/accord/AccordMessageSink.java @@ -28,9 +28,9 @@ import com.google.common.collect.ImmutableMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import accord.api.AsyncExecutor; import accord.api.MessageSink; import accord.impl.RequestCallbacks; -import accord.local.AgentExecutor; import accord.local.Node; import accord.messages.Callback; import accord.messages.MessageType; @@ -180,7 +180,7 @@ public class AccordMessageSink implements MessageSink // TODO (expected): permit bulk send to save esp. on callback registration (and combine records) @Override - public void send(Node.Id to, Request request, int attempt, AgentExecutor executor, Callback callback) + public void send(Node.Id to, Request request, int attempt, AsyncExecutor executor, Callback callback) { Verb verb = VerbMapping.getVerb(request); Preconditions.checkNotNull(verb, "Verb is null for type %s", request.type()); diff --git a/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java b/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java index c3b55718c5..02185431c6 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java +++ b/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java @@ -58,6 +58,7 @@ import accord.primitives.TxnId; import accord.primitives.Unseekables; import accord.primitives.Writes; import accord.utils.ImmutableBitSet; +import accord.utils.UnhandledEnum; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.dht.Murmur3Partitioner; import org.apache.cassandra.schema.TableId; @@ -353,6 +354,7 @@ public class AccordObjectSizes case AcceptedMedium: case AcceptedMediumWithDefinition: case AcceptedSlow: + case AcceptedSlowWithDefAndVote: case AcceptedSlowWithDefinition: case PreCommitted: case PreCommittedWithDeps: @@ -378,7 +380,7 @@ public class AccordObjectSizes case Invalidated: return INVALIDATED; default: - throw new IllegalStateException("Unhandled status " + command.status()); + throw new UnhandledEnum(command.saveStatus()); } } } diff --git a/src/java/org/apache/cassandra/service/accord/AccordService.java b/src/java/org/apache/cassandra/service/accord/AccordService.java index e7b93be7a6..be74ecc321 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordService.java +++ b/src/java/org/apache/cassandra/service/accord/AccordService.java @@ -42,6 +42,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import accord.api.Journal; +import accord.api.ProtocolModifiers; import accord.coordinate.CoordinateMaxConflict; import accord.coordinate.CoordinateTransaction; import accord.coordinate.KeyBarriers; @@ -131,12 +132,11 @@ import org.apache.cassandra.utils.concurrent.AsyncPromise; import org.apache.cassandra.utils.concurrent.Future; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; +import static accord.api.ProtocolModifiers.Toggles.FastExec.MAY_BYPASS_SAFESTORE; import static accord.local.durability.DurabilityService.SyncLocal.Self; import static accord.local.durability.DurabilityService.SyncRemote.All; import static accord.messages.SimpleReply.Ok; -import static accord.primitives.Routable.Domain.Key; import static accord.primitives.Txn.Kind.Write; -import static accord.primitives.TxnId.Cardinality.cardinality; import static accord.topology.TopologyManager.TopologyRange; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -154,8 +154,17 @@ import static org.apache.cassandra.utils.Clock.Global.nanoTime; public class AccordService implements IAccordService, Shutdownable { private static final Logger logger = LoggerFactory.getLogger(AccordService.class); + static + { + ProtocolModifiers.Toggles.setPermitLocalExecution(true); + ProtocolModifiers.Toggles.setRequiresUniqueHlcs(true); + ProtocolModifiers.Toggles.setFastReadExecMayResendTxn(true); + ProtocolModifiers.Toggles.setFastReadExec(MAY_BYPASS_SAFESTORE); + ProtocolModifiers.Toggles.setFastWriteExec(MAY_BYPASS_SAFESTORE); + ProtocolModifiers.Toggles.setDataStoreDetectsFutureReads(true); + } - private enum State {INIT, STARTED, SHUTTING_DOWN, SHUTDOWN} + private enum State { INIT, STARTED, SHUTTING_DOWN, SHUTDOWN } private final Node node; private final Shutdownable nodeShutdown; @@ -320,7 +329,7 @@ public class AccordService implements IAccordService, Shutdownable DefaultProgressLogs::new, DefaultLocalListeners.Factory::new, AccordCommandStores.factory(), - new AccordInteropFactory(agent, configService), + new AccordInteropFactory(configService), journal.durableBeforePersister(), journal); this.nodeShutdown = toShutdownable(node); @@ -515,7 +524,7 @@ public class AccordService implements IAccordService, Shutdownable return syncInternal(minBound, keys, syncLocal, syncRemote); return KeyBarriers.find(node, minBound, keys.get(0).toUnseekable(), syncLocal, syncRemote) - .flatMap(found -> KeyBarriers.await(node, found, syncLocal, syncRemote)) + .flatMap(found -> KeyBarriers.await(node, node.someSequentialExecutor(), found, syncLocal, syncRemote)) .flatMap(success -> { if (success) return null; @@ -525,7 +534,7 @@ public class AccordService implements IAccordService, Shutdownable private AsyncChain syncInternal(Timestamp minBound, Keys keys, DurabilityService.SyncLocal syncLocal, DurabilityService.SyncRemote syncRemote) { - TxnId txnId = node.nextTxnId(minBound, Write, Key, cardinality(keys)); + TxnId txnId = node.nextTxnId(minBound, keys, Write); FullRoute route = node.computeRoute(txnId, keys); Txn txn = new Txn.InMemory(Write, keys, TxnRead.createNoOpRead(keys), TxnQuery.NONE, TxnUpdate.empty(), new TableMetadatasAndKeys(TableMetadatas.none(), keys)); return CoordinateTransaction.coordinate(node, route, txnId, txn) @@ -535,7 +544,7 @@ public class AccordService implements IAccordService, Shutdownable @Override public AsyncChain maxConflict(Ranges ranges) { - return node.commandStores().any().build(() -> CoordinateMaxConflict.maxConflict(node, ranges)).flatMap(i -> i); + return CoordinateMaxConflict.maxConflict(node, ranges); } public static V getBlocking(AsyncChain async, Seekables keysOrRanges, RequestBookkeeping bookkeeping, long startedAt, long deadline, boolean isTxnRequest) @@ -631,10 +640,16 @@ public class AccordService implements IAccordService, Shutdownable return coordinateAsync(minEpoch, txn, consistencyLevel, requestTime).awaitAndGet(); } + @Override + public List executors() + { + return ((AccordCommandStores)node.commandStores()).executors(); + } + @Override public @Nonnull IAccordResult coordinateAsync(long minEpoch, @Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, @Nonnull Dispatcher.RequestTime requestTime) { - TxnId txnId = node.nextTxnId(txn.kind(), txn.keys().domain(), cardinality(txn.keys())); + TxnId txnId = node.nextTxnId(txn); long timeout = txnId.isWrite() ? DatabaseDescriptor.getWriteRpcTimeout(NANOSECONDS) : DatabaseDescriptor.getReadRpcTimeout(NANOSECONDS); ClientRequestBookkeeping bookkeeping = txn.isWrite() ? accordWriteBookkeeping : accordReadBookkeeping; bookkeeping.metrics.keySize.update(txn.keys().size()); diff --git a/src/java/org/apache/cassandra/service/accord/AccordTask.java b/src/java/org/apache/cassandra/service/accord/AccordTask.java index 9930d2093e..8a7c4986a1 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordTask.java +++ b/src/java/org/apache/cassandra/service/accord/AccordTask.java @@ -27,15 +27,16 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.MDC; import accord.api.Journal; import accord.api.RoutingKey; @@ -56,8 +57,10 @@ import accord.utils.async.AsyncChains; import accord.utils.async.Cancellable; import org.agrona.collections.Object2ObjectHashMap; import org.agrona.collections.ObjectHashSet; +import org.apache.cassandra.concurrent.DebuggableTask; import org.apache.cassandra.service.accord.AccordCacheEntry.Status; import org.apache.cassandra.service.accord.AccordCommandStore.Caches; +import org.apache.cassandra.service.accord.AccordExecutor.SubmittableTask; import org.apache.cassandra.service.accord.AccordExecutor.Task; import org.apache.cassandra.service.accord.AccordExecutor.TaskQueue; import org.apache.cassandra.service.accord.AccordKeyspace.CommandsForKeyAccessor; @@ -85,18 +88,12 @@ import static org.apache.cassandra.service.accord.AccordTask.State.WAITING_TO_RU import static org.apache.cassandra.service.accord.AccordTask.State.WAITING_TO_SCAN_RANGES; import static org.apache.cassandra.utils.Clock.Global.nanoTime; -public abstract class AccordTask extends Task implements Runnable, Function, Cancellable +public abstract class AccordTask extends SubmittableTask implements Runnable, Function, Cancellable, DebuggableTask { private static final Logger logger = LoggerFactory.getLogger(AccordTask.class); private static final NoSpamLogger noSpamLogger = NoSpamLogger.getLogger(logger, 1, TimeUnit.MINUTES); private static final boolean SANITY_CHECK = DTEST_ACCORD_JOURNAL_SANITY_CHECK_ENABLED.getBoolean(); - private static class LoggingProps - { - private static final String COMMAND_STORE = "command_store"; - private static final String ACCORD_TASK = "accord_task"; - } - static class ForFunction extends AccordTask { private final Function function; @@ -190,9 +187,11 @@ public abstract class AccordTask extends Task implements Runnable, Function loggingIdUpdater = AtomicReferenceFieldUpdater.newUpdater(AccordTask.class, String.class, "loggingId"); // TODO (desired): merge all of these maps into one @Nullable Object2ObjectHashMap commands; @@ -207,43 +206,40 @@ public abstract class AccordTask extends Task implements Runnable, Function callback; private List sanityCheck; - public long createdAt = nanoTime(), loadedAt, runQueuedAt, runAt, completedAt; + public long createdAt = nanoTime(), waitingToRunAt, runningAt, completedAt; - private void setLoggingIds() + public AccordTask(@Nonnull AccordCommandStore commandStore, PreLoadContext preLoadContext) { - MDC.put(LoggingProps.COMMAND_STORE, commandStore.loggingId); - MDC.put(LoggingProps.ACCORD_TASK, loggingId); - } - - private void clearLoggingIds() - { - MDC.remove(LoggingProps.COMMAND_STORE); - MDC.remove(LoggingProps.ACCORD_TASK); - } - - public AccordTask(AccordCommandStore commandStore, PreLoadContext preLoadContext) - { - super(commandStore); - this.loggingId = "0x" + Long.toHexString(nextLoggingId.incrementAndGet()); + this.commandStore = commandStore; this.preLoadContext = preLoadContext; + this.loggingId = "0x" + Long.toHexString(nextLoggingId.incrementAndGet()); if (logger.isTraceEnabled()) - { - setLoggingIds(); logger.trace("Created {} on {}", this, commandStore); - clearLoggingIds(); + } + + private String loggingId() + { + String id = loggingId; + if (id == null) + { + TxnId primaryTxnId = preLoadContext.primaryTxnId(); + id = "0x" + Long.toHexString(nextLoggingId.incrementAndGet()) + (primaryTxnId != null ? '/' + primaryTxnId.toString() : ""); + if (!loggingIdUpdater.compareAndSet(this, null, id)) + id = loggingId; } + return id; } @Override public String toString() { - return "AccordTask{" + state + "}-" + loggingId; + return "AccordTask{" + state + "}-" + loggingId(); } public String toDescription() { - return "AccordTask{" + state + "}-" + loggingId + ": " + return "AccordTask{" + state + "}-" + loggingId() + ": " + (queued == null ? "unqueued" : queued.kind) + ", primaryTxnId: " + preLoadContext.primaryTxnId() + ", waitingToLoad: " + summarise(waitingToLoad) @@ -298,11 +294,11 @@ public abstract class AccordTask extends Task implements Runnable, Function no loading or waiting; found %s", this, AccordTask::toDescription); - loadedAt = nanoTime(); + waitingToRunAt = nanoTime(); } else if (state == RUNNING) { - runAt = nanoTime(); + runningAt = nanoTime(); } else if (state.isExecuted()) { @@ -315,6 +311,8 @@ public abstract class AccordTask extends Task implements Runnable, Function chain() { return new AsyncChains.Head<>() @@ -360,6 +358,12 @@ public abstract class AccordTask extends Task implements Runnable, Function extends Task implements Runnable, Function extends Task implements Runnable, Function extends Task implements Runnable, Function extends Task implements Runnable, Function extends Task implements Runnable, Function summaries.put(summary.txnId, summary), caches); caches.commands().register(commandWatcher); } @@ -1113,6 +1120,35 @@ public abstract class AccordTask extends Task implements Runnable, Function implements IVerbHandler } else { - node.withEpochAtLeast(waitForEpoch, (ignored, withEpochFailure) -> { + node.withEpochAtLeast(waitForEpoch, null, (ignored, withEpochFailure) -> { if (withEpochFailure != null) throw new RuntimeException("Timed out waiting for epoch when processing message from " + fromNodeId + " to " + node + " message " + message, withEpochFailure); request.process(node, fromNodeId, message.header); diff --git a/src/java/org/apache/cassandra/service/accord/IAccordService.java b/src/java/org/apache/cassandra/service/accord/IAccordService.java index 2530a080fa..adb23a37af 100644 --- a/src/java/org/apache/cassandra/service/accord/IAccordService.java +++ b/src/java/org/apache/cassandra/service/accord/IAccordService.java @@ -85,6 +85,8 @@ public interface IAccordService @Nonnull IAccordResult coordinateAsync(long minEpoch, @Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, RequestTime requestTime); @Nonnull TxnResult coordinate(long minEpoch, @Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, RequestTime requestTime) throws RequestExecutionException; + List executors(); + interface IAccordResult { V success(); @@ -214,6 +216,12 @@ public interface IAccordService throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml"); } + @Override + public List executors() + { + return List.of(); + } + @Override public @Nonnull IAccordResult coordinateAsync(long minEpoch, @Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, RequestTime requestTime) { @@ -383,6 +391,12 @@ public interface IAccordService return delegate.coordinate(minEpoch, txn, consistencyLevel, requestTime); } + @Override + public List executors() + { + return delegate.executors(); + } + @Nonnull @Override public IAccordResult coordinateAsync(long minEpoch, @Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, RequestTime requestTime) diff --git a/src/java/org/apache/cassandra/service/accord/ImmediateAsyncExecutor.java b/src/java/org/apache/cassandra/service/accord/ImmediateAsyncExecutor.java new file mode 100644 index 0000000000..e015a33f78 --- /dev/null +++ b/src/java/org/apache/cassandra/service/accord/ImmediateAsyncExecutor.java @@ -0,0 +1,68 @@ +/* + * 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 org.apache.cassandra.service.accord; + +import java.util.concurrent.Callable; +import java.util.function.BiConsumer; + +import javax.annotation.Nonnull; + +import accord.api.AsyncExecutor; +import accord.utils.async.AsyncChain; +import accord.utils.async.AsyncChains; +import org.apache.cassandra.service.accord.api.AccordAgent; + +public class ImmediateAsyncExecutor implements AsyncExecutor, BiConsumer +{ + public static final ImmediateAsyncExecutor INSTANCE = new ImmediateAsyncExecutor(); + + @Override + public AsyncChain build(Callable task) + { + try + { + return AsyncChains.success(task.call()); + } + catch (Throwable t) + { + AccordAgent.handleUncaughtException(t); + return AsyncChains.failure(t); + } + } + + @Override + public void execute(@Nonnull Runnable command) + { + try + { + command.run(); + } + catch (Throwable t) + { + AccordAgent.handleUncaughtException(t); + } + } + + @Override + public void accept(Object o, Throwable throwable) + { + if (throwable != null) + AccordAgent.handleUncaughtException(throwable); + } +} diff --git a/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java b/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java index 9f07e351ab..8c754f0d70 100644 --- a/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java +++ b/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java @@ -22,8 +22,6 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; -import javax.annotation.Nullable; - import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +38,6 @@ import accord.local.SafeCommandStore; import accord.local.TimeService; import accord.messages.ReplyContext; import accord.primitives.Keys; -import accord.primitives.Participants; import accord.primitives.Ranges; import accord.primitives.Routable; import accord.primitives.Status; @@ -143,10 +140,14 @@ public class AccordAgent implements Agent @Override public void onUncaughtException(Throwable t) + { + handleUncaughtException(t); + } + + public static void handleUncaughtException(Throwable t) { if (t instanceof RequestTimeoutException || t instanceof CancellationException) return; - logger.error("Uncaught accord exception", t); JVMStabilityInspector.uncaughtException(Thread.currentThread(), t); } @@ -154,7 +155,7 @@ public class AccordAgent implements Agent public void onCaughtException(Throwable t, String context) { logger.warn(context, t); - JVMStabilityInspector.uncaughtException(Thread.currentThread(), t); + JVMStabilityInspector.inspectThrowable(t); } @Override @@ -351,10 +352,4 @@ public class AccordAgent implements Agent { return node.now() - (100 + getAccordScheduleDurabilityTxnIdLag(MICROSECONDS)); } - - @Override - public void onViolation(String message, Participants participants, @Nullable TxnId notWitnessed, @Nullable Timestamp notWitnessedExecuteAt, @Nullable TxnId by, @Nullable Timestamp byEexecuteAt) - { - logger.error(message); - } } diff --git a/src/java/org/apache/cassandra/service/accord/api/AccordViolationHandler.java b/src/java/org/apache/cassandra/service/accord/api/AccordViolationHandler.java new file mode 100644 index 0000000000..44687e00f5 --- /dev/null +++ b/src/java/org/apache/cassandra/service/accord/api/AccordViolationHandler.java @@ -0,0 +1,45 @@ +/* + * 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 org.apache.cassandra.service.accord.api; + +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import accord.api.ViolationHandler; +import accord.primitives.Participants; +import accord.primitives.Timestamp; +import accord.primitives.TxnId; + +public class AccordViolationHandler implements ViolationHandler +{ + private static final Logger logger = LoggerFactory.getLogger(AccordViolationHandler.class); + + static + { + ViolationHandlerHolder.set(AccordViolationHandler::new); + } + + @Override + public void onViolation(String message, Participants participants, @Nullable TxnId notWitnessed, @Nullable Timestamp notWitnessedExecuteAt, @Nullable TxnId by, @Nullable Timestamp byEexecuteAt) + { + logger.error(message); + } +} diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java index 009c592fcd..e33a2072c0 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java @@ -27,9 +27,10 @@ import accord.api.Result; import accord.api.Update; import accord.coordinate.CoordinationAdapter; import accord.coordinate.CoordinationAdapter.Adapters.TxnAdapter; -import accord.coordinate.ExecuteFlag.ExecuteFlags; +import accord.coordinate.ExecuteFlag.CoordinationFlags; import accord.coordinate.ExecutePath; import accord.local.Node; +import accord.local.SequentialAsyncExecutor; import accord.messages.Apply; import accord.primitives.Ballot; import accord.primitives.Deps; @@ -43,8 +44,6 @@ import accord.topology.Topologies; import accord.topology.Topologies.SelectNodeOwnership; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.service.accord.AccordEndpointMapper; -import org.apache.cassandra.service.accord.api.AccordAgent; -import org.apache.cassandra.service.accord.interop.AccordInteropExecution.InteropExecutor; import org.apache.cassandra.service.accord.txn.AccordUpdate; import org.apache.cassandra.service.accord.txn.TxnRead; @@ -58,11 +57,10 @@ public class AccordInteropAdapter extends TxnAdapter { final AccordInteropAdapter standard, recovery; - public AccordInteropFactory(AccordAgent agent, AccordEndpointMapper endpointMapper) + public AccordInteropFactory(AccordEndpointMapper endpointMapper) { - final InteropExecutor executor = new InteropExecutor(agent); - standard = new AccordInteropAdapter(executor, endpointMapper, Minimal); - recovery = new AccordInteropAdapter(executor, endpointMapper, Maximal); + standard = new AccordInteropAdapter(endpointMapper, Minimal); + recovery = new AccordInteropAdapter(endpointMapper, Maximal); } @Override @@ -74,36 +72,33 @@ public class AccordInteropAdapter extends TxnAdapter } }; - private final InteropExecutor executor; private final AccordEndpointMapper endpointMapper; private final Apply.Kind applyKind; - private AccordInteropAdapter(InteropExecutor executor, AccordEndpointMapper endpointMapper, Apply.Kind applyKind) + private AccordInteropAdapter(AccordEndpointMapper endpointMapper, Apply.Kind applyKind) { super(Minimal); - this.executor = executor; this.endpointMapper = endpointMapper; this.applyKind = applyKind; } @Override - public void execute(Node node, Topologies any, FullRoute route, Ballot ballot, ExecutePath path, ExecuteFlags executeFlags, TxnId txnId, Txn txn, Timestamp executeAt, Deps stableDeps, Deps sendDeps, BiConsumer callback) + 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) { - if (!doInteropExecute(node, route, ballot, txnId, txn, executeAt, stableDeps, callback)) - super.execute(node, any, route, ballot, path, executeFlags, txnId, txn, executeAt, stableDeps, sendDeps, callback); + if (!doInteropExecute(node, executor, route, ballot, txnId, txn, executeAt, stableDeps, callback)) + super.execute(node, executor, any, route, ballot, path, flags, txnId, txn, executeAt, stableDeps, sendDeps, callback); } @Override - public void persist(Node node, Topologies any, Route require, Route sendTo, SelectNodeOwnership selectSendTo, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, boolean informDurableOnDone, BiConsumer callback) + public void persist(Node node, SequentialAsyncExecutor executor, Topologies any, Route require, Route sendTo, SelectNodeOwnership selectSendTo, FullRoute route, Ballot ballot, CoordinationFlags flags, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, boolean informDurableOnDone, BiConsumer callback) { - if (applyKind == Minimal && doInteropPersist(node, any, require, sendTo, selectSendTo, ballot, txnId, txn, executeAt, deps, writes, result, route, informDurableOnDone, callback)) + if (applyKind == Minimal && doInteropPersist(node, executor, any, require, sendTo, selectSendTo, ballot, txnId, txn, executeAt, deps, writes, result, route, informDurableOnDone, callback)) return; - super.persist(node, any, require, sendTo, selectSendTo, route, ballot, txnId, txn, executeAt, deps, writes, result, informDurableOnDone, callback); + super.persist(node, executor, any, require, sendTo, selectSendTo, route, ballot, flags, txnId, txn, executeAt, deps, writes, result, informDurableOnDone, callback); } - - private boolean doInteropExecute(Node node, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) + private boolean doInteropExecute(Node node, SequentialAsyncExecutor executor, FullRoute route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer callback) { // Unrecoverable repair always needs to be run by AccordInteropExecution AccordUpdate.Kind updateKind = AccordUpdate.kind(txn.update()); @@ -116,7 +111,7 @@ public class AccordInteropAdapter extends TxnAdapter return true; } - private boolean doInteropPersist(Node node, Topologies any, Route require, Route sendTo, SelectNodeOwnership selectSendTo, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, FullRoute fullRoute, boolean informDurableOnDone, BiConsumer callback) + private boolean doInteropPersist(Node node, SequentialAsyncExecutor executor, Topologies any, Route require, Route sendTo, SelectNodeOwnership selectSendTo, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, FullRoute fullRoute, boolean informDurableOnDone, BiConsumer callback) { Update update = txn.update(); ConsistencyLevel consistencyLevel = update instanceof AccordUpdate ? ((AccordUpdate) update).cassandraCommitCL() : null; @@ -124,7 +119,7 @@ public class AccordInteropAdapter extends TxnAdapter return false; Topologies all = execution(node, any, sendTo, selectSendTo, fullRoute, txnId, executeAt); - new AccordInteropPersist(node, all, txnId, require, ballot, txn, executeAt, deps, writes, result, fullRoute, consistencyLevel, informDurableOnDone, callback) + new AccordInteropPersist(node, executor, all, txnId, require, ballot, txn, executeAt, deps, writes, result, fullRoute, consistencyLevel, CoordinationFlags.none(), informDurableOnDone, callback) .start(Minimal, any, writes, result); return true; } diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropApply.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropApply.java index 3a5671c970..e5f6ba4857 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropApply.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropApply.java @@ -22,6 +22,7 @@ import javax.annotation.Nullable; import accord.api.LocalListeners; import accord.api.Result; +import accord.coordinate.ExecuteFlag.ExecuteFlags; import accord.local.Command; import accord.local.Node.Id; import accord.local.SafeCommand; @@ -66,23 +67,23 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL public static final Apply.Factory FACTORY = new Apply.Factory() { @Override - public Apply create(Kind kind, Id to, Topologies participates, TxnId txnId, Ballot ballot, Route route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, FullRoute fullRoute) + public Apply create(Kind kind, Id to, Topologies participates, TxnId txnId, Ballot ballot, Route route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, FullRoute fullRoute, ExecuteFlags flags) { checkArgument(kind != Kind.Maximal, "Shouldn't need to send a maximal commit with interop support"); ConsistencyLevel commitCL = txn.update() instanceof AccordUpdate ? ((AccordUpdate) txn.update()).cassandraCommitCL() : null; // Any asynchronous apply option should use the regular Apply that doesn't wait for writes to complete if (commitCL == null || commitCL == ConsistencyLevel.ANY) - return Apply.FACTORY.create(kind, to, participates, txnId, ballot, route, txn, executeAt, deps, writes, result, fullRoute); - return new AccordInteropApply(kind, to, participates, txnId, ballot, route, txn, executeAt, deps, writes, result, fullRoute); + return Apply.FACTORY.create(kind, to, participates, txnId, ballot, route, txn, executeAt, deps, writes, result, fullRoute, flags); + return new AccordInteropApply(kind, to, participates, txnId, ballot, route, txn, executeAt, deps, writes, result, fullRoute, flags); } }; public static final IVersionedSerializer serializer = new ApplySerializer() { @Override - protected AccordInteropApply deserializeApply(TxnId txnId, Ballot ballot, Route scope, long minEpoch, long waitForEpoch, long maxEpoch, Apply.Kind kind, Timestamp executeAt, PartialDeps deps, PartialTxn txn, @Nullable FullRoute fullRoute, Writes writes, Result result) + protected AccordInteropApply deserializeApply(TxnId txnId, Ballot ballot, Route scope, long minEpoch, long waitForEpoch, long maxEpoch, Apply.Kind kind, Timestamp executeAt, PartialDeps deps, PartialTxn txn, @Nullable FullRoute fullRoute, Writes writes, Result result, ExecuteFlags flags) { - return new AccordInteropApply(kind, txnId, ballot, scope, minEpoch, waitForEpoch, maxEpoch, executeAt, deps, txn, fullRoute, writes, result); + return new AccordInteropApply(kind, txnId, ballot, scope, minEpoch, waitForEpoch, maxEpoch, executeAt, deps, txn, fullRoute, writes, result, flags); } }; @@ -90,14 +91,14 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL transient Int2ObjectHashMap listeners; boolean failed; - private AccordInteropApply(Kind kind, TxnId txnId, Ballot ballot, Route route, long minEpoch, long waitForEpoch, long maxEpoch, Timestamp executeAt, PartialDeps deps, @Nullable PartialTxn txn, @Nullable FullRoute fullRoute, Writes writes, Result result) + private AccordInteropApply(Kind kind, TxnId txnId, Ballot ballot, Route route, long minEpoch, long waitForEpoch, long maxEpoch, Timestamp executeAt, PartialDeps deps, @Nullable PartialTxn txn, @Nullable FullRoute fullRoute, Writes writes, Result result, ExecuteFlags flags) { - super(kind, txnId, ballot, route, minEpoch, waitForEpoch, maxEpoch, executeAt, deps, txn, fullRoute, writes, result); + super(kind, txnId, ballot, route, minEpoch, waitForEpoch, maxEpoch, executeAt, deps, txn, fullRoute, writes, result, flags); } - private AccordInteropApply(Kind kind, Id to, Topologies participates, TxnId txnId, Ballot ballot, Route route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, FullRoute fullRoute) + private AccordInteropApply(Kind kind, Id to, Topologies participates, TxnId txnId, Ballot ballot, Route route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, FullRoute fullRoute, ExecuteFlags flags) { - super(kind, to, participates, txnId, ballot, route, txn, executeAt, deps, writes, result, fullRoute); + super(kind, to, participates, txnId, ballot, route, txn, executeAt, deps, writes, result, fullRoute, flags); } @Override diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java index ffd42f0c48..39d1a0eb20 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java @@ -22,19 +22,17 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; -import accord.api.Agent; import accord.api.Data; import accord.api.Result; import accord.coordinate.CoordinationAdapter; -import accord.local.AgentExecutor; -import accord.local.CommandStore; +import accord.coordinate.ExecuteFlag.CoordinationFlags; import accord.local.Node; import accord.local.Node.Id; +import accord.local.SequentialAsyncExecutor; import accord.messages.Commit; import accord.messages.Commit.Kind; import accord.primitives.AbstractRanges; @@ -76,7 +74,6 @@ import org.apache.cassandra.schema.TableId; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.accord.AccordEndpointMapper; import org.apache.cassandra.service.accord.TokenRange; -import org.apache.cassandra.service.accord.api.AccordAgent; import org.apache.cassandra.service.accord.api.TokenKey; import org.apache.cassandra.service.accord.interop.AccordInteropReadCallback.MaximalCommitSender; import org.apache.cassandra.service.accord.serializers.TableMetadatasAndKeys; @@ -111,35 +108,6 @@ import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.accordWri */ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSender { - static class InteropExecutor implements AgentExecutor - { - private final AccordAgent agent; - - public InteropExecutor(AccordAgent agent) - { - this.agent = agent; - } - - @Override - public Agent agent() - { - return agent; - } - - @Override - public AsyncChain build(Callable task) - { - try - { - return AsyncChains.success(task.call()); - } - catch (Throwable e) - { - return AsyncChains.failure(e); - } - } - } - private final Node node; private final TxnId txnId; private final Txn txn; @@ -148,7 +116,7 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen private final Timestamp executeAt; private final Deps deps; private final BiConsumer callback; - private final AgentExecutor executor; + private final SequentialAsyncExecutor executor; private final ConsistencyLevel consistencyLevel; private final AccordEndpointMapper endpointMapper; @@ -163,7 +131,7 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen private final AccordUpdate.Kind updateKind; public AccordInteropExecution(Node node, TxnId txnId, Txn txn, AccordUpdate.Kind updateKind, FullRoute route, Ballot ballot, Timestamp executeAt, Deps deps, BiConsumer callback, - AgentExecutor executor, ConsistencyLevel consistencyLevel, AccordEndpointMapper endpointMapper) + SequentialAsyncExecutor executor, ConsistencyLevel consistencyLevel, AccordEndpointMapper endpointMapper) { requireArgument(!txn.read().keys().isEmpty() || updateKind == AccordUpdate.Kind.UNRECOVERABLE_REPAIR); this.node = node; @@ -401,10 +369,9 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen else result = readChains(); - CommandStore cs = node.commandStores().select(route.homeKey()); - result.beginAsResult().withExecutor(cs).begin((data, failure) -> { + result.begin((data, failure) -> { if (failure == null) - ((CoordinationAdapter)node.coordinationAdapter(txnId, Standard)).persist(node, executes, route, ballot, txnId, txn, executeAt, deps, txnId.is(Write) ? txn.execute(txnId, executeAt, data) : null, txn.result(txnId, executeAt, data), callback); + ((CoordinationAdapter)node.coordinationAdapter(txnId, Standard)).persist(node, executor, executes, route, ballot, CoordinationFlags.none(), txnId, txn, executeAt, deps, txnId.is(Write) ? txn.execute(txnId, executeAt, data) : null, txn.result(txnId, executeAt, data), callback); else callback.accept(null, failure); }); diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropPersist.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropPersist.java index bad44822ac..5c494fc386 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropPersist.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropPersist.java @@ -21,12 +21,14 @@ package org.apache.cassandra.service.accord.interop; import java.util.function.BiConsumer; import accord.api.Result; +import accord.coordinate.ExecuteFlag; import accord.coordinate.Persist; import accord.coordinate.tracking.AllTracker; import accord.coordinate.tracking.QuorumTracker; import accord.coordinate.tracking.RequestStatus; import accord.coordinate.tracking.ResponseTracker; import accord.local.Node; +import accord.local.SequentialAsyncExecutor; import accord.messages.Apply; import accord.primitives.Ballot; import accord.primitives.Deps; @@ -109,9 +111,9 @@ public class AccordInteropPersist extends Persist private final ConsistencyLevel consistencyLevel; private CallbackHolder callback; - public AccordInteropPersist(Node node, Topologies topologies, TxnId txnId, Route sendTo, Ballot ballot, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, FullRoute fullRoute, ConsistencyLevel consistencyLevel, boolean informDurableOnDone, BiConsumer clientCallback) + public AccordInteropPersist(Node node, SequentialAsyncExecutor executor, Topologies topologies, TxnId txnId, Route sendTo, Ballot ballot, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, FullRoute fullRoute, ConsistencyLevel consistencyLevel, ExecuteFlag.CoordinationFlags flags, boolean informDurableOnDone, BiConsumer clientCallback) { - super(node, topologies, txnId, ballot, sendTo, txn, executeAt, deps, writes, result, fullRoute, informDurableOnDone, AccordInteropApply.FACTORY); + super(node, executor, topologies, txnId, ballot, sendTo, txn, executeAt, deps, writes, result, fullRoute, flags, informDurableOnDone, AccordInteropApply.FACTORY); Invariants.requireArgument(consistencyLevel == ConsistencyLevel.QUORUM || consistencyLevel == ConsistencyLevel.ALL || consistencyLevel == ConsistencyLevel.SERIAL || consistencyLevel == ConsistencyLevel.ONE); this.consistencyLevel = consistencyLevel; registerClientCallback(result, clientCallback); diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropRead.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropRead.java index c7e5d36af8..5f474cb22b 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropRead.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropRead.java @@ -62,8 +62,7 @@ import org.apache.cassandra.service.accord.api.TokenKey; import org.apache.cassandra.service.accord.serializers.CommandSerializers; import org.apache.cassandra.service.accord.serializers.IVersionedSerializer; import org.apache.cassandra.service.accord.serializers.KeySerializers; -import org.apache.cassandra.service.accord.serializers.ReadDataSerializers; -import org.apache.cassandra.service.accord.serializers.ReadDataSerializers.ReadDataSerializer; +import org.apache.cassandra.service.accord.serializers.ReadDataSerializer; import org.apache.cassandra.service.accord.serializers.Version; import org.apache.cassandra.service.accord.txn.TxnNamedRead; import org.apache.cassandra.service.accord.txn.TxnRead; @@ -77,7 +76,7 @@ import static com.google.common.base.Preconditions.checkState; public class AccordInteropRead extends ReadData { - public static final IVersionedSerializer requestSerializer = new ReadDataSerializer() + public static final IVersionedSerializer requestSerializer = new IVersionedSerializer<>() { @Override public void serialize(AccordInteropRead read, DataOutputPlus out, Version version) throws IOException @@ -108,7 +107,7 @@ public class AccordInteropRead extends ReadData } }; - public static final IVersionedSerializer replySerializer = new ReadDataSerializers.ReplySerializer<>(LocalReadData.serializer); + public static final IVersionedSerializer replySerializer = new ReadDataSerializer.ReplySerializer<>(LocalReadData.serializer); private static class LocalReadData implements Data { @@ -153,6 +152,14 @@ public class AccordInteropRead extends ReadData this.remoteResponse = null; } + private LocalReadData(@Nonnull List> responses, @Nonnull ReadCommand readCommand) + { + checkNotNull(readCommand, "readCommand is null"); + this.localResponses = responses; + this.readCommand = readCommand; + this.remoteResponse = null; + } + public LocalReadData(@Nonnull ReadResponse remoteResponse) { checkNotNull(remoteResponse); @@ -172,9 +179,15 @@ public class AccordInteropRead extends ReadData @Override public Data merge(Data data) { + if (localResponses.isEmpty()) + return data; + + LocalReadData other = (LocalReadData)data; + if (other.localResponses.isEmpty()) + return this; + checkState(remoteResponse == null, "Already serialized"); checkState(readCommand.isRangeRequest(), "Should only ever be a single partition"); - LocalReadData other = (LocalReadData)data; checkState(readCommand == other.readCommand, "Should share the same ReadCommand"); if (localResponses.size() == 1) { @@ -182,10 +195,25 @@ public class AccordInteropRead extends ReadData merged.add(localResponses.get(0)); localResponses = merged; } + // TODO (required): this may be safe, but it is surprising and we should avoid it (e.g. by using immutable structure like btree) localResponses.addAll(other.localResponses); return this; } + @Override + public Data without(Ranges ranges) + { + List> copy = new ArrayList<>(localResponses.size()); + for (Pair r : localResponses) + { + if (!ranges.contains(r.left)) + copy.add(r); + } + if (copy.size() == localResponses.size()) + return this; + return new LocalReadData(localResponses, readCommand); + } + private void ensureRemoteResponse() { if (remoteResponse != null) @@ -225,13 +253,13 @@ public class AccordInteropRead extends ReadData public AccordInteropRead(Node.Id to, Topologies topologies, TxnId txnId, Participants scope, long executeAtEpoch, ReadCommand command) { - super(to, topologies, txnId, scope, executeAtEpoch); + super(to, topologies, txnId, scope, null, null, executeAtEpoch); this.command = command; } public AccordInteropRead(TxnId txnId, Participants scope, long executeAtEpoch, ReadCommand command) { - super(txnId, scope, executeAtEpoch); + super(txnId, scope, null, null, executeAtEpoch); this.command = command; } @@ -251,7 +279,7 @@ public class AccordInteropRead extends ReadData SinglePartitionReadCommand readCommand = ((SinglePartitionReadCommand)command); TokenKey key = new TokenKey(readCommand.metadata().id, readCommand.partitionKey().getToken()); if (!execute.contains(key)) - return AsyncChains.success(null); + return AsyncChains.success(new LocalReadData(new ArrayList<>(), readCommand)); ReadCommand submit = readCommand.withTransactionalSettings(TxnNamedRead.readsWithoutReconciliation(txnRead.cassandraConsistencyLevel()), nowInSeconds); return AsyncChains.ofCallable(Stage.READ.executor(), () -> new LocalReadData(key, ReadCommandVerbHandler.instance.doRead(submit, false), command)); @@ -273,7 +301,7 @@ public class AccordInteropRead extends ReadData } if (chains.isEmpty()) - return AsyncChains.success(null); + return AsyncChains.success(new LocalReadData(new ArrayList<>(), command)); return AsyncChains.reduce(chains, Data::merge); } @@ -285,9 +313,9 @@ public class AccordInteropRead extends ReadData } @Override - protected ReadOk constructReadOk(Ranges unavailable, Data data, long uniqueHlc) + protected void reply(Ranges unavailable, Data data, long uniqueHlc) { - return new InteropReadOk(unavailable, data, uniqueHlc); + reply(new InteropReadOk(unavailable, data, uniqueHlc), null); } @Override diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropReadRepair.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropReadRepair.java index 995d93b6b0..0bfd3aeff9 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropReadRepair.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropReadRepair.java @@ -49,8 +49,7 @@ import org.apache.cassandra.service.accord.AccordMessageSink.AccordMessageType; import org.apache.cassandra.service.accord.serializers.CommandSerializers; import org.apache.cassandra.service.accord.serializers.IVersionedSerializer; import org.apache.cassandra.service.accord.serializers.KeySerializers; -import org.apache.cassandra.service.accord.serializers.ReadDataSerializers; -import org.apache.cassandra.service.accord.serializers.ReadDataSerializers.ReadDataSerializer; +import org.apache.cassandra.service.accord.serializers.ReadDataSerializer; import org.apache.cassandra.service.accord.serializers.Version; /** @@ -60,7 +59,7 @@ import org.apache.cassandra.service.accord.serializers.Version; */ public class AccordInteropReadRepair extends ReadData { - public static final IVersionedSerializer requestSerializer = new ReadDataSerializer() + public static final IVersionedSerializer requestSerializer = new IVersionedSerializer<>() { @Override public void serialize(AccordInteropReadRepair repair, DataOutputPlus out, Version version) throws IOException @@ -119,17 +118,17 @@ public class AccordInteropReadRepair extends ReadData public long serializedSize(Data t, Version version) { return 0; } }; - public static final IVersionedSerializer replySerializer = new ReadDataSerializers.ReplySerializer<>(noop_data_serializer); + public static final IVersionedSerializer replySerializer = new ReadDataSerializer.ReplySerializer<>(noop_data_serializer); public AccordInteropReadRepair(Node.Id to, Topologies topologies, TxnId txnId, Participants scope, long executeAtEpoch, Mutation mutation) { - super(to, topologies, txnId, scope, executeAtEpoch); + super(to, topologies, txnId, scope, null, null, executeAtEpoch); this.mutation = mutation; } public AccordInteropReadRepair(TxnId txnId, Participants scope, long executeAtEpoch, Mutation mutation) { - super(txnId, scope, executeAtEpoch); + super(txnId, scope, null, null, executeAtEpoch); this.mutation = mutation; } @@ -156,9 +155,9 @@ public class AccordInteropReadRepair extends ReadData } @Override - protected ReadOk constructReadOk(Ranges unavailable, Data data, long uniqueHlc) + protected void reply(Ranges unavailable, Data data, long uniqueHlc) { - return new InteropReadRepairOk(unavailable, data, uniqueHlc); + reply(new InteropReadRepairOk(unavailable, data, uniqueHlc), null); } @Override diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropStableThenRead.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropStableThenRead.java index 4f282467b7..5dcbb4552f 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropStableThenRead.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropStableThenRead.java @@ -50,7 +50,6 @@ import org.apache.cassandra.service.accord.serializers.CommitSerializers; import org.apache.cassandra.service.accord.serializers.DepsSerializers; import org.apache.cassandra.service.accord.serializers.IVersionedSerializer; import org.apache.cassandra.service.accord.serializers.KeySerializers; -import org.apache.cassandra.service.accord.serializers.ReadDataSerializers.ReadDataSerializer; import org.apache.cassandra.service.accord.serializers.Version; import static accord.messages.Commit.WithDeps.HasDeps; @@ -63,7 +62,7 @@ import static accord.primitives.SaveStatus.ReadyToExecute; public class AccordInteropStableThenRead extends AccordInteropRead { // TODO (desired): duplicates a lot of StableThenReadSerializer - public static final IVersionedSerializer requestSerializer = new ReadDataSerializer<>() + public static final IVersionedSerializer requestSerializer = new IVersionedSerializer<>() { @Override public void serialize(AccordInteropStableThenRead read, DataOutputPlus out, Version version) throws IOException diff --git a/src/java/org/apache/cassandra/service/accord/serializers/ApplySerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/ApplySerializers.java index 181634cc50..8716d87709 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/ApplySerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/ApplySerializers.java @@ -21,6 +21,7 @@ package org.apache.cassandra.service.accord.serializers; import java.io.IOException; import accord.api.Result; +import accord.coordinate.ExecuteFlag.ExecuteFlags; import accord.messages.Apply; import accord.primitives.Ballot; import accord.primitives.FullRoute; @@ -31,6 +32,7 @@ import accord.primitives.Timestamp; import accord.primitives.TxnId; import accord.primitives.Writes; import accord.utils.Invariants; +import accord.utils.VIntCoding; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.io.UnversionedSerializer; import org.apache.cassandra.io.util.DataInputPlus; @@ -76,10 +78,11 @@ public class ApplySerializers KeySerializers.nullableFullRoute.serialize(apply.fullRoute, out); if (apply.txnId.is(Write)) CommandSerializers.writes.serialize(apply.writes, out, version); + out.writeUnsignedVInt32(apply.flags.bits()); } protected abstract A deserializeApply(TxnId txnId, Ballot ballot, Route scope, long minEpoch, long waitForEpoch, long maxEpoch, Apply.Kind kind, - Timestamp executeAt, PartialDeps deps, PartialTxn txn, FullRoute fullRoute, Writes writes, Result result); + Timestamp executeAt, PartialDeps deps, PartialTxn txn, FullRoute fullRoute, Writes writes, Result result, ExecuteFlags flags); @Override public A deserializeBody(DataInputPlus in, Version version, TxnId txnId, Route scope, long waitForEpoch) throws IOException @@ -94,13 +97,14 @@ public class ApplySerializers CommandSerializers.nullablePartialTxn.deserialize(in, version), KeySerializers.nullableFullRoute.deserialize(in), (txnId.is(Write) ? CommandSerializers.writes.deserialize(in, version) : null), - ResultSerializers.APPLIED); + ResultSerializers.APPLIED, + ExecuteFlags.get(in.readUnsignedVInt32())); } @Override public long serializedBodySize(A apply, Version version) { - return CommandSerializers.ballot.serializedSize(apply.ballot) + return CommandSerializers.ballot.serializedSize(apply.ballot) + TypeSizes.sizeofVInt(apply.minEpoch - apply.waitForEpoch) + TypeSizes.sizeofUnsignedVInt(apply.maxEpoch - apply.minEpoch) + kind.serializedSize(apply.kind) @@ -108,7 +112,9 @@ public class ApplySerializers + DepsSerializers.partialDeps.serializedSize(apply.deps) + CommandSerializers.nullablePartialTxn.serializedSize(apply.txn, version) + KeySerializers.nullableFullRoute.serializedSize(apply.fullRoute) - + (apply.txnId.is(Write) ? CommandSerializers.writes.serializedSize(apply.writes, version) : 0); + + (apply.txnId.is(Write) ? CommandSerializers.writes.serializedSize(apply.writes, version) : 0) + + VIntCoding.sizeOfUnsignedVInt(apply.flags.bits()) + ; } } @@ -116,9 +122,9 @@ public class ApplySerializers { @Override protected Apply deserializeApply(TxnId txnId, Ballot ballot, Route scope, long minEpoch, long waitForEpoch, long maxEpoch, Apply.Kind kind, - Timestamp executeAt, PartialDeps deps, PartialTxn txn, FullRoute fullRoute, Writes writes, Result result) + Timestamp executeAt, PartialDeps deps, PartialTxn txn, FullRoute fullRoute, Writes writes, Result result, ExecuteFlags flags) { - return Apply.SerializationSupport.create(txnId, ballot, scope, minEpoch, waitForEpoch, maxEpoch, kind, executeAt, deps, txn, fullRoute, writes, result); + return Apply.SerializationSupport.create(txnId, ballot, scope, minEpoch, waitForEpoch, maxEpoch, kind, executeAt, deps, txn, fullRoute, writes, result, flags); } }; diff --git a/src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializer.java b/src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializer.java new file mode 100644 index 0000000000..aba9a3788b --- /dev/null +++ b/src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializer.java @@ -0,0 +1,360 @@ +/* + * 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 org.apache.cassandra.service.accord.serializers; + +import java.io.IOException; + +import accord.api.Data; +import accord.coordinate.ExecuteFlag.ExecuteFlags; +import accord.messages.ApplyThenWaitUntilApplied; +import accord.messages.Commit; +import accord.messages.ReadData; +import accord.messages.ReadData.CommitOrReadNack; +import accord.messages.ReadData.ReadOk; +import accord.messages.ReadData.ReadOkWithFutureEpoch; +import accord.messages.ReadData.ReadReply; +import accord.messages.ReadData.ReadType; +import accord.messages.ReadEphemeralTxnData; +import accord.messages.ReadTxnData; +import accord.messages.StableThenRead; +import accord.messages.WaitUntilApplied; +import accord.primitives.FullRoute; +import accord.primitives.PartialDeps; +import accord.primitives.PartialTxn; +import accord.primitives.Participants; +import accord.primitives.Ranges; +import accord.primitives.Timestamp; +import accord.primitives.TxnId; +import accord.primitives.Writes; +import accord.utils.UnhandledEnum; +import org.apache.cassandra.db.TypeSizes; +import org.apache.cassandra.io.VersionedSerializer; +import org.apache.cassandra.io.util.DataInputPlus; +import org.apache.cassandra.io.util.DataOutputPlus; +import org.apache.cassandra.service.accord.serializers.CommandSerializers.ExecuteAtSerializer; +import org.apache.cassandra.service.accord.txn.TxnData; +import org.apache.cassandra.utils.vint.VIntCoding; + +import static org.apache.cassandra.utils.NullableSerializer.deserializeNullable; +import static org.apache.cassandra.utils.NullableSerializer.serializeNullable; +import static org.apache.cassandra.utils.NullableSerializer.serializedNullableSize; + +public class ReadDataSerializer implements IVersionedSerializer +{ + public static final ReadDataSerializer request = new ReadDataSerializer(); + + private static final Commit.Kind[] KINDS = Commit.Kind.values(); + private static final ReadType[] TYPES = ReadType.values(); + private static final int HAS_TXN = 0x20; + private static final int HAS_EXECUTE_AT = 0x40; + private static final int HAS_EXECUTE_AT_EPOCH = 0x80; + + // ATWUA = ApplyThenWaitUntilApplied + private static final int ATWUA_HAS_MIN_EPOCH = 0x1; + private static final int ATWUA_HAS_WRITES = 0x2; + + // STR = StableThenRead + private static final int STR_HAS_DEPS = 0x1; + private static final int STR_HAS_FULL_ROUTE = 0x2; + + @Override + public void serialize(ReadData read, DataOutputPlus out, Version version) throws IOException + { + ReadType type = read.kind(); + boolean hasTxn = read.partialTxn() != null; + boolean hasExecuteAt = read.executeAt() != null; + boolean hasExecuteAtEpoch = (read.executeAt() != null ? read.executeAt() : read.txnId).epoch() != read.executeAtEpoch; + + out.writeUnsignedVInt32(type.ordinal() + | (read.flags.bits() << 3) + | (hasTxn ? HAS_TXN : 0) + | (hasExecuteAt ? HAS_EXECUTE_AT : 0) + | (hasExecuteAtEpoch ? HAS_EXECUTE_AT_EPOCH : 0) + ); + CommandSerializers.txnId.serialize(read.txnId, out); + KeySerializers.participants.serialize(read.scope, out); + if (hasTxn) + CommandSerializers.partialTxn.serialize(read.partialTxn(), out, version); + if (hasExecuteAt) + ExecuteAtSerializer.serialize(read.txnId, read.executeAt(), out); + if (hasExecuteAtEpoch) + out.writeVInt(read.executeAtEpoch - (read.executeAt() != null ? read.executeAt() : read.txnId).epoch()); + + switch (type) + { + default: throw new UnhandledEnum(type); + case readTxnData: break; + case waitUntilApplied: + { + out.writeVInt(read.txnId.epoch() - ((WaitUntilApplied)read).minEpoch()); + break; + } + case readEphemeral: + { + ReadEphemeralTxnData re = (ReadEphemeralTxnData) read; + DepsSerializers.partialDeps.serialize(re.partialDeps(), out); + KeySerializers.fullRoute.serialize(re.route(), out); + break; + } + case stableThenRead: + { + StableThenRead str = (StableThenRead) read; + out.writeUnsignedVInt32(str.kind.ordinal()); + out.writeVInt(read.txnId.epoch() - str.minEpoch); + boolean hasDeps = str.partialDeps() != null; + boolean hasFullRoute = str.route != null; + out.writeUnsignedVInt32((hasDeps ? STR_HAS_DEPS : 0) + | (hasFullRoute? STR_HAS_FULL_ROUTE : 0)); + if (hasDeps) + DepsSerializers.partialDeps.serialize(str.partialDeps(), out); + if (hasFullRoute) + KeySerializers.fullRoute.serialize(str.route, out); + break; + } + case applyThenWaitUntilApplied: + { + ApplyThenWaitUntilApplied msg = (ApplyThenWaitUntilApplied) read; + boolean hasMinEpoch = msg.minEpoch() != read.txnId.epoch(); + boolean hasWrites = msg.writes != null; + out.writeUnsignedVInt32((hasMinEpoch ? ATWUA_HAS_MIN_EPOCH : 0) + | (hasWrites ? ATWUA_HAS_WRITES : 0)); + if (hasMinEpoch) + out.writeVInt(read.txnId.epoch() - msg.minEpoch()); + DepsSerializers.partialDeps.serialize(msg.deps, out); + KeySerializers.fullRoute.serialize(msg.route, out); + if (hasWrites) + CommandSerializers.writes.serialize(msg.writes, out, version); + break; + } + } + } + + @Override + public ReadData deserialize(DataInputPlus in, Version version) throws IOException + { + ReadType type; + ExecuteFlags flags; + boolean hasTxn, hasExecuteAt, hasExecuteAtEpoch; + { + int tmp = in.readUnsignedVInt32(); + type = TYPES[tmp & 0x7]; + flags = ExecuteFlags.get((tmp >>> 3) & 0x3); + hasTxn = (tmp & HAS_TXN) != 0; + hasExecuteAt = (tmp & HAS_EXECUTE_AT) != 0; + hasExecuteAtEpoch = (tmp & HAS_EXECUTE_AT_EPOCH) != 0; + } + + TxnId txnId = CommandSerializers.txnId.deserialize(in); + Participants scope = KeySerializers.participants.deserialize(in); + PartialTxn txn = hasTxn ? CommandSerializers.partialTxn.deserialize(in, version) : null; + Timestamp executeAt = hasExecuteAt ? ExecuteAtSerializer.deserialize(txnId, in) : null; + long executeAtEpoch = (hasExecuteAt ? executeAt : txnId).epoch(); + if (hasExecuteAtEpoch) + executeAtEpoch += in.readVInt(); + + switch (type) + { + default: throw new UnhandledEnum(type); + case readTxnData: + { + return ReadTxnData.SerializerSupport.create(txnId, scope, txn, executeAt, executeAtEpoch, flags); + } + case waitUntilApplied: + { + long minEpoch = txnId.epoch() - in.readVInt(); + return WaitUntilApplied.SerializerSupport.create(txnId, scope, minEpoch, executeAtEpoch); + } + case readEphemeral: + { + PartialDeps deps = DepsSerializers.partialDeps.deserialize(in); + FullRoute route = KeySerializers.fullRoute.deserialize(in); + return ReadEphemeralTxnData.SerializerSupport.create(txnId, scope, txn, deps, route, flags); + } + case stableThenRead: + { + Commit.Kind kind = KINDS[in.readUnsignedVInt32()]; + long minEpoch = txnId.epoch() - in.readVInt(); + boolean hasDeps, hasFullRoute; + { + int extraFlags = in.readUnsignedVInt32(); + hasDeps = (extraFlags & STR_HAS_DEPS) != 0; + hasFullRoute = (extraFlags & STR_HAS_FULL_ROUTE) != 0; + } + PartialDeps deps = hasDeps ? DepsSerializers.partialDeps.deserialize(in) : null; + FullRoute route = hasFullRoute ? KeySerializers.fullRoute.deserialize(in) : null; + return StableThenRead.SerializerSupport.create(txnId, scope, kind, minEpoch, executeAt, txn, deps, route); + } + case applyThenWaitUntilApplied: + { + boolean hasMinEpoch, hasWrites; + { + int extraFlags = in.readUnsignedVInt32(); + hasMinEpoch = (extraFlags & ATWUA_HAS_MIN_EPOCH) != 0; + hasWrites = (extraFlags & ATWUA_HAS_WRITES) != 0; + } + long minEpoch = txnId.epoch(); + if (hasMinEpoch) + minEpoch = txnId.epoch() - in.readVInt(); + PartialDeps deps = DepsSerializers.partialDeps.deserialize(in); + FullRoute route = KeySerializers.fullRoute.deserialize(in); + Writes writes = hasWrites ? CommandSerializers.writes.deserialize(in, version) : null; + return ApplyThenWaitUntilApplied.SerializerSupport.create(txnId, scope, minEpoch, executeAt, route, txn, deps, writes, ResultSerializers.APPLIED); + } + } + } + + @Override + public long serializedSize(ReadData read, Version version) + { + ReadType type = read.kind(); + boolean hasTxn = read.partialTxn() != null; + boolean hasExecuteAt = read.executeAt() != null; + boolean hasExecuteAtEpoch = (read.executeAt() != null ? read.executeAt() : read.txnId).epoch() != read.executeAtEpoch; + + long size = VIntCoding.computeUnsignedVIntSize(type.ordinal() + | (read.flags.bits() << 3) + | (hasTxn ? HAS_TXN : 0) + | (hasExecuteAt ? HAS_EXECUTE_AT : 0) + | (hasExecuteAtEpoch ? HAS_EXECUTE_AT_EPOCH : 0)) + + CommandSerializers.txnId.serializedSize(read.txnId) + + KeySerializers.participants.serializedSize(read.scope); + if (hasTxn) + size += CommandSerializers.partialTxn.serializedSize(read.partialTxn(), version); + if (hasExecuteAt) + size += ExecuteAtSerializer.serializedSize(read.txnId, read.executeAt()); + if (hasExecuteAtEpoch) + size += VIntCoding.computeVIntSize(read.executeAtEpoch - (read.executeAt() != null ? read.executeAt() : read.txnId).epoch()); + + switch (type) + { + default: throw new UnhandledEnum(type); + case readTxnData: break; + case waitUntilApplied: + { + size += VIntCoding.computeVIntSize(read.txnId.epoch() - ((WaitUntilApplied)read).minEpoch()); + break; + } + case readEphemeral: + { + ReadEphemeralTxnData re = (ReadEphemeralTxnData) read; + size += DepsSerializers.partialDeps.serializedSize(re.partialDeps()); + size += KeySerializers.fullRoute.serializedSize(re.route()); + break; + } + case stableThenRead: + { + StableThenRead str = (StableThenRead) read; + size += VIntCoding.computeUnsignedVIntSize(str.kind.ordinal()); + size += VIntCoding.computeVIntSize(read.txnId.epoch() - str.minEpoch); + boolean hasDeps = str.partialDeps() != null; + boolean hasFullRoute = str.route != null; + size += VIntCoding.computeUnsignedVIntSize((hasDeps ? STR_HAS_DEPS : 0) + | (hasFullRoute? STR_HAS_FULL_ROUTE : 0)); + if (hasDeps) + size += DepsSerializers.partialDeps.serializedSize(str.partialDeps()); + if (hasFullRoute) + size += KeySerializers.fullRoute.serializedSize(str.route); + break; + } + case applyThenWaitUntilApplied: + { + ApplyThenWaitUntilApplied msg = (ApplyThenWaitUntilApplied) read; + boolean hasMinEpoch = msg.minEpoch() != read.txnId.epoch(); + boolean hasWrites = msg.writes != null; + size += VIntCoding.computeUnsignedVIntSize((hasMinEpoch ? ATWUA_HAS_MIN_EPOCH : 0) + | (hasWrites ? ATWUA_HAS_WRITES : 0)); + if (hasMinEpoch) + size += VIntCoding.computeVIntSize(read.txnId.epoch() - msg.minEpoch()); + size += DepsSerializers.partialDeps.serializedSize(msg.deps); + size += KeySerializers.fullRoute.serializedSize(msg.route); + if (hasWrites) + size += CommandSerializers.writes.serializedSize(msg.writes, version); + break; + } + } + return size; + } + + public static final class ReplySerializer implements IVersionedSerializer + { + final CommitOrReadNack[] nacks = CommitOrReadNack.values(); + private final VersionedSerializer dataSerializer; + + public ReplySerializer(VersionedSerializer dataSerializer) + { + this.dataSerializer = dataSerializer; + } + + @Override + public void serialize(ReadReply reply, DataOutputPlus out, Version version) throws IOException + { + if (!reply.isOk()) + { + out.writeByte(3 + ((CommitOrReadNack) reply).ordinal()); + return; + } + + ReadOk readOk = (ReadOk) reply; + int flags = readOk.getClass() == ReadOkWithFutureEpoch.class ? 2 : readOk.uniqueHlc != 0 ? 1 : 0; + out.writeByte(flags); + serializeNullable(readOk.unavailable, out, KeySerializers.ranges); + dataSerializer.serialize((D) readOk.data, out, version); + switch (flags) + { + case 2: out.writeUnsignedVInt(((ReadOkWithFutureEpoch) reply).futureEpoch); break; + case 1: out.writeUnsignedVInt(readOk.uniqueHlc); + } + } + + @Override + public ReadReply deserialize(DataInputPlus in, Version version) throws IOException + { + int flags = in.readByte(); + if (flags > 2) + return nacks[flags - 3]; + + Ranges unavailable = deserializeNullable(in, KeySerializers.ranges); + D data = dataSerializer.deserialize(in, version); + + long extraLong = flags == 0 ? 0 : in.readUnsignedVInt(); + if (flags <= 1) + return new ReadOk(unavailable, data, extraLong); + return new ReadOkWithFutureEpoch(unavailable, data, extraLong); + } + + @Override + public long serializedSize(ReadReply reply, Version version) + { + if (!reply.isOk()) + return TypeSizes.BYTE_SIZE; + + ReadOk readOk = (ReadOk) reply; + long size = TypeSizes.BYTE_SIZE + + serializedNullableSize(readOk.unavailable, KeySerializers.ranges) + + dataSerializer.serializedSize((D) readOk.data, version); + if (readOk.uniqueHlc != 0) + size += TypeSizes.sizeofUnsignedVInt(readOk.uniqueHlc); + else if (readOk instanceof ReadOkWithFutureEpoch) + size += TypeSizes.sizeofUnsignedVInt(((ReadOkWithFutureEpoch) readOk).futureEpoch); + return size; + } + } + + public static final IVersionedSerializer reply = new ReplySerializer<>(TxnData.nullableSerializer); +} diff --git a/src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializers.java deleted file mode 100644 index 82e702b5c2..0000000000 --- a/src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializers.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * 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 org.apache.cassandra.service.accord.serializers; - -import java.io.IOException; - -import accord.api.Data; -import accord.messages.ApplyThenWaitUntilApplied; -import accord.messages.Commit; -import accord.messages.ReadData; -import accord.messages.ReadData.CommitOrReadNack; -import accord.messages.ReadData.ReadOk; -import accord.messages.ReadData.ReadOkWithFutureEpoch; -import accord.messages.ReadData.ReadReply; -import accord.messages.ReadData.ReadType; -import accord.messages.ReadEphemeralTxnData; -import accord.messages.ReadTxnData; -import accord.messages.StableThenRead; -import accord.messages.WaitUntilApplied; -import accord.primitives.FullRoute; -import accord.primitives.PartialDeps; -import accord.primitives.PartialTxn; -import accord.primitives.Participants; -import accord.primitives.Ranges; -import accord.primitives.Timestamp; -import accord.primitives.TxnId; -import org.apache.cassandra.db.TypeSizes; -import org.apache.cassandra.io.VersionedSerializer; -import org.apache.cassandra.io.util.DataInputPlus; -import org.apache.cassandra.io.util.DataOutputPlus; -import org.apache.cassandra.service.accord.serializers.CommandSerializers.ExecuteAtSerializer; -import org.apache.cassandra.service.accord.txn.TxnData; - -import static accord.messages.Commit.WithDeps.HasDeps; -import static accord.messages.Commit.WithDeps.NoDeps; -import static accord.messages.Commit.WithTxn.HasTxn; -import static accord.messages.Commit.WithTxn.NoTxn; -import static org.apache.cassandra.db.TypeSizes.sizeof; -import static org.apache.cassandra.utils.NullableSerializer.deserializeNullable; -import static org.apache.cassandra.utils.NullableSerializer.serializeNullable; -import static org.apache.cassandra.utils.NullableSerializer.serializedNullableSize; - -public class ReadDataSerializers -{ - public static final IVersionedSerializer readData = new IVersionedSerializer() - { - @Override - public void serialize(ReadData t, DataOutputPlus out, Version version) throws IOException - { - out.writeByte(t.kind().val); - serializerFor(t).serialize(t, out, version); - } - - @Override - public ReadData deserialize(DataInputPlus in, Version version) throws IOException - { - return serializerFor(ReadType.valueOf(in.readByte())).deserialize(in, version); - } - - @Override - public long serializedSize(ReadData t, Version version) - { - return sizeof(t.kind().val) + serializerFor(t).serializedSize(t, version); - } - }; - - public static final ApplyThenWaitUntilAppliedSerializer applyThenWaitUntilApplied = new ApplyThenWaitUntilAppliedSerializer(); - - public static class ApplyThenWaitUntilAppliedSerializer implements ReadDataSerializer - { - @Override - public void serialize(ApplyThenWaitUntilApplied msg, DataOutputPlus out, Version version) throws IOException - { - CommandSerializers.txnId.serialize(msg.txnId, out); - KeySerializers.participants.serialize(msg.scope, out); - out.writeUnsignedVInt(msg.minEpoch()); - ExecuteAtSerializer.serialize(msg.txnId, msg.executeAt, out); - KeySerializers.fullRoute.serialize(msg.route, out); - CommandSerializers.partialTxn.serialize(msg.txn, out, version); - DepsSerializers.partialDeps.serialize(msg.deps, out); - CommandSerializers.nullableWrites.serialize(msg.writes, out, version); - } - - @Override - public ApplyThenWaitUntilApplied deserialize(DataInputPlus in, Version version) throws IOException - { - TxnId txnId = CommandSerializers.txnId.deserialize(in); - return ApplyThenWaitUntilApplied.SerializerSupport.create( - txnId, - KeySerializers.participants.deserialize(in), - in.readUnsignedVInt(), - ExecuteAtSerializer.deserialize(txnId, in), - KeySerializers.fullRoute.deserialize(in), - CommandSerializers.partialTxn.deserialize(in, version), - DepsSerializers.partialDeps.deserialize(in), - CommandSerializers.nullableWrites.deserialize(in, version), - ResultSerializers.APPLIED); - } - - @Override - public long serializedSize(ApplyThenWaitUntilApplied msg, Version version) - { - return CommandSerializers.txnId.serializedSize(msg.txnId) - + KeySerializers.participants.serializedSize(msg.scope) - + TypeSizes.sizeofUnsignedVInt(msg.minEpoch()) - + ExecuteAtSerializer.serializedSize(msg.txnId, msg.executeAt) - + KeySerializers.fullRoute.serializedSize(msg.route) - + CommandSerializers.partialTxn.serializedSize(msg.txn, version) - + DepsSerializers.partialDeps.serializedSize(msg.deps) - + CommandSerializers.nullableWrites.serializedSize(msg.writes, version); - } - } - - private static final ReadDataSerializer readTxnData = new ReadDataSerializer() - { - @Override - public void serialize(ReadTxnData read, DataOutputPlus out, Version version) throws IOException - { - CommandSerializers.txnId.serialize(read.txnId, out); - KeySerializers.participants.serialize(read.scope, out); - out.writeUnsignedVInt(read.executeAtEpoch); - } - - @Override - public ReadTxnData deserialize(DataInputPlus in, Version version) throws IOException - { - TxnId txnId = CommandSerializers.txnId.deserialize(in); - Participants scope = KeySerializers.participants.deserialize(in); - long executeAtEpoch = in.readUnsignedVInt(); - return ReadTxnData.SerializerSupport.create(txnId, scope, executeAtEpoch); - } - - @Override - public long serializedSize(ReadTxnData read, Version version) - { - return CommandSerializers.txnId.serializedSize(read.txnId) - + KeySerializers.participants.serializedSize(read.scope) - + TypeSizes.sizeofUnsignedVInt(read.executeAtEpoch); - } - }; - - public static final ReadDataSerializer readEphemeralTxnData = new ReadDataSerializer<>() - { - @Override - public void serialize(ReadEphemeralTxnData read, DataOutputPlus out, Version version) throws IOException - { - CommandSerializers.txnId.serialize(read.txnId, out); - KeySerializers.participants.serialize(read.scope, out); - out.writeUnsignedVInt(read.executeAtEpoch); - CommandSerializers.partialTxn.serialize(read.partialTxn(), out, version); - DepsSerializers.partialDeps.serialize(read.partialDeps(), out); - KeySerializers.fullRoute.serialize(read.route(), out); - } - - @Override - public ReadEphemeralTxnData deserialize(DataInputPlus in, Version version) throws IOException - { - TxnId txnId = CommandSerializers.txnId.deserialize(in); - Participants scope = KeySerializers.participants.deserialize(in); - long executeAtEpoch = in.readUnsignedVInt(); - PartialTxn partialTxn = CommandSerializers.partialTxn.deserialize(in, version); - PartialDeps partialDeps = DepsSerializers.partialDeps.deserialize(in); - FullRoute route = KeySerializers.fullRoute.deserialize(in); - return ReadEphemeralTxnData.SerializerSupport.create(txnId, scope, executeAtEpoch, partialTxn, partialDeps, route); - } - - @Override - public long serializedSize(ReadEphemeralTxnData read, Version version) - { - return CommandSerializers.txnId.serializedSize(read.txnId) - + KeySerializers.participants.serializedSize(read.scope) - + TypeSizes.sizeofUnsignedVInt(read.executeAtEpoch) - + CommandSerializers.partialTxn.serializedSize(read.partialTxn(), version) - + DepsSerializers.partialDeps.serializedSize(read.partialDeps()) - + KeySerializers.fullRoute.serializedSize(read.route()); - } - }; - - public interface ReadDataSerializer extends IVersionedSerializer - { - void serialize(T bound, DataOutputPlus out, Version version) throws IOException; - T deserialize(DataInputPlus in, Version version) throws IOException; - long serializedSize(T condition, Version version); - } - - private static ReadDataSerializer serializerFor(ReadData toSerialize) - { - return serializerFor(toSerialize.kind()); - } - - private static ReadDataSerializer serializerFor(ReadType type) - { - switch (type) - { - case readTxnData: - return readTxnData; - case readDataWithoutTimestamp: - return readEphemeralTxnData; - case applyThenWaitUntilApplied: - return applyThenWaitUntilApplied; - case waitUntilApplied: - return waitUntilApplied; - default: - throw new IllegalStateException("Unsupported ExecuteType " + type); - } - } - - public static final class ReplySerializer implements IVersionedSerializer - { - final CommitOrReadNack[] nacks = CommitOrReadNack.values(); - private final VersionedSerializer dataSerializer; - - public ReplySerializer(VersionedSerializer dataSerializer) - { - this.dataSerializer = dataSerializer; - } - - @Override - public void serialize(ReadReply reply, DataOutputPlus out, Version version) throws IOException - { - if (!reply.isOk()) - { - out.writeByte(3 + ((CommitOrReadNack) reply).ordinal()); - return; - } - - ReadOk readOk = (ReadOk) reply; - int flags = readOk.getClass() == ReadOkWithFutureEpoch.class ? 2 : readOk.uniqueHlc != 0 ? 1 : 0; - out.writeByte(flags); - serializeNullable(readOk.unavailable, out, KeySerializers.ranges); - dataSerializer.serialize((D) readOk.data, out, version); - switch (flags) - { - case 2: out.writeUnsignedVInt(((ReadOkWithFutureEpoch) reply).futureEpoch); break; - case 1: out.writeUnsignedVInt(readOk.uniqueHlc); - } - } - - @Override - public ReadReply deserialize(DataInputPlus in, Version version) throws IOException - { - int flags = in.readByte(); - if (flags > 2) - return nacks[flags - 3]; - - Ranges unavailable = deserializeNullable(in, KeySerializers.ranges); - D data = dataSerializer.deserialize(in, version); - - long extraLong = flags == 0 ? 0 : in.readUnsignedVInt(); - if (flags <= 1) - return new ReadOk(unavailable, data, extraLong); - return new ReadOkWithFutureEpoch(unavailable, data, extraLong); - } - - @Override - public long serializedSize(ReadReply reply, Version version) - { - if (!reply.isOk()) - return TypeSizes.BYTE_SIZE; - - ReadOk readOk = (ReadOk) reply; - long size = TypeSizes.BYTE_SIZE - + serializedNullableSize(readOk.unavailable, KeySerializers.ranges) - + dataSerializer.serializedSize((D) readOk.data, version); - if (readOk.uniqueHlc != 0) - size += TypeSizes.sizeofUnsignedVInt(readOk.uniqueHlc); - else if (readOk instanceof ReadOkWithFutureEpoch) - size += TypeSizes.sizeofUnsignedVInt(((ReadOkWithFutureEpoch) readOk).futureEpoch); - return size; - } - } - - public static final IVersionedSerializer reply = new ReplySerializer<>(TxnData.nullableSerializer); - - // TODO (desired): duplicates ReadTxnData ser/de logic; conside deduplicating if another instance of this is added - public static final ReadDataSerializer waitUntilApplied = new ReadDataSerializer() - { - @Override - public void serialize(WaitUntilApplied waitUntilApplied, DataOutputPlus out, Version version) throws IOException - { - CommandSerializers.txnId.serialize(waitUntilApplied.txnId, out); - KeySerializers.participants.serialize(waitUntilApplied.scope, out); - out.writeUnsignedVInt(waitUntilApplied.minEpoch()); - out.writeUnsignedVInt(waitUntilApplied.executeAtEpoch - waitUntilApplied.minEpoch()); - } - - @Override - public WaitUntilApplied deserialize(DataInputPlus in, Version version) throws IOException - { - TxnId txnId = CommandSerializers.txnId.deserialize(in); - Participants scope = KeySerializers.participants.deserialize(in); - long minEpoch = in.readUnsignedVInt(); - long executeAtEpoch = minEpoch + in.readUnsignedVInt(); - return WaitUntilApplied.SerializerSupport.create(txnId, scope, minEpoch, executeAtEpoch); - } - - @Override - public long serializedSize(WaitUntilApplied waitUntilApplied, Version version) - { - return CommandSerializers.txnId.serializedSize(waitUntilApplied.txnId) - + KeySerializers.participants.serializedSize(waitUntilApplied.scope) - + TypeSizes.sizeofUnsignedVInt(waitUntilApplied.minEpoch()) - + TypeSizes.sizeofUnsignedVInt(waitUntilApplied.executeAtEpoch - waitUntilApplied.minEpoch()); - } - }; - - // TODO (desired): duplicates a lot of Commit serializer - public static final ReadDataSerializer stableThenRead = new ReadDataSerializer<>() - { - @Override - public void serialize(StableThenRead read, DataOutputPlus out, Version version) throws IOException - { - CommandSerializers.txnId.serialize(read.txnId, out); - KeySerializers.participants.serialize(read.scope, out); - CommitSerializers.kind.serialize(read.kind, out); - out.writeUnsignedVInt(read.minEpoch); - ExecuteAtSerializer.serialize(read.txnId, read.executeAt, out); - if (read.kind.withTxn != NoTxn) - CommandSerializers.nullablePartialTxn.serialize(read.partialTxn, out, version); - if (read.kind.withDeps == HasDeps) - DepsSerializers.partialDeps.serialize(read.partialDeps, out); - if (read.kind.withTxn == HasTxn) - KeySerializers.fullRoute.serialize(read.route, out); - } - - @Override - public StableThenRead deserialize(DataInputPlus in, Version version) throws IOException - { - TxnId txnId = CommandSerializers.txnId.deserialize(in); - Participants scope = KeySerializers.participants.deserialize(in); - Commit.Kind kind = CommitSerializers.kind.deserialize(in); - long minEpoch = in.readUnsignedVInt(); - Timestamp executeAt = ExecuteAtSerializer.deserialize(txnId, in); - PartialTxn partialTxn = kind.withTxn == NoTxn ? null : CommandSerializers.nullablePartialTxn.deserialize(in, version); - PartialDeps partialDeps = kind.withDeps == NoDeps ? null : DepsSerializers.partialDeps.deserialize(in); - FullRoute < ?> route = kind.withTxn == HasTxn ? KeySerializers.fullRoute.deserialize(in) : null; - return StableThenRead.SerializerSupport.create(txnId, scope, kind, minEpoch, executeAt, partialTxn, partialDeps, route); - } - - @Override - public long serializedSize(StableThenRead read, Version version) - { - return CommandSerializers.txnId.serializedSize(read.txnId) - + KeySerializers.participants.serializedSize(read.scope) - + CommitSerializers.kind.serializedSize(read.kind) - + TypeSizes.sizeofUnsignedVInt(read.minEpoch) - + ExecuteAtSerializer.serializedSize(read.txnId, read.executeAt) - + (read.kind.withTxn == NoTxn ? 0 : CommandSerializers.nullablePartialTxn.serializedSize(read.partialTxn, version)) - + (read.kind.withDeps != HasDeps ? 0 : DepsSerializers.partialDeps.serializedSize(read.partialDeps)) - + (read.kind.withTxn != HasTxn ? 0 : KeySerializers.fullRoute.serializedSize(read.route)); - } - }; -} diff --git a/src/java/org/apache/cassandra/service/accord/txn/TxnData.java b/src/java/org/apache/cassandra/service/accord/txn/TxnData.java index 27369dcaef..74ef6b1e13 100644 --- a/src/java/org/apache/cassandra/service/accord/txn/TxnData.java +++ b/src/java/org/apache/cassandra/service/accord/txn/TxnData.java @@ -22,6 +22,9 @@ import java.io.IOException; import java.util.Map; import accord.api.Data; +import accord.primitives.Ranges; +import accord.primitives.Timestamp; +import accord.primitives.TxnId; import org.agrona.collections.Int2ObjectHashMap; import org.apache.cassandra.db.EmptyIterators; import org.apache.cassandra.db.SinglePartitionReadCommand; @@ -131,14 +134,74 @@ public class TxnData extends Int2ObjectHashMap implements TxnResul @Override public TxnData merge(Data data) { - TxnData that = (TxnData) data; - TxnData merged = new TxnData(); - this.forEach(merged::put); - for (Map.Entry e : that.entrySet()) - merged.merge(e.getKey(), e.getValue(), TxnDataValue::merge); + return merge(this, (TxnData) data); + } + + private static TxnData merge(TxnData a, TxnData b) + { + if (a.size() < b.size()) { TxnData tmp = a; a = b; b = tmp; } + + TxnData merged = null; + int matches = 0; + for (Map.Entry e : a.entrySet()) + { + Integer key = e.getKey(); + TxnDataValue av = e.getValue(); + TxnDataValue bv = b.get(key); + if (bv != null || merged != null) + { + if (bv == null) merged.put(key, av); + else + { + ++matches; + TxnDataValue upd = e.getValue().merge(bv); + if (merged != null || upd != av) + { + if (merged == null) merged = new TxnData(); + merged.put(key, upd); + } + } + } + } + + if (matches == b.size()) + return merged == null ? a : merged; + + if (merged == null) + { + merged = new TxnData(); + a.forEach(merged::put); + } + + b.forEach(merged::putIfAbsent); return merged; } + @Override + public Data without(Ranges ranges) + { + TxnData result = null; + for (Map.Entry e : entrySet()) + { + TxnDataValue oldValue = e.getValue(); + TxnDataValue newValue = oldValue.without(ranges); + if (oldValue == newValue) + { + if (result != null) + result.put(e.getKey(), oldValue); + } + else + { + if (result == null) + result = new TxnData(); + if (newValue != null) + result.put(e.getKey(), newValue); + } + } + + return result != null ? result : this; + } + public static Data merge(Data left, Data right) { if (left == null) @@ -149,6 +212,20 @@ public class TxnData extends Int2ObjectHashMap implements TxnResul return left.merge(right); } + @Override + public boolean validateReply(TxnId txnId, Timestamp executeAt, boolean futureReadPossible) + { + if (futureReadPossible) + { + for (TxnDataValue value : values()) + { + if (value.maxTimestamp() >= executeAt.hlc()) + return false; + } + } + return true; + } + @Override public long estimatedSizeOnHeap() { diff --git a/src/java/org/apache/cassandra/service/accord/txn/TxnDataKeyValue.java b/src/java/org/apache/cassandra/service/accord/txn/TxnDataKeyValue.java index ea8d014757..e18524cf5d 100644 --- a/src/java/org/apache/cassandra/service/accord/txn/TxnDataKeyValue.java +++ b/src/java/org/apache/cassandra/service/accord/txn/TxnDataKeyValue.java @@ -20,6 +20,7 @@ package org.apache.cassandra.service.accord.txn; import java.io.IOException; +import accord.primitives.Ranges; import accord.utils.Invariants; import org.apache.cassandra.db.partitions.FilteredPartition; import org.apache.cassandra.db.rows.Row; @@ -32,6 +33,7 @@ import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.service.accord.api.TokenKey; import org.apache.cassandra.service.accord.serializers.Version; import static org.apache.cassandra.db.SerializationHeader.StableHeaderSerializer.STABLE; @@ -57,6 +59,12 @@ public class TxnDataKeyValue extends FilteredPartition implements TxnDataValue return this; } + @Override + public TxnDataValue without(Ranges ranges) + { + return ranges.contains(new TokenKey(metadata().id, partitionKey().getToken())) ? this : null; + } + @Override public long estimatedSizeOnHeap() { diff --git a/src/java/org/apache/cassandra/service/accord/txn/TxnDataRangeValue.java b/src/java/org/apache/cassandra/service/accord/txn/TxnDataRangeValue.java index edb9ac7528..a23c05828b 100644 --- a/src/java/org/apache/cassandra/service/accord/txn/TxnDataRangeValue.java +++ b/src/java/org/apache/cassandra/service/accord/txn/TxnDataRangeValue.java @@ -27,6 +27,7 @@ import javax.annotation.Nullable; import com.google.common.collect.Lists; +import accord.primitives.Ranges; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.db.partitions.FilteredPartition; import org.apache.cassandra.db.partitions.PartitionIterator; @@ -40,6 +41,8 @@ import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.service.accord.TokenRange; +import org.apache.cassandra.service.accord.api.TokenKey; import org.apache.cassandra.service.accord.serializers.Version; import org.apache.cassandra.utils.ObjectSizes; @@ -83,6 +86,34 @@ public class TxnDataRangeValue extends ArrayList implements T return this; } + @Override + public TxnDataValue without(Ranges ranges) + { + TokenRange range = range(); + if (range == null || !ranges.intersects(range)) + return this; + + if (ranges.contains(range)) + return null; + + TxnDataRangeValue result = new TxnDataRangeValue(); + for (FilteredPartition p : this) + { + if (!ranges.contains(new TokenKey(p.metadata().id, p.partitionKey().getToken()))) + result.add(p); + } + return result; + } + + @Override + public long maxTimestamp() + { + long max = 0; + for (FilteredPartition partition : this) + max = Math.max(max, partition.maxTimestamp()); + return max; + } + Supplier toPartitionIterator(boolean reversed) { // Sorting isn't preserved when merging TxnDataRangeValues together so sort here @@ -95,13 +126,25 @@ public class TxnDataRangeValue extends ArrayList implements T sort(Comparator.comparing(FilteredPartition::partitionKey)); } - private @Nullable TableId tableId() + public @Nullable TableId tableId() { if (isEmpty()) return null; return get(0).metadata().id; } + public @Nullable TokenRange range() + { + if (isEmpty()) + return null; + FilteredPartition first = get(0); + FilteredPartition last = get(size() - 1); + return TokenRange.create(new TokenKey(first.metadata().id, first.partitionKey().getToken()), + new TokenKey(last.metadata().id, last.partitionKey().getToken())); + } + + + @Override public long estimatedSizeOnHeap() { diff --git a/src/java/org/apache/cassandra/service/accord/txn/TxnDataValue.java b/src/java/org/apache/cassandra/service/accord/txn/TxnDataValue.java index bee1340854..3fc9c08198 100644 --- a/src/java/org/apache/cassandra/service/accord/txn/TxnDataValue.java +++ b/src/java/org/apache/cassandra/service/accord/txn/TxnDataValue.java @@ -20,6 +20,9 @@ package org.apache.cassandra.service.accord.txn; import java.io.IOException; +import javax.annotation.Nullable; + +import accord.primitives.Ranges; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.service.accord.serializers.IVersionedSerializer; @@ -63,6 +66,8 @@ public interface TxnDataValue TxnDataValue.Kind kind(); TxnDataValue merge(TxnDataValue other); + @Nullable TxnDataValue without(Ranges ranges); + long maxTimestamp(); long estimatedSizeOnHeap(); diff --git a/src/java/org/apache/cassandra/service/accord/txn/TxnNamedRead.java b/src/java/org/apache/cassandra/service/accord/txn/TxnNamedRead.java index ab774c806a..87a37a355a 100644 --- a/src/java/org/apache/cassandra/service/accord/txn/TxnNamedRead.java +++ b/src/java/org/apache/cassandra/service/accord/txn/TxnNamedRead.java @@ -35,10 +35,7 @@ import accord.primitives.Seekable; import accord.primitives.Timestamp; import accord.utils.Invariants; import accord.utils.async.AsyncChain; -import accord.utils.async.AsyncChains; import accord.utils.async.AsyncResults; -import org.apache.cassandra.concurrent.DebuggableTask; -import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.PartitionPosition; import org.apache.cassandra.db.PartitionRangeReadCommand; @@ -60,6 +57,7 @@ import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.schema.TableId; +import org.apache.cassandra.service.accord.AccordExecutor; import org.apache.cassandra.service.accord.TokenRange; import org.apache.cassandra.service.accord.api.TokenKey; import org.apache.cassandra.service.accord.api.PartitionKey; @@ -69,7 +67,6 @@ import org.apache.cassandra.service.accord.serializers.Version; import org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.Comparables; -import org.apache.cassandra.utils.MonotonicClock; import org.apache.cassandra.utils.ObjectSizes; import static com.google.common.base.Preconditions.checkState; @@ -229,7 +226,7 @@ public class TxnNamedRead extends AbstractParameterisedVersionedSerialized read(TableMetadatas tables, ConsistencyLevel consistencyLevel, Seekable key, Timestamp executeAt) + public AsyncChain read(AccordExecutor executor, TableMetadatas tables, ConsistencyLevel consistencyLevel, Seekable key, Timestamp executeAt) { ReadCommand command = deserialize(tables); if (command == null) @@ -244,9 +241,9 @@ public class TxnNamedRead extends AbstractParameterisedVersionedSerialized performLocalKeyRead(SinglePartitionReadCommand read) + private AsyncChain performLocalKeyRead(AccordExecutor executor, SinglePartitionReadCommand command) { - Callable readCallable = () -> + Callable callable = new Callable<>() { - try (ReadExecutionController controller = read.executionController(); - PartitionIterator iterator = UnfilteredPartitionIterators.filter(read.executeLocally(controller), read.nowInSec())) + @Override + public Data call() { - TxnData result = new TxnData(); - if (iterator.hasNext()) + try (ReadExecutionController controller = command.executionController(); + PartitionIterator iterator = UnfilteredPartitionIterators.filter(command.executeLocally(controller), command.nowInSec())) { - TxnDataKeyValue value = new TxnDataKeyValue(iterator.next()); - if (value.hasRows() || read.selectsFullPartition()) - result.put(name, value); + TxnData result = new TxnData(); + if (iterator.hasNext()) + { + TxnDataKeyValue value = new TxnDataKeyValue(iterator.next()); + if (value.hasRows() || command.selectsFullPartition()) + result.put(name, value); + } + return result; } - return result; + } + + @Override + public String toString() + { + return command.toCQLString(); } }; - return AsyncChains.ofCallable(Stage.READ.executor(), readCallable, (callable, receiver) -> - new DebuggableTask.RunnableDebuggableTask() - { - private final long approxCreationTimeNanos = MonotonicClock.Global.approxTime.now(); - private volatile long approxStartTimeNanos; - - @Override - public void run() - { - approxStartTimeNanos = MonotonicClock.Global.approxTime.now(); - - try - { - Data call = callable.call(); - receiver.accept(call, null); - } - catch (Throwable t) - { - logger.debug("AsyncChain Callable threw an Exception", t); - receiver.accept(null, t); - } - } - - @Override - public long creationTimeNanos() - { - return approxCreationTimeNanos; - } - - @Override - public long startTimeNanos() - { - return approxStartTimeNanos; - } - - @Override - public String description() - { - return read.toCQLString(); - } - } - ); + return submit(executor, callable, callable); } public static PartitionRangeReadCommand commandForSubrange(PartitionRangeReadCommand command, Range r, ConsistencyLevel consistencyLevel, long nowInSeconds) @@ -392,75 +358,48 @@ public class TxnNamedRead extends AbstractParameterisedVersionedSerialized performLocalRangeRead(PartitionRangeReadCommand command, Range r, ConsistencyLevel consistencyLevel, long nowInSeconds) + private AsyncChain performLocalRangeRead(AccordExecutor executor, PartitionRangeReadCommand command, Range r, ConsistencyLevel consistencyLevel, long nowInSeconds) { PartitionRangeReadCommand read = commandForSubrange(command, r, consistencyLevel, nowInSeconds); - Callable readCallable = () -> + Callable callable = new Callable<>() { - try (ReadExecutionController controller = read.executionController(); - UnfilteredPartitionIterator partition = read.executeLocally(controller); - PartitionIterator iterator = UnfilteredPartitionIterators.filter(partition, read.nowInSec())) + @Override + public Data call() { - TxnData result = new TxnData(); - TxnDataRangeValue value = new TxnDataRangeValue(); - while (iterator.hasNext()) + try (ReadExecutionController controller = read.executionController(); + UnfilteredPartitionIterator partition = read.executeLocally(controller); + PartitionIterator iterator = UnfilteredPartitionIterators.filter(partition, read.nowInSec())) { - try (RowIterator rows = iterator.next()) + TxnData result = new TxnData(); + TxnDataRangeValue value = new TxnDataRangeValue(); + while (iterator.hasNext()) { - FilteredPartition filtered = FilteredPartition.create(rows); - if (filtered.hasRows() || read.selectsFullPartition()) + try (RowIterator rows = iterator.next()) { - value.add(filtered); + FilteredPartition filtered = FilteredPartition.create(rows); + if (filtered.hasRows() || read.selectsFullPartition()) + { + value.add(filtered); + } } } + result.put(TxnData.txnDataName(TxnDataNameKind.USER), value); + return result; } - result.put(TxnData.txnDataName(TxnDataNameKind.USER), value); - return result; + } + + @Override + public String toString() + { + return command.toCQLString(); } }; + return submit(executor, callable, callable); + } - return AsyncChains.ofCallable(Stage.READ.executor(), readCallable, (callable, receiver) -> - new DebuggableTask.RunnableDebuggableTask() - { - private final long approxCreationTimeNanos = MonotonicClock.Global.approxTime.now(); - private volatile long approxStartTimeNanos; - - @Override - public void run() - { - approxStartTimeNanos = MonotonicClock.Global.approxTime.now(); - - try - { - Data call = callable.call(); - receiver.accept(call, null); - } - catch (Throwable t) - { - logger.debug("AsyncChain Callable threw an Exception", t); - receiver.accept(null, t); - } - } - - @Override - public long creationTimeNanos() - { - return approxCreationTimeNanos; - } - - @Override - public long startTimeNanos() - { - return approxStartTimeNanos; - } - - @Override - public String description() - { - return command.toCQLString(); - } - } - ); + private AsyncChain submit(AccordExecutor executor, Callable readCallable, Object describe) + { + return executor.buildDebuggable(readCallable, describe); } static final ParameterisedVersionedSerializer serializer = new ParameterisedVersionedSerializer<>() diff --git a/src/java/org/apache/cassandra/service/accord/txn/TxnRead.java b/src/java/org/apache/cassandra/service/accord/txn/TxnRead.java index d19c241614..20815d1bcf 100644 --- a/src/java/org/apache/cassandra/service/accord/txn/TxnRead.java +++ b/src/java/org/apache/cassandra/service/accord/txn/TxnRead.java @@ -28,9 +28,9 @@ import javax.annotation.Nullable; import com.google.common.collect.ImmutableList; import accord.api.Data; -import accord.api.DataStore; import accord.api.Key; import accord.api.Read; +import accord.local.CommandStore; import accord.local.SafeCommandStore; import accord.primitives.Keys; import accord.primitives.Participants; @@ -40,7 +40,6 @@ import accord.primitives.Routable.Domain; import accord.primitives.Seekable; import accord.primitives.Seekables; import accord.primitives.Timestamp; -import accord.utils.Invariants; import accord.utils.UnhandledEnum; import accord.utils.async.AsyncChain; import accord.utils.async.AsyncChains; @@ -53,16 +52,17 @@ import org.apache.cassandra.dht.AbstractBounds; import org.apache.cassandra.io.ParameterisedVersionedSerializer; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; +import org.apache.cassandra.service.accord.AccordCommandStore; +import org.apache.cassandra.service.accord.AccordExecutor; import org.apache.cassandra.service.accord.api.PartitionKey; import org.apache.cassandra.service.accord.serializers.TableMetadatas; import org.apache.cassandra.service.accord.serializers.TableMetadatasAndKeys; import org.apache.cassandra.service.accord.serializers.Version; -import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.utils.ObjectSizes; import static accord.primitives.Routables.Slice.Minimal; +import static accord.utils.Invariants.require; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; import static org.apache.cassandra.service.accord.AccordSerializers.consistencyLevelSerializer; import static org.apache.cassandra.service.accord.IAccordService.SUPPORTED_READ_CONSISTENCY_LEVELS; import static org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind.CAS_READ; @@ -124,7 +124,7 @@ public class TxnRead extends AbstractKeySorted implements Read // TODO (expected): relax this condition, require only that it holds for each equal byte[] // right now this means we don't permit two different range queries in the same transaction touching adjacent ranges // this is a pretty weak restriction and doesn't interfere with current CQL capabilities, but should be addressed eventually - Invariants.require(domain == Domain.Key || ((Ranges)keys()).mergeTouching() == keys()); + require(domain == Domain.Key || ((Ranges)keys()).mergeTouching() == keys()); } private TxnRead(TableMetadatas tables, @Nonnull List items, @Nullable ConsistencyLevel cassandraConsistencyLevel) @@ -134,14 +134,13 @@ public class TxnRead extends AbstractKeySorted implements Read checkArgument(cassandraConsistencyLevel == null || SUPPORTED_READ_CONSISTENCY_LEVELS.contains(cassandraConsistencyLevel), "Unsupported consistency level for read: %s", cassandraConsistencyLevel); this.cassandraConsistencyLevel = cassandraConsistencyLevel; this.domain = items.get(0).key().domain(); - Invariants.require(domain == Domain.Key || ((Ranges)keys()).mergeTouching() == keys()); + require(domain == Domain.Key || ((Ranges)keys()).mergeTouching() == keys()); } private static void sortReads(List reads) { - if (reads.size() == 0) - return; - reads.sort(TXN_NAMED_READ_KEY_COMPARATOR); + if (reads.size() > 1) + reads.sort(TXN_NAMED_READ_KEY_COMPARATOR); } public static TxnRead createTxnRead(TableMetadatas tables, @Nonnull List items, @Nullable ConsistencyLevel consistencyLevel, Domain domain) @@ -360,7 +359,7 @@ public class TxnRead extends AbstractKeySorted implements Read } else { - Invariants.require(c == 0); + require(c == 0); pending = pending.merge(add); } } @@ -380,22 +379,26 @@ public class TxnRead extends AbstractKeySorted implements Read } @Override - public AsyncChain read(Seekable key, SafeCommandStore safeStore, Timestamp executeAt, DataStore store) + public AsyncChain read(SafeCommandStore safeStore, Seekable key, Timestamp executeAt) { - // Set to null since we don't need it and interop can pass in null - safeStore = null; - ClusterMetadata cm = ClusterMetadata.current(); - checkState(cm.epoch.getEpoch() >= executeAt.epoch(), "TCM epoch %d is < executeAt epoch %d", cm.epoch.getEpoch(), executeAt.epoch()); + return readDirect(safeStore.commandStore(), key, executeAt); + } - List> results = new ArrayList<>(); - forEachWithKey(key, read -> results.add(read.read(tables, cassandraConsistencyLevel, key, executeAt))); - - if (results.isEmpty()) - // Result type must match everywhere + @Override + public AsyncChain readDirect(CommandStore commandStore, Seekable key, Timestamp executeAt) + { + if (key == null) return AsyncChains.success(new TxnData()); - if (results.size() == 1) - return results.get(0); + AccordExecutor executor = ((AccordCommandStore)commandStore).executor(); + if (items.length == 1 && key.equals(items[0].key())) + return items[0].read(executor, tables, cassandraConsistencyLevel, key, executeAt); + + List> results = new ArrayList<>(); + forEachWithKey(key, read -> results.add(read.read(executor, tables, cassandraConsistencyLevel, key, executeAt))); + + if (results.isEmpty()) + return AsyncChains.success(new TxnData()); return AsyncChains.reduce(results, Data::merge); } diff --git a/src/java/org/apache/cassandra/service/accord/txn/TxnWrite.java b/src/java/org/apache/cassandra/service/accord/txn/TxnWrite.java index 1df846ed2c..1560247114 100644 --- a/src/java/org/apache/cassandra/service/accord/txn/TxnWrite.java +++ b/src/java/org/apache/cassandra/service/accord/txn/TxnWrite.java @@ -27,13 +27,14 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Executor; import com.google.common.collect.Iterables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import accord.api.DataStore; import accord.api.Write; +import accord.local.CommandStore; import accord.local.SafeCommandStore; import accord.primitives.PartialTxn; import accord.primitives.Routable.Domain; @@ -45,7 +46,6 @@ import accord.primitives.TxnId; import accord.primitives.Writes; import accord.utils.async.AsyncChain; import accord.utils.async.AsyncChains; -import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.cql3.UpdateParameters; import org.apache.cassandra.db.Clustering; import org.apache.cassandra.db.Columns; @@ -62,6 +62,8 @@ import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.schema.ColumnMetadata; +import org.apache.cassandra.service.accord.AccordCommandStore; +import org.apache.cassandra.service.accord.AccordExecutor; import org.apache.cassandra.service.accord.api.PartitionKey; import org.apache.cassandra.service.accord.serializers.TableMetadatas; import org.apache.cassandra.service.accord.serializers.TableMetadatasAndKeys; @@ -135,13 +137,13 @@ public class TxnWrite extends AbstractKeySorted implements Writ '}'; } - public AsyncChain write(TableMetadatas tables, boolean preserveTimestamps, long timestamp) + public AsyncChain write(Executor executor, TableMetadatas tables, boolean preserveTimestamps, long timestamp) { PartitionUpdate update = deserialize(tables); if (!preserveTimestamps) update = new PartitionUpdate.Builder(update, 0).updateAllTimestamp(timestamp).build(); Mutation mutation = new Mutation(update, PotentialTxnConflicts.ALLOW); - return AsyncChains.ofRunnable(Stage.MUTATION.executor(), mutation::applyUnsafe); + return AsyncChains.ofRunnable(executor, mutation::applyUnsafe); } @Override @@ -424,7 +426,13 @@ public class TxnWrite extends AbstractKeySorted implements Writ } @Override - public AsyncChain apply(Seekable key, SafeCommandStore safeStore, TxnId txnId, Timestamp executeAt, DataStore store, PartialTxn txn) + public AsyncChain apply(SafeCommandStore safeStore, Seekable key, TxnId txnId, Timestamp executeAt, PartialTxn txn) + { + return applyDirect(safeStore.commandStore(), key, txnId, executeAt, txn); + } + + @Override + public AsyncChain applyDirect(CommandStore commandStore, Seekable key, TxnId txnId, Timestamp executeAt, PartialTxn txn) { // UnrecoverableRepairUpdate will deserialize as null at other nodes // Accord should skip the Update for a read transaction, but handle it here anyways @@ -438,15 +446,16 @@ public class TxnWrite extends AbstractKeySorted implements Writ List> results = new ArrayList<>(); if (isConditionMet) { + AccordExecutor executor = ((AccordCommandStore) commandStore).executor(); boolean preserveTimestamps = txnUpdate.preserveTimestamps(); // Apply updates not specified fully by the client but built from fragments completed by data from reads. // This occurs, for example, when an UPDATE statement uses a value assigned by a LET statement. - forEachWithKey(key, write -> results.add(write.write(tables, preserveTimestamps, timestamp))); + forEachWithKey(key, write -> results.add(write.write(executor, tables, preserveTimestamps, timestamp))); // Apply updates that are fully specified by the client and not reliant on data from reads. // ex. INSERT INTO tbl (a, b, c) VALUES (1, 2, 3) // These updates are persisted only in TxnUpdate and not in TxnWrite to avoid duplication. List updates = txnUpdate.completeUpdatesForKey((RoutableKey) key); - updates.forEach(write -> results.add(write.write(tables, preserveTimestamps, timestamp))); + updates.forEach(write -> results.add(write.write(executor, tables, preserveTimestamps, timestamp))); } if (results.isEmpty()) diff --git a/test/distributed/org/apache/cassandra/distributed/test/QueriesTableTest.java b/test/distributed/org/apache/cassandra/distributed/test/QueriesTableTest.java index e0805620a7..d30220f631 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/QueriesTableTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/QueriesTableTest.java @@ -26,7 +26,6 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import accord.impl.progresslog.DefaultProgressLogs; import com.datastax.driver.core.ConsistencyLevel; import com.datastax.driver.core.Session; import com.datastax.driver.core.SimpleStatement; @@ -42,7 +41,6 @@ import org.apache.cassandra.db.ReadExecutionController; import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.Feature; -import org.apache.cassandra.distributed.api.IInvokableInstance; import org.apache.cassandra.distributed.api.Row; import org.apache.cassandra.distributed.api.SimpleQueryResult; import org.apache.cassandra.service.consensus.TransactionalMode; @@ -67,7 +65,6 @@ public class QueriesTableTest extends TestBaseImpl SHARED_CLUSTER = init(Cluster.build(1).withInstanceInitializer(QueryDelayHelper::install) .withConfig(c -> c.with(Feature.NATIVE_PROTOCOL, Feature.GOSSIP) .set("write_request_timeout", "10s")).start()); - DRIVER_CLUSTER = JavaDriverUtils.create(SHARED_CLUSTER); SESSION = DRIVER_CLUSTER.connect(); } @@ -178,10 +175,6 @@ public class QueriesTableTest extends TestBaseImpl { SHARED_CLUSTER.schemaChange("CREATE TABLE " + KEYSPACE + ".accord_tbl (k int primary key, v int) WITH " + TransactionalMode.mixed_reads.asCqlParam()); - // Disable recovery to make sure only one local read occurs: - for (IInvokableInstance instance : SHARED_CLUSTER) - instance.runOnInstance(() -> DefaultProgressLogs.unsafePauseForTesting(true)); - String update = "BEGIN TRANSACTION\n" + " LET row1 = (SELECT * FROM " + KEYSPACE + ".accord_tbl WHERE k = 0);\n" + " SELECT row1.k, row1.v;\n" + @@ -220,7 +213,7 @@ public class QueriesTableTest extends TestBaseImpl String threadId = row.get("thread_id").toString(); String task = row.get("task").toString(); - readVisible |= threadId.contains("Read") && task.contains("SELECT"); + readVisible |= (threadId.contains("AccordExecutor") || threadId.contains("Read")) && task.contains("SELECT"); coordinatorTxnVisible |= threadId.contains("Native-Transport-Requests") && task.contains("BEGIN TRANSACTION"); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordLoadTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordLoadTest.java index 83bf2d295b..15c195f475 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordLoadTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordLoadTest.java @@ -166,10 +166,18 @@ public class AccordLoadTest extends AccordTestBase } catch (RejectedExecutionException e) { - int index = 1 + random.nextInt(cluster.size()); - logger.info("Picking new coordinator ... {}", index); - coordinator = cluster.coordinator(index); inFlight.release(); + while (true) + { + try + { + int index = 1 + random.nextInt(cluster.size()); + logger.info("Picking new coordinator ... {}", index); + coordinator = cluster.coordinator(index); + break; + } + catch (Throwable t) { logger.info("Failed to select coordinator", t); } + } } batchSize++; if (System.nanoTime() >= batchEnd) diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMetricsTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMetricsTest.java index f0f83cb036..2be7826c5e 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMetricsTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMetricsTest.java @@ -32,6 +32,9 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import accord.api.ProtocolModifiers; +import accord.primitives.TxnId.FastPath; +import accord.primitives.TxnId.FastPaths; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.virtual.AccordDebugKeyspace; import org.apache.cassandra.distributed.api.ConsistencyLevel; @@ -72,7 +75,10 @@ public class AccordMetricsTest extends AccordTestBase public static void setupClass() throws IOException { AccordTestBase.setupCluster(Function.identity(), 2); - SHARED_CLUSTER.forEach(node -> node.runOnInstance(() -> AccordService.instance().setCacheSize(0))); + SHARED_CLUSTER.forEach(node -> node.runOnInstance(() -> { + AccordService.instance().setCacheSize(0); + ProtocolModifiers.Toggles.setPermittedFastPaths(new FastPaths(FastPath.Unoptimised)); + })); for (int i = 0; i < SHARED_CLUSTER.size(); i++) // initialize metrics logger.trace(SHARED_CLUSTER.get(i + 1).callOnInstance(() -> AccordMetrics.readMetrics.toString() + AccordMetrics.writeMetrics.toString())); } @@ -120,8 +126,8 @@ public class AccordMetricsTest extends AccordTestBase SHARED_CLUSTER.coordinator(1).executeWithResult(readCql(), ConsistencyLevel.ALL, 0, 0, 1, 1); assertCoordinatorMetrics(0, "ro", 1, 0, 0, 0, 0); assertCoordinatorMetrics(1, "ro", 0, 0, 0, 0, 0); - assertReplicaMetrics(0, "ro", 1, 1, 0); - assertReplicaMetrics(1, "ro", 1, 1, 0); + assertReplicaMetrics(0, "ro", 0, 1, 1); + assertReplicaMetrics(1, "ro", 0, 1, 1); assertZeroMetrics("rw"); } @@ -285,9 +291,9 @@ public class AccordMetricsTest extends AccordTestBase Map metrics = diff(countingMetrics0).get(node); Function metric = n -> metrics.get(nameFactory.createMetricName(n).getMetricName()); assertThat(metric.apply(AccordMetrics.STABLE_LATENCY)).isLessThanOrEqualTo(stable); - assertThat(metric.apply(AccordMetrics.EXECUTE_LATENCY)).isEqualTo(executions); + assertThat(metric.apply(AccordMetrics.PREAPPLY_LATENCY)).isEqualTo(executions); assertThat(metric.apply(AccordMetrics.APPLY_LATENCY)).isEqualTo(applications); - assertThat(metric.apply(AccordMetrics.APPLY_DURATION)).isEqualTo(applications); + assertThat(metric.apply(AccordMetrics.APPLY_DURATION)).isEqualTo(scope.equals("rw") ? applications : 0); assertThat(metric.apply(AccordMetrics.PARTIAL_DEPENDENCIES)).isEqualTo(executions); // Verify that replica metrics are published to the appropriate virtual table: diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordReadInteroperabilityTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordReadInteroperabilityTest.java index a66c79ad88..e8efed6957 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordReadInteroperabilityTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordReadInteroperabilityTest.java @@ -172,8 +172,7 @@ public class AccordReadInteroperabilityTest extends AccordTestBase assertEquals(0, messageCount(Verb.ACCORD_INTEROP_STABLE_THEN_READ_REQ)); assertEquals(0, messageCount(Verb.ACCORD_INTEROP_READ_REQ)); assertEquals(0, messageCount(Verb.ACCORD_INTEROP_READ_RSP)); - // Durability scheduling creates a lot of background commits that generate read responses - assertTrue(messageCount(Verb.ACCORD_READ_RSP) > 0); + assertTrue(messageCount(Verb.ACCORD_GET_EPHMRL_READ_DEPS_REQ) > 0); } }); } diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionAccordIteratorsTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionAccordIteratorsTest.java index 0d6d11216f..f458ac8461 100644 --- a/test/unit/org/apache/cassandra/db/compaction/CompactionAccordIteratorsTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/CompactionAccordIteratorsTest.java @@ -38,7 +38,6 @@ import org.slf4j.LoggerFactory; import accord.api.Agent; import accord.api.Key; -import accord.api.Result; import accord.local.CheckedCommands; import accord.local.Command; import accord.local.CommandStore; @@ -60,7 +59,6 @@ import accord.primitives.Status; import accord.primitives.Txn; import accord.primitives.Txn.Kind; import accord.primitives.TxnId; -import accord.primitives.Writes; import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.cql3.QueryProcessor; @@ -88,7 +86,6 @@ import org.apache.cassandra.service.accord.AccordTestUtils; import org.apache.cassandra.service.accord.IAccordService; import org.apache.cassandra.service.accord.api.TokenKey; import org.apache.cassandra.utils.FBUtilities; -import org.apache.cassandra.utils.Pair; import static accord.local.KeyHistory.SYNC; import static accord.local.PreLoadContext.contextFor; @@ -281,15 +278,12 @@ public class CompactionAccordIteratorsTest private static void flush(AccordCommandStore commandStore) { - commandStore.executeBlocking(() -> { - // clear cache and wait for post-eviction writes to complete - try (AccordExecutor.ExclusiveGlobalCaches cache = commandStore.executor().lockCaches();) - { - long cacheSize = cache.global.capacity(); - cache.global.setCapacity(0); - cache.global.setCapacity(cacheSize); - } - }); + try (AccordExecutor.ExclusiveGlobalCaches cache = commandStore.executor().lockCaches();) + { + long cacheSize = cache.global.capacity(); + cache.global.setCapacity(0); + cache.global.setCapacity(cacheSize); + } commandsForKey.forceBlockingFlush(FlushReason.UNIT_TESTS); while (commandStore.executor().hasTasks()) LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); @@ -331,10 +325,11 @@ public class CompactionAccordIteratorsTest CheckedCommands.commit(safe, SaveStatus.Stable, Ballot.ZERO, txnId, route, partialTxn, txnId, partialDeps, (a, b) -> {}); }).beginAsResult()); flush(commandStore); - getUninterruptibly(commandStore.execute(contextFor(txnId, route, SYNC), safe -> { - Pair result = AccordTestUtils.processTxnResultDirect(safe, txnId, partialTxn, txnId); + getUninterruptibly(commandStore.submit(contextFor(txnId, route, SYNC), safe -> { + return AccordTestUtils.processTxnResultDirect(safe, txnId, partialTxn, txnId); + }).flatMap(i -> i).flatMap(result -> commandStore.execute(contextFor(txnId, route, SYNC), safe -> { CheckedCommands.apply(safe, txnId, route, txnId, partialDeps, partialTxn, result.left, result.right, (a, b) -> {}); - }).beginAsResult()); + }))); flush(commandStore); // The apply chain is asychronous, so it is easiest to just spin until it is applied // in order to have the updated state in the system table diff --git a/test/unit/org/apache/cassandra/db/virtual/AccordDebugKeyspaceTest.java b/test/unit/org/apache/cassandra/db/virtual/AccordDebugKeyspaceTest.java index fec005868d..a2677f3160 100644 --- a/test/unit/org/apache/cassandra/db/virtual/AccordDebugKeyspaceTest.java +++ b/test/unit/org/apache/cassandra/db/virtual/AccordDebugKeyspaceTest.java @@ -237,6 +237,7 @@ public class AccordDebugKeyspaceTest extends CQLTester case ACCORD_ACCEPT_RSP: case ACCORD_CHECK_STATUS_REQ: case ACCORD_CHECK_STATUS_RSP: + case ACCORD_READ_REQ: case ACCORD_READ_RSP: case ACCORD_AWAIT_REQ: case ACCORD_AWAIT_RSP: diff --git a/test/unit/org/apache/cassandra/index/accord/RouteIndexTest.java b/test/unit/org/apache/cassandra/index/accord/RouteIndexTest.java index 963ee402c8..3ac830611e 100644 --- a/test/unit/org/apache/cassandra/index/accord/RouteIndexTest.java +++ b/test/unit/org/apache/cassandra/index/accord/RouteIndexTest.java @@ -171,7 +171,7 @@ public class RouteIndexTest extends CQLTester Domain domain = state.domainGen.next(rs); TxnId txnId = state.nextTxnId(domain); Route route = createRoute(state, rs, domain, rs.nextInt(1, 20)); - int storeId = state.accordService.node().commandStores().select(route.homeKey()).id(); + int storeId = state.accordService.node().commandStores().unsafeForKey(route.homeKey()).id(); return new InsertTxn(storeId, txnId, SaveStatus.PreAccepted, route); } diff --git a/test/unit/org/apache/cassandra/service/accord/AccordCommandStoreTest.java b/test/unit/org/apache/cassandra/service/accord/AccordCommandStoreTest.java index 6faca72d34..f102b169d8 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordCommandStoreTest.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordCommandStoreTest.java @@ -49,6 +49,7 @@ import accord.primitives.TxnId; import accord.primitives.Writes; import accord.utils.ImmutableBitSet; import accord.utils.SimpleBitSet; +import accord.utils.async.AsyncChains; import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.db.ColumnFamilyStore; @@ -130,7 +131,7 @@ public class AccordCommandStoreTest SimpleBitSet waitingOnApply = new SimpleBitSet(3); waitingOnApply.set(1); Command.WaitingOn waitingOn = new Command.WaitingOn(dependencies.keyDeps.keys(), dependencies.rangeDeps, new ImmutableBitSet(waitingOnApply), new ImmutableBitSet(2)); - Pair result = AccordTestUtils.processTxnResult(commandStore, txnId, txn, executeAt); + Pair result = AsyncChains.getBlocking(AccordTestUtils.processTxnResult(commandStore, txnId, txn, executeAt)); Command expected = Command.Executed.executed(txnId, SaveStatus.Applied, Majority, StoreParticipants.all(route), promised, executeAt, txn, dependencies, accepted, diff --git a/test/unit/org/apache/cassandra/service/accord/AccordMessageSinkTest.java b/test/unit/org/apache/cassandra/service/accord/AccordMessageSinkTest.java index 62196013a9..061eab4d6f 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordMessageSinkTest.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordMessageSinkTest.java @@ -95,7 +95,7 @@ public class AccordMessageSinkTest public void txnRead() { TxnId txnId = nextTxnId(42, Txn.Kind.Read, Routable.Domain.Key); - Request request = new ReadTxnData(node, topologies, txnId, topology.ranges(), txnId.epoch()); + Request request = new ReadTxnData(node, topologies, txnId, topology.ranges(), null, null, txnId.epoch()); checkRequestReplies(request, new ReadData.ReadOk(null, null, 0), CommitOrReadNack.Insufficient); diff --git a/test/unit/org/apache/cassandra/service/accord/AccordTaskTest.java b/test/unit/org/apache/cassandra/service/accord/AccordTaskTest.java index 9c6dc1360c..f5f36b4b2c 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordTaskTest.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordTaskTest.java @@ -207,14 +207,12 @@ public class AccordTaskTest }).beginAsResult()); // clear cache - commandStore.executeBlocking(() -> { - try (ExclusiveGlobalCaches cache = commandStore.executor().lockCaches();) - { - long cacheSize = cache.global.capacity(); - cache.global.setCapacity(0); - cache.global.setCapacity(cacheSize); - } - }); + try (ExclusiveGlobalCaches cache = commandStore.executor().lockCaches();) + { + long cacheSize = cache.global.capacity(); + cache.global.setCapacity(0); + cache.global.setCapacity(cacheSize); + } while (commandStore.executor().hasTasks()) LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); @@ -261,14 +259,12 @@ public class AccordTaskTest }).beginAsResult()); // clear cache - commandStore.executeBlocking(() -> { - try (ExclusiveGlobalCaches cache = commandStore.executor().lockCaches();) - { - long cacheSize = cache.global.capacity(); - cache.global.setCapacity(0); - cache.global.setCapacity(cacheSize); - } - }); + try (ExclusiveGlobalCaches cache = commandStore.executor().lockCaches();) + { + long cacheSize = cache.global.capacity(); + cache.global.setCapacity(0); + cache.global.setCapacity(cacheSize); + } while (commandStore.executor().hasTasks()) LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); @@ -288,7 +284,10 @@ public class AccordTaskTest // all txn use the same key; 0 Keys keys = keys(Schema.instance.getTableMetadata("ks", "tbl"), 0); AccordCommandStore commandStore = createAccordCommandStore(clock::incrementAndGet, "ks", "tbl"); - commandStore.executeBlocking(() -> commandStore.executor().cacheUnsafe().setCapacity(0)); + try (AccordExecutor.ExclusiveGlobalCaches cache = commandStore.executor().lockCaches();) + { + cache.global.setCapacity(0); + } Gen txnIdGen = rs -> txnId(1, clock.incrementAndGet(), 1); qt().withSeed(3447647345436261108L).withPure(false) diff --git a/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java b/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java index 397bc8fc28..10afacf74c 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java @@ -34,6 +34,7 @@ import java.util.stream.IntStream; import com.google.common.collect.Sets; import org.junit.Assert; +import accord.api.AsyncExecutor; import accord.api.Data; import accord.api.Journal; import accord.api.ProgressLog.NoOpProgressLog; @@ -52,6 +53,7 @@ import accord.local.Node.Id; import accord.local.NodeCommandStoreService; import accord.local.PreLoadContext; import accord.local.SafeCommandStore; +import accord.local.SequentialAsyncExecutor; import accord.local.StoreParticipants; import accord.local.TimeService; import accord.local.durability.DurabilityService; @@ -74,6 +76,7 @@ import accord.topology.Shard; import accord.topology.Topology; import accord.topology.TopologyManager; import accord.utils.SortedArrays.SortedArrayList; +import accord.utils.async.AsyncChain; import accord.utils.async.AsyncChains; import org.apache.cassandra.ServerTestUtils; import org.apache.cassandra.concurrent.ExecutorPlus; @@ -210,35 +213,24 @@ public class AccordTestUtils return Ballot.fromValues(epoch, hlc, new Node.Id(node)); } - public static Pair processTxnResult(AccordCommandStore commandStore, TxnId txnId, PartialTxn txn, Timestamp executeAt) throws Throwable + public static AsyncChain> processTxnResult(AccordCommandStore commandStore, TxnId txnId, PartialTxn txn, Timestamp executeAt) throws Throwable { - AtomicReference> result = new AtomicReference<>(); + AtomicReference>> result = new AtomicReference<>(); getUninterruptibly(commandStore.execute(PreLoadContext.contextFor(txn.keys().toParticipants()), safeStore -> result.set(processTxnResultDirect(safeStore, txnId, txn, executeAt)))); return result.get(); } - public static Pair processTxnResultDirect(SafeCommandStore safeStore, TxnId txnId, PartialTxn txn, Timestamp executeAt) + public static AsyncChain> processTxnResultDirect(SafeCommandStore safeStore, TxnId txnId, PartialTxn txn, Timestamp executeAt) { TxnRead read = (TxnRead) txn.read(); - Data readData = read.keys().stream().map(key -> { - try - { - return AsyncChains.getBlocking(read.read(key, safeStore, executeAt, null)); - } - catch (InterruptedException e) - { - throw new UncheckedInterruptedException(e); - } - catch (ExecutionException e) - { - throw new RuntimeException(e); - } - }) - .reduce(null, TxnData::merge); - return Pair.create(txnId.is(Write) ? txn.execute(txnId, executeAt, readData) : null, - txn.query().compute(txnId, executeAt, txn.keys(), readData, txn.read(), txn.update())); - + return AsyncChains.allOf(read.keys().stream().map(key -> read.read(safeStore, key, executeAt)) + .collect(Collectors.toList())) + .map(list -> { + Data data = list.stream().reduce(Data::merge).orElse(new TxnData()); + return Pair.create(txnId.is(Write) ? txn.execute(txnId, executeAt, data) : null, + txn.query().compute(txnId, executeAt, txn.keys(), data, txn.read(), txn.update())); + }); } public static String wrapInTxn(String query) @@ -342,7 +334,20 @@ public class AccordTestUtils { NodeCommandStoreService time = new NodeCommandStoreService() { + @Override + public AsyncExecutor someExecutor() + { + return null; + } + + @Override + public SequentialAsyncExecutor someSequentialExecutor() + { + return null; + } + private ToLongFunction elapsed = TimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.MICROSECONDS, this::now); + private long stamp = 0; @Override public Timeouts timeouts() { return null; } @Override public DurableBefore durableBefore() { return DurableBefore.EMPTY; } @@ -353,6 +358,8 @@ public class AccordTestUtils @Override public long uniqueNow(long atLeast) { return now.getAsLong(); } @Override public long elapsed(TimeUnit timeUnit) { return elapsed.applyAsLong(timeUnit); } @Override public TopologyManager topology() { throw new UnsupportedOperationException(); } + @Override public long currentStamp() { return stamp; } + @Override public void updateStamp() {++stamp;} }; AccordAgent agent = new AccordAgent(); diff --git a/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStore.java b/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStore.java index ef7e92c6ea..f47f56a9b0 100644 --- a/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStore.java +++ b/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStore.java @@ -31,6 +31,7 @@ import java.util.function.Predicate; import java.util.function.ToLongFunction; import javax.annotation.Nullable; +import accord.api.AsyncExecutor; import accord.api.Journal; import accord.api.LocalListeners; import accord.api.ProgressLog; @@ -52,6 +53,7 @@ import accord.local.NodeCommandStoreService; import accord.local.PreLoadContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; +import accord.local.SequentialAsyncExecutor; import accord.local.TimeService; import accord.local.durability.DurabilityService; import accord.messages.BeginRecovery; @@ -164,8 +166,21 @@ public class SimulatedAccordCommandStore implements AutoCloseable this.storeService = new NodeCommandStoreService() { + @Override + public AsyncExecutor someExecutor() + { + return null; + } + + @Override + public SequentialAsyncExecutor someSequentialExecutor() + { + return null; + } + private final ToLongFunction elapsed = TimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.NANOSECONDS, this::now); final Timeouts timeouts = new DefaultTimeouts(this); + long stamp; @Override public Timeouts timeouts() { return timeouts; } @@ -215,6 +230,18 @@ public class SimulatedAccordCommandStore implements AutoCloseable { throw new UnsupportedOperationException(); } + + @Override + public long currentStamp() + { + return stamp; + } + + @Override + public void updateStamp() + { + ++stamp; + } }; TestAgent.RethrowAgent agent = new TestAgent.RethrowAgent() diff --git a/test/unit/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializerTest.java b/test/unit/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializerTest.java index dcea11348d..03c327bca8 100644 --- a/test/unit/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializerTest.java +++ b/test/unit/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializerTest.java @@ -46,6 +46,7 @@ import org.junit.BeforeClass; import org.junit.Test; import accord.api.Agent; +import accord.api.AsyncExecutor; import accord.api.DataStore; import accord.api.Journal; import accord.api.Key; @@ -65,6 +66,7 @@ import accord.local.NodeCommandStoreService; import accord.local.PreLoadContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; +import accord.local.SequentialAsyncExecutor; import accord.local.StoreParticipants; import accord.local.TimeService; import accord.local.cfk.CommandsForKey; @@ -244,7 +246,6 @@ public class CommandsForKeySerializerTest return Command.Committed.committed(builder(), saveStatus); case PreApplied: - case Applying: case Applied: return Command.Executed.executed(builder(), saveStatus); @@ -423,7 +424,7 @@ public class CommandsForKeySerializerTest @Test public void serde() { - testOne(629993588068216851L); + testOne(-4567266914751633833L); Random random = new Random(); for (int i = 0 ; i < 10000 ; ++i) { @@ -479,7 +480,7 @@ public class CommandsForKeySerializerTest } } - Choices saveStatusChoices = Choices.uniform(EnumSet.complementOf(EnumSet.of(SaveStatus.TruncatedApply, SaveStatus.TruncatedUnapplied, SaveStatus.TruncatedApplyWithOutcome)).toArray(SaveStatus[]::new)); + Choices saveStatusChoices = Choices.uniform(EnumSet.complementOf(EnumSet.of(SaveStatus.Applying, SaveStatus.TruncatedApply, SaveStatus.TruncatedUnapplied, SaveStatus.TruncatedApplyWithOutcome)).toArray(SaveStatus[]::new)); Supplier saveStatusSupplier = () -> { SaveStatus result = saveStatusChoices.choose(source); while (result.is(Status.Truncated)) // we don't currently process truncations @@ -697,6 +698,8 @@ public class CommandsForKeySerializerTest @Override public ProgressLog progressLog() { return null; } @Override public NodeCommandStoreService node() { return new NodeCommandStoreService() { + @Override public AsyncExecutor someExecutor() { throw new UnsupportedOperationException(); } + @Override public SequentialAsyncExecutor someSequentialExecutor() { throw new UnsupportedOperationException(); } @Override public long epoch() { return 0;} @Override public Node.Id id() { return Node.Id.NONE; } @Override public Timeouts timeouts() { return null; } @@ -704,6 +707,8 @@ public class CommandsForKeySerializerTest @Override public DurabilityService durability() { return null; } @Override public long uniqueNow(long atLeast) { return 0; } @Override public TopologyManager topology() { return null; } + @Override public long currentStamp() { return 0; } + @Override public void updateStamp() { throw new UnsupportedOperationException(); } @Override public long now() { return 0; } @Override public long elapsed(TimeUnit unit) { return 0; } }; }