- 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
This commit is contained in:
Benedict Elliott Smith 2025-04-13 14:41:34 +01:00
parent 8d641e06fc
commit 6d303e6843
64 changed files with 1938 additions and 1255 deletions

@ -1 +1 @@
Subproject commit 07ba9c85d083b586ff5c2382ef747b0fe71a9c47
Subproject commit 154c05f50dcfe9cb69d14e4a67a137ecd0600e88

View File

@ -42,9 +42,14 @@ public interface DebuggableTask
public long startTimeNanos();
public String description();
interface RunnableDebuggableTask extends Runnable, DebuggableTask {}
interface CallableDebuggableTask<T> extends Callable<T>, DebuggableTask {}
interface DebuggableTaskRunner
{
DebuggableTask running();
String id();
}
/**
* Wraps a {@link DebuggableTask} to include the name of the thread running it.

View File

@ -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<SEPWorker.Work> implements Runnable
final class SEPWorker extends AtomicReference<SEPWorker.Work> 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<SEPWorker.Work> 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<SEPWorker.Work> 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()
{
/*

View File

@ -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<RunningDebuggableTask> runningTasks()
public Stream<? extends DebuggableTaskRunner> workers()
{
return allWorkers.stream()
.map(worker -> new RunningDebuggableTask(worker.toString(), worker.currentDebuggableTask()))
.filter(RunningDebuggableTask::hasTask)
.collect(Collectors.toList());
return allWorkers.stream();
}
void maybeStartSpinningWorker()

View File

@ -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<UnfilteredRowIterator>
{
private final AbortableUnfilteredRowTransformation abortableIter;

View File

@ -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<Row>
@ -442,4 +443,32 @@ public abstract class AbstractBTreePartition implements Partition, Iterable<Row>
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.<Row>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;
}
}

View File

@ -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<Row> 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,

View File

@ -330,7 +330,6 @@ public class BTreeRow extends AbstractRow
if (!mayFilterColumns && !mayHaveShadowed && droppedColumns.isEmpty())
return this;
LivenessInfo newInfo = primaryKeyLivenessInfo;
Deletion newDeletion = deletion;
if (mayHaveShadowed)

View File

@ -286,10 +286,7 @@ public class ComplexColumnData extends ColumnData implements Iterable<Cell<?>>
public long maxTimestamp()
{
long timestamp = complexDeletion.markedForDeleteAt();
for (Cell<?> cell : this)
timestamp = Math.max(timestamp, cell.timestamp());
return timestamp;
return BTree.<Cell>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

View File

@ -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<? extends DebuggableTask.DebuggableTaskRunner> 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;
}
}

View File

@ -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());
}
}

View File

@ -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 ),

View File

@ -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

View File

@ -326,18 +326,32 @@ public class AccordCacheEntry<K, V> extends IntrusiveLinkedListNode
public <P> Loading load(BiFunction<P, Runnable, Cancellable> loadExecutor, P param, Adapter<K, V, ?> 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<AccordTask<?>> 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<K, V> 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);
}

View File

@ -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<TxnId, Command, AccordSafeCommand>.Instance commands;
final AccordCache.Type<RoutingKey, CommandsForKey, AccordSafeCommandsForKey>.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<Void> build(PreLoadContext preLoadContext, Consumer<? super SafeCommandStore> 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);
}
}
}

View File

@ -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())

View File

@ -103,13 +103,14 @@ public class AccordDataStore implements DataStore
List<Future<?>> futures = new ArrayList<>();
for (Map.Entry<TableId, SnapshotBounds> 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())
{

View File

@ -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<AccordTask<?>> waitingToLoad = new TaskQueue<>(WAITING_TO_LOAD);
private final TaskQueue<Task> waitingToRun = new TaskQueue<>(WAITING_TO_RUN);
private final Object2ObjectHashMap<AccordCommandStore, CommandStoreQueue> 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<? extends DebuggableTaskRunner> 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 <T> AsyncChain<T> build(Callable<T> task)
{
return AsyncChains.ofCallable(this, task);
return new AsyncChains.Head<>()
{
@Override
protected Cancellable start(BiConsumer<? super T, Throwable> callback)
{
return submit(new PlainChain<>(task, callback, null));
}
};
}
public <T> AsyncChain<T> buildDebuggable(Callable<T> task, Object describe)
{
return new AsyncChains.Head<>()
{
@Override
protected Cancellable start(BiConsumer<? super T, Throwable> 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 <R> void submit(AccordTask<R> operation)
{
submit(AccordExecutor::loadExclusive, Function.identity(), operation);
submit(AccordExecutor::submitExclusive, Function.identity(), operation);
}
public <R> void cancel(AccordTask<R> task)
@ -474,22 +501,14 @@ public abstract class AccordExecutor implements CacheSize, AccordCacheEntry.OnLo
abstract <P1s, P1a, P2, P3, P4> void submit(QuintConsumer<AccordExecutor, P1s, P2, P3, P4> sync, QuadFunction<P1a, P2, P3, P4, Object> async, P1s p1s, P1a p1a, P2 p2, P3 p3, P4 p4);
private void submitExclusive(AsyncPromise<Void> result, Runnable run, AccordCommandStore commandStore)
{
++tasks;
PlainRunnable task = new PlainRunnable(result, run, commandStore);
task.queuePosition = ++nextPosition;
waitingToRun(task);
}
private void submitExclusive(AsyncPromise<Void> 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<Void> 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<TaskRunner, Task> 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<Task>
private static final AtomicReferenceFieldUpdater<SequentialExecutor, Thread> ownerUpdater = AtomicReferenceFieldUpdater.newUpdater(SequentialExecutor.class, Thread.class, "owner");
public class SequentialExecutor extends TaskQueue<Task> 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 <T> AsyncChain<T> build(Callable<T> task)
{
return new AsyncChains.Head<>()
{
@Override
protected Cancellable start(BiConsumer<? super T, Throwable> 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<T extends Task> extends IntrusivePriorityHeap<T>
@ -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<Void> result;
final Runnable run;
final AccordCommandStore commandStore;
private SubmitPlainRunnable(AsyncPromise<Void> 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<K, V> 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<Void> result;
final Runnable run;
abstract SequentialExecutor executor();
PlainRunnable(AsyncPromise<Void> 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<Void> result;
final Runnable run;
final @Nullable SequentialExecutor executor;
PlainRunnable(AsyncPromise<Void> 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<T> extends Plain
{
final Callable<T> call;
final BiConsumer<? super T, Throwable> callback;
final @Nullable SequentialExecutor executor;
PlainChain(Callable<T> call, BiConsumer<? super T, Throwable> 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<T> extends PlainChain<T> implements DebuggableTask
{
final long createdAtNanos;
long startedAtNanos;
final Object describe;
DebuggableChain(Callable<T> call, BiConsumer<? super T, Throwable> 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;
}
}
}

View File

@ -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 <P1s, P1a, P2, P3, P4> void submitExternal(QuintConsumer<AccordExecutor, P1s, P2, P3, P4> sync, QuadFunction<P1a, P2, P3, P4, Object> async, P1s p1s, P1a p1a, P2 p2, P3 p3, P4 p4);
<P1s, P1a, P2, P3, P4> void submit(QuintConsumer<AccordExecutor, P1s, P2, P3, P4> sync, QuadFunction<P1a, P2, P3, P4, Object> 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<? extends DebuggableTaskRunner> active()
{
return loops().active();
}
@Override

View File

@ -51,6 +51,12 @@ class AccordExecutorAsyncSubmit extends AccordExecutorAbstractSemiSyncSubmit
lock.await();
}
@Override
AccordExecutorLoops loops()
{
return loops;
}
@Override
boolean isInLoop()
{

View File

@ -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<Thread> loops;
private final Long2ObjectHashMap<LoopTask> tasks;
private final AtomicInteger running = new AtomicInteger();
private final Condition terminated = Condition.newOneTimeCondition();
public AccordExecutorLoops(Mode mode, int threads, IntFunction<String> name, Function<Mode, Runnable> loopFactory)
public AccordExecutorLoops(Mode mode, int threads, IntFunction<String> loopName, BiFunction<String, Mode, LoopTask> 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<? extends DebuggableTaskRunner> active()
{
return tasks.values().stream();
}
public boolean isInLoop()
{
Thread thread = Thread.currentThread();

View File

@ -53,6 +53,12 @@ class AccordExecutorSemiSyncSubmit extends AccordExecutorAbstractSemiSyncSubmit
hasWork.await();
}
@Override
AccordExecutorLoops loops()
{
return loops;
}
@Override
boolean isInLoop()
{

View File

@ -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)

View File

@ -64,6 +64,12 @@ class AccordExecutorSyncSubmit extends AccordExecutorAbstractLockLoop
hasWork.await();
}
@Override
AccordExecutorLoops loops()
{
return loops;
}
@Override
boolean isInLoop()
{

View File

@ -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<TokenRange, SessionInfo> copy = ImmutableMap.builder();
for (Map.Entry<TokenRange, SessionInfo> 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<Data> read(Seekable key, SafeCommandStore commandStore, Timestamp executeAt, DataStore store)
public AsyncChain<Data> 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

View File

@ -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());

View File

@ -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());
}
}
}

View File

@ -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<Void> 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<Timestamp> maxConflict(Ranges ranges)
{
return node.commandStores().any().build(() -> CoordinateMaxConflict.maxConflict(node, ranges)).flatMap(i -> i);
return CoordinateMaxConflict.maxConflict(node, ranges);
}
public static <V> V getBlocking(AsyncChain<V> 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<AccordExecutor> executors()
{
return ((AccordCommandStores)node.commandStores()).executors();
}
@Override
public @Nonnull IAccordResult<TxnResult> 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());

View File

@ -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<R> extends Task implements Runnable, Function<SafeCommandStore, R>, Cancellable
public abstract class AccordTask<R> extends SubmittableTask implements Runnable, Function<SafeCommandStore, R>, 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<R> extends AccordTask<R>
{
private final Function<? super SafeCommandStore, R> function;
@ -190,9 +187,11 @@ public abstract class AccordTask<R> extends Task implements Runnable, Function<S
}
private State state = INITIALIZED;
final AccordCommandStore commandStore;
private final PreLoadContext preLoadContext;
private final String loggingId;
private volatile String loggingId;
private static final AtomicLong nextLoggingId = new AtomicLong(Clock.Global.currentTimeMillis());
private static final AtomicReferenceFieldUpdater<AccordTask, String> loggingIdUpdater = AtomicReferenceFieldUpdater.newUpdater(AccordTask.class, String.class, "loggingId");
// TODO (desired): merge all of these maps into one
@Nullable Object2ObjectHashMap<TxnId, AccordSafeCommand> commands;
@ -207,43 +206,40 @@ public abstract class AccordTask<R> extends Task implements Runnable, Function<S
private BiConsumer<? super R, Throwable> callback;
private List<Command> 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<R> extends Task implements Runnable, Function<S
{
Invariants.require(rangeScanner == null || rangeScanner.scanned);
Invariants.require(loading == null && waitingToLoad == null, "WAITING_TO_RUN => 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<R> extends Task implements Runnable, Function<S
return preLoadContext.keys();
}
// TODO (expected): try to execute immediately BUT consider ordering requirements
// esp. with deferred actions on e.g. CommandsForKey (not yet supported but also important for performance)
public AsyncChain<R> chain()
{
return new AsyncChains.Head<>()
@ -360,6 +358,12 @@ public abstract class AccordTask<R> extends Task implements Runnable, Function<S
}
}
@Override
void submitExclusive(AccordExecutor owner)
{
owner.submitExclusive(this);
}
public void setupExclusive()
{
setupInternal(commandStore.cachesExclusive());
@ -634,9 +638,12 @@ public abstract class AccordTask<R> extends Task implements Runnable, Function<S
}
@Override
protected void preRunExclusive()
protected void preRunExclusive(@Nullable AccordExecutor.TaskRunner runner)
{
state(RUNNING);
queued = null;
if (runner != null)
runner.setDebuggable(this);
if (rangeScanner != null)
{
commandsForRanges = rangeScanner.finish(commandStore.cachesExclusive());
@ -651,7 +658,6 @@ public abstract class AccordTask<R> extends Task implements Runnable, Function<S
@Override
public void run()
{
setLoggingIds();
logger.trace("Running {} with state {}", this, state);
AccordSafeCommandStore safeStore = null;
try
@ -708,7 +714,6 @@ public abstract class AccordTask<R> extends Task implements Runnable, Function<S
finally
{
logger.trace("Exiting {}", this);
clearLoggingIds();
}
}
@ -750,8 +755,10 @@ public abstract class AccordTask<R> extends Task implements Runnable, Function<S
}
}
protected void cleanupExclusive()
protected void cleanupExclusive(@Nullable AccordExecutor.TaskRunner runner)
{
if (runner != null)
runner.clearDebuggable();
releaseResources(commandStore.cachesExclusive());
if (state == FAILING)
state(FAILED);
@ -1084,7 +1091,7 @@ public abstract class AccordTask<R> extends Task implements Runnable, Function<S
void startInternal(Caches caches)
{
summaryLoader = commandStore.diskCommandsForRanges().loader(preLoadContext.primaryTxnId(), preLoadContext.keyHistory(), keysOrRanges);
summaryLoader = commandStore.commandsForRanges().loader(preLoadContext.primaryTxnId(), preLoadContext.keyHistory(), keysOrRanges);
summaryLoader.forEachInCache(summary -> summaries.put(summary.txnId, summary), caches);
caches.commands().register(commandWatcher);
}
@ -1113,6 +1120,35 @@ public abstract class AccordTask<R> extends Task implements Runnable, Function<S
caches.commands().unregister(commandWatcher);
return new CommandsForRanges(summaries);
}
@Override
public String toString()
{
return "Scanning range intersections for " + AccordTask.this;
}
}
@Override
public DebuggableTask debuggable()
{
return this;
}
@Override
public long creationTimeNanos()
{
return createdAt;
}
@Override
public long startTimeNanos()
{
return runningAt;
}
@Override
public String description()
{
return toString();
}
}

View File

@ -68,7 +68,7 @@ public class AccordVerbHandler<T extends Request> implements IVerbHandler<T>
}
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);

View File

@ -85,6 +85,8 @@ public interface IAccordService
@Nonnull IAccordResult<TxnResult> 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<AccordExecutor> executors();
interface IAccordResult<V>
{
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<AccordExecutor> executors()
{
return List.of();
}
@Override
public @Nonnull IAccordResult<TxnResult> 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<AccordExecutor> executors()
{
return delegate.executors();
}
@Nonnull
@Override
public IAccordResult<TxnResult> coordinateAsync(long minEpoch, @Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, RequestTime requestTime)

View File

@ -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<Object, Throwable>
{
public static final ImmediateAsyncExecutor INSTANCE = new ImmediateAsyncExecutor();
@Override
public <T> AsyncChain<T> build(Callable<T> 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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<? super Result, Throwable> 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<? super Result, Throwable> 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<? super Result, Throwable> 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<? super Result, Throwable> 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<? super Result, Throwable> callback)
private boolean doInteropExecute(Node node, SequentialAsyncExecutor executor, FullRoute<?> route, Ballot ballot, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super Result, Throwable> 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<? super Result, Throwable> 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<? super Result, Throwable> 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;
}

View File

@ -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<AccordInteropApply> serializer = new ApplySerializer<AccordInteropApply>()
{
@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<LocalListeners.Registered> 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

View File

@ -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 <T> AsyncChain<T> build(Callable<T> 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<? super Result, Throwable> 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<? super Result, Throwable> 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);
});

View File

@ -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<? super Result, Throwable> 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<? super Result, Throwable> 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);

View File

@ -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<AccordInteropRead> requestSerializer = new ReadDataSerializer<AccordInteropRead>()
public static final IVersionedSerializer<AccordInteropRead> 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<ReadReply> replySerializer = new ReadDataSerializers.ReplySerializer<>(LocalReadData.serializer);
public static final IVersionedSerializer<ReadReply> 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<Pair<TokenKey, ReadResponse>> 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<Pair<TokenKey, ReadResponse>> copy = new ArrayList<>(localResponses.size());
for (Pair<TokenKey, ReadResponse> 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

View File

@ -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<AccordInteropReadRepair> requestSerializer = new ReadDataSerializer<AccordInteropReadRepair>()
public static final IVersionedSerializer<AccordInteropReadRepair> 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<ReadReply> replySerializer = new ReadDataSerializers.ReplySerializer<>(noop_data_serializer);
public static final IVersionedSerializer<ReadReply> 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

View File

@ -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<AccordInteropStableThenRead> requestSerializer = new ReadDataSerializer<>()
public static final IVersionedSerializer<AccordInteropStableThenRead> requestSerializer = new IVersionedSerializer<>()
{
@Override
public void serialize(AccordInteropStableThenRead read, DataOutputPlus out, Version version) throws IOException

View File

@ -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);
}
};

View File

@ -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<ReadData>
{
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<D extends Data> implements IVersionedSerializer<ReadReply>
{
final CommitOrReadNack[] nacks = CommitOrReadNack.values();
private final VersionedSerializer<D, Version> dataSerializer;
public ReplySerializer(VersionedSerializer<D, Version> 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<ReadReply> reply = new ReplySerializer<>(TxnData.nullableSerializer);
}

View File

@ -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> readData = new IVersionedSerializer<ReadData>()
{
@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<ApplyThenWaitUntilApplied>
{
@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> readTxnData = new ReadDataSerializer<ReadTxnData>()
{
@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> 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<T extends ReadData> extends IVersionedSerializer<T>
{
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<D extends Data> implements IVersionedSerializer<ReadReply>
{
final CommitOrReadNack[] nacks = CommitOrReadNack.values();
private final VersionedSerializer<D, Version> dataSerializer;
public ReplySerializer(VersionedSerializer<D, Version> 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<ReadReply> 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> waitUntilApplied = new ReadDataSerializer<WaitUntilApplied>()
{
@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> 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));
}
};
}

View File

@ -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<TxnDataValue> implements TxnResul
@Override
public TxnData merge(Data data)
{
TxnData that = (TxnData) data;
TxnData merged = new TxnData();
this.forEach(merged::put);
for (Map.Entry<Integer, TxnDataValue> 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<Integer, TxnDataValue> 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<Integer, TxnDataValue> 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<TxnDataValue> 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()
{

View File

@ -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()
{

View File

@ -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<FilteredPartition> 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<PartitionIterator> toPartitionIterator(boolean reversed)
{
// Sorting isn't preserved when merging TxnDataRangeValues together so sort here
@ -95,13 +126,25 @@ public class TxnDataRangeValue extends ArrayList<FilteredPartition> 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()
{

View File

@ -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();

View File

@ -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<ReadC
return TimeUnit.MICROSECONDS.toSeconds(executeAt.hlc());
}
public AsyncChain<Data> read(TableMetadatas tables, ConsistencyLevel consistencyLevel, Seekable key, Timestamp executeAt)
public AsyncChain<Data> 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<ReadC
switch (key.domain())
{
case Key:
return performLocalKeyRead(((SinglePartitionReadCommand) command).withTransactionalSettings(withoutReconciliation, nowInSeconds));
return performLocalKeyRead(executor, ((SinglePartitionReadCommand) command).withTransactionalSettings(withoutReconciliation, nowInSeconds));
case Range:
return performLocalRangeRead(((PartitionRangeReadCommand) command), key.asRange(), consistencyLevel, nowInSeconds);
return performLocalRangeRead(executor, ((PartitionRangeReadCommand) command), key.asRange(), consistencyLevel, nowInSeconds);
default:
throw new IllegalStateException("Unhandled domain " + key.domain());
}
@ -289,66 +286,35 @@ public class TxnNamedRead extends AbstractParameterisedVersionedSerialized<ReadC
return deserialize(tables);
}
private AsyncChain<Data> performLocalKeyRead(SinglePartitionReadCommand read)
private AsyncChain<Data> performLocalKeyRead(AccordExecutor executor, SinglePartitionReadCommand command)
{
Callable<Data> readCallable = () ->
Callable<Data> 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<ReadC
return command.withTransactionalSettings(nowInSeconds, subRange, isRangeContinuation, readsWithoutReconciliation(consistencyLevel));
}
private AsyncChain<Data> performLocalRangeRead(PartitionRangeReadCommand command, Range r, ConsistencyLevel consistencyLevel, long nowInSeconds)
private AsyncChain<Data> performLocalRangeRead(AccordExecutor executor, PartitionRangeReadCommand command, Range r, ConsistencyLevel consistencyLevel, long nowInSeconds)
{
PartitionRangeReadCommand read = commandForSubrange(command, r, consistencyLevel, nowInSeconds);
Callable<Data> readCallable = () ->
Callable<Data> 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<Data> submit(AccordExecutor executor, Callable<Data> readCallable, Object describe)
{
return executor.buildDebuggable(readCallable, describe);
}
static final ParameterisedVersionedSerializer<TxnNamedRead, TableMetadatasAndKeys, Version> serializer = new ParameterisedVersionedSerializer<>()

View File

@ -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<TxnNamedRead> 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<TxnNamedRead> items, @Nullable ConsistencyLevel cassandraConsistencyLevel)
@ -134,14 +134,13 @@ public class TxnRead extends AbstractKeySorted<TxnNamedRead> 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<TxnNamedRead> 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<TxnNamedRead> items, @Nullable ConsistencyLevel consistencyLevel, Domain domain)
@ -360,7 +359,7 @@ public class TxnRead extends AbstractKeySorted<TxnNamedRead> implements Read
}
else
{
Invariants.require(c == 0);
require(c == 0);
pending = pending.merge(add);
}
}
@ -380,22 +379,26 @@ public class TxnRead extends AbstractKeySorted<TxnNamedRead> implements Read
}
@Override
public AsyncChain<Data> read(Seekable key, SafeCommandStore safeStore, Timestamp executeAt, DataStore store)
public AsyncChain<Data> 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<AsyncChain<Data>> 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<Data> 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<AsyncChain<Data>> 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);
}

View File

@ -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<TxnWrite.Update> implements Writ
'}';
}
public AsyncChain<Void> write(TableMetadatas tables, boolean preserveTimestamps, long timestamp)
public AsyncChain<Void> 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<TxnWrite.Update> implements Writ
}
@Override
public AsyncChain<Void> apply(Seekable key, SafeCommandStore safeStore, TxnId txnId, Timestamp executeAt, DataStore store, PartialTxn txn)
public AsyncChain<Void> apply(SafeCommandStore safeStore, Seekable key, TxnId txnId, Timestamp executeAt, PartialTxn txn)
{
return applyDirect(safeStore.commandStore(), key, txnId, executeAt, txn);
}
@Override
public AsyncChain<Void> 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<TxnWrite.Update> implements Writ
List<AsyncChain<Void>> 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<Update> 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())

View File

@ -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");
}

View File

@ -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)

View File

@ -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<String, Long> metrics = diff(countingMetrics0).get(node);
Function<String, Long> 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:

View File

@ -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);
}
});
}

View File

@ -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<Writes, Result> 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

View File

@ -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:

View File

@ -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);
}

View File

@ -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<Writes, Result> result = AccordTestUtils.processTxnResult(commandStore, txnId, txn, executeAt);
Pair<Writes, Result> 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,

View File

@ -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);

View File

@ -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<TxnId> txnIdGen = rs -> txnId(1, clock.incrementAndGet(), 1);
qt().withSeed(3447647345436261108L).withPure(false)

View File

@ -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<Writes, Result> processTxnResult(AccordCommandStore commandStore, TxnId txnId, PartialTxn txn, Timestamp executeAt) throws Throwable
public static AsyncChain<Pair<Writes, Result>> processTxnResult(AccordCommandStore commandStore, TxnId txnId, PartialTxn txn, Timestamp executeAt) throws Throwable
{
AtomicReference<Pair<Writes, Result>> result = new AtomicReference<>();
AtomicReference<AsyncChain<Pair<Writes, Result>>> 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<Writes, Result> processTxnResultDirect(SafeCommandStore safeStore, TxnId txnId, PartialTxn txn, Timestamp executeAt)
public static AsyncChain<Pair<Writes, Result>> 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<TimeUnit> 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();

View File

@ -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<TimeUnit> 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()

View File

@ -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<SaveStatus> saveStatusChoices = Choices.uniform(EnumSet.complementOf(EnumSet.of(SaveStatus.TruncatedApply, SaveStatus.TruncatedUnapplied, SaveStatus.TruncatedApplyWithOutcome)).toArray(SaveStatus[]::new));
Choices<SaveStatus> saveStatusChoices = Choices.uniform(EnumSet.complementOf(EnumSet.of(SaveStatus.Applying, SaveStatus.TruncatedApply, SaveStatus.TruncatedUnapplied, SaveStatus.TruncatedApplyWithOutcome)).toArray(SaveStatus[]::new));
Supplier<SaveStatus> 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; }
}; }